Skip to content

Commit 3a9fcb1

Browse files
committed
Add hosting integration for Azure Container Registry
1 parent 935f06b commit 3a9fcb1

29 files changed

+883
-198
lines changed

Aspire.sln

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.0.31903.59
@@ -675,6 +675,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.Npgsql.EntityF
675675
EndProject
676676
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Components.Common.Tests", "tests\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj", "{30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}"
677677
EndProject
678+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
679+
EndProject
680+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Azure.ContainerRegistry", "src\Aspire.Hosting.Azure.ContainerRegistry\Aspire.Hosting.Azure.ContainerRegistry.csproj", "{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}"
681+
EndProject
678682
Global
679683
GlobalSection(SolutionConfigurationPlatforms) = preSolution
680684
Debug|Any CPU = Debug|Any CPU
@@ -3961,6 +3965,18 @@ Global
39613965
{30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}.Release|x64.Build.0 = Release|Any CPU
39623966
{30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}.Release|x86.ActiveCfg = Release|Any CPU
39633967
{30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}.Release|x86.Build.0 = Release|Any CPU
3968+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3969+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
3970+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Debug|x64.ActiveCfg = Debug|Any CPU
3971+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Debug|x64.Build.0 = Debug|Any CPU
3972+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Debug|x86.ActiveCfg = Debug|Any CPU
3973+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Debug|x86.Build.0 = Debug|Any CPU
3974+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
3975+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Release|Any CPU.Build.0 = Release|Any CPU
3976+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Release|x64.ActiveCfg = Release|Any CPU
3977+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Release|x64.Build.0 = Release|Any CPU
3978+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Release|x86.ActiveCfg = Release|Any CPU
3979+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}.Release|x86.Build.0 = Release|Any CPU
39643980
EndGlobalSection
39653981
GlobalSection(SolutionProperties) = preSolution
39663982
HideSolutionNode = FALSE
@@ -4285,6 +4301,7 @@ Global
42854301
{192747A2-9338-DECF-5C8C-28EB8E13829B} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
42864302
{8FCA0CFA-7823-6A2F-342A-107A994915B0} = {C424395C-1235-41A4-BF55-07880A04368C}
42874303
{30950CEB-2232-F9FC-04FF-ADDCB8AC30A7} = {C424395C-1235-41A4-BF55-07880A04368C}
4304+
{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
42884305
EndGlobalSection
42894306
GlobalSection(ExtensibilityGlobals) = postSolution
42904307
SolutionGuid = {47DCFECF-5631-4BDE-A1EC-BE41E90F60C4}

playground/AzureContainerApps/AzureContainerApps.AppHost/api-roles-account-kv.module.bicep

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ resource account_kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
99
name: account_kv_outputs_name
1010
}
1111

12-
resource account_kv_KeyVaultAdministrator 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
13-
name: guid(account_kv.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483'))
12+
resource account_kv_KeyVaultSecretsUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
13+
name: guid(account_kv.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6'))
1414
properties: {
1515
principalId: principalId
16-
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')
16+
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
1717
principalType: 'ServicePrincipal'
1818
}
1919
scope: account_kv

src/Aspire.Hosting.Azure.AppContainers/Aspire.Hosting.Azure.AppContainers.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<PackageReference Include="Azure.Provisioning.KeyVault" />
2020
<PackageReference Include="Azure.Provisioning.Storage" />
2121
<ProjectReference Include="..\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" />
22+
<ProjectReference Include="..\Aspire.Hosting.Azure.ContainerRegistry\Aspire.Hosting.Azure.ContainerRegistry.csproj" />
2223
</ItemGroup>
2324

2425
</Project>

src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,19 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
9898

9999
infra.Add(identity);
100100

101-
var containerRegistry = new ContainerRegistryService(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_acr"))
101+
ContainerRegistryService? containerRegistry = null;
102+
if (appEnvResource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var registryReferenceAnnotation) && registryReferenceAnnotation.Registry is AzureProvisioningResource registry)
102103
{
103-
Sku = new() { Name = ContainerRegistrySkuName.Basic },
104-
Tags = tags
105-
};
106-
104+
containerRegistry = (ContainerRegistryService)registry.AddAsExistingResource(infra);
105+
}
106+
else
107+
{
108+
containerRegistry = new ContainerRegistryService(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_acr"))
109+
{
110+
Sku = new() { Name = ContainerRegistrySkuName.Basic },
111+
Tags = tags
112+
};
113+
}
107114
infra.Add(containerRegistry);
108115

109116
var pullRa = containerRegistry.CreateRoleAssignment(ContainerRegistryBuiltInRole.AcrPull, identity);
@@ -345,7 +352,7 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
345352
/// <returns><see cref="IResourceBuilder{T}"/></returns>
346353
/// <remarks>
347354
/// By default, the container app environment resources use a different naming convention than azd.
348-
///
355+
///
349356
/// This method allows for reusing the previously deployed resources if the application was deployed using
350357
/// azd without calling <see cref="AddAzureContainerAppEnvironment"/>
351358
/// </remarks>

src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
2+
13
// Licensed to the .NET Foundation under one or more agreements.
24
// The .NET Foundation licenses this file to you under the MIT license.
35

@@ -60,11 +62,12 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
6062
// container app environment in the deployment target information
6163
// associated with each compute resource that needs an image
6264
#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
63-
r.Annotations.Add(new DeploymentTargetAnnotation(containerApp)
65+
if (r is IComputeResource computeResource)
6466
{
65-
ContainerRegistryInfo = caes.FirstOrDefault(),
66-
ComputeEnvironment = environment as IComputeEnvironmentResource // will be null if azd
67-
});
67+
computeResource.ContainerRegistry = caes.FirstOrDefault();
68+
computeResource.ComputeEnvironment = environment as IComputeEnvironmentResource;
69+
}
70+
r.Annotations.Add(new DeploymentTargetAnnotation(containerApp));
6871
#pragma warning restore ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
6972
}
7073

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
5+
<IsPackable>true</IsPackable>
6+
<PackageTags>aspire integration hosting azure containerregistry</PackageTags>
7+
<Description>Azure Container Registry resource types for .NET Aspire.</Description>
8+
<EnablePackageValidation>false</EnablePackageValidation>
9+
<SuppressFinalPackageVersion>true</SuppressFinalPackageVersion>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Compile Include="$(RepoRoot)src\Shared\AzureRoleAssignmentUtils.cs" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" />
18+
<PackageReference Include="Azure.Provisioning" />
19+
<PackageReference Include="Azure.Provisioning.ContainerRegistry" />
20+
</ItemGroup>
21+
22+
23+
</Project>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
2+
3+
// Licensed to the .NET Foundation under one or more agreements.
4+
// The .NET Foundation licenses this file to you under the MIT license.
5+
6+
using Aspire.Hosting.ApplicationModel;
7+
using Aspire.Hosting.Azure;
8+
using Aspire.Hosting.Azure.ContainerRegistry;
9+
using Azure.Provisioning;
10+
using Azure.Provisioning.ContainerRegistry;
11+
12+
namespace Aspire.Hosting;
13+
14+
/// <summary>
15+
/// Provides extension methods for adding Azure Container Registry resources to the application model.
16+
/// </summary>
17+
public static class AzureContainerRegistryExtensions
18+
{
19+
/// <summary>
20+
/// Adds an Azure Container Registry resource to the application model.
21+
/// </summary>
22+
/// <param name="builder">The builder for the distributed application.</param>
23+
/// <param name="name">The name of the resource.</param>
24+
/// <returns>A reference to the <see cref="IResourceBuilder{AzureContainerRegistryResource}"/> builder.</returns>
25+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> is null.</exception>
26+
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is null or empty.</exception>
27+
public static IResourceBuilder<AzureContainerRegistryResource> AddAzureContainerRegistry(this IDistributedApplicationBuilder builder, [ResourceName] string name)
28+
{
29+
ArgumentNullException.ThrowIfNull(builder);
30+
ArgumentException.ThrowIfNullOrEmpty(name);
31+
32+
builder.AddAzureProvisioning();
33+
34+
var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
35+
{
36+
var registry = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infrastructure,
37+
(identifier, name) =>
38+
{
39+
var resource = ContainerRegistryService.FromExisting(identifier);
40+
resource.Name = name;
41+
return resource;
42+
},
43+
(infrastructure) => new ContainerRegistryService(infrastructure.AspireResource.GetBicepIdentifier())
44+
{
45+
Sku = new() { Name = ContainerRegistrySkuName.Basic },
46+
Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
47+
});
48+
49+
infrastructure.Add(registry);
50+
51+
infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = registry.Name });
52+
infrastructure.Add(new ProvisioningOutput("loginServer", typeof(string)) { Value = registry.LoginServer });
53+
};
54+
55+
var resource = new AzureContainerRegistryResource(name, configureInfrastructure);
56+
return builder.AddResource(resource)
57+
.WithAnnotation(new DefaultRoleAssignmentsAnnotation(new HashSet<RoleDefinition>()));
58+
}
59+
60+
/// <summary>
61+
/// Configures a resource that implements <see cref="IContainerRegistry"/> to use the specified Azure Container Registry.
62+
/// </summary>
63+
/// <typeparam name="T">The resource type that implements <see cref="IContainerRegistry"/>.</typeparam>
64+
/// <param name="builder">The resource builder for a resource that implements <see cref="IContainerRegistry"/>.</param>
65+
/// <param name="registryBuilder">The resource builder for the <see cref="AzureContainerRegistryResource"/> to use.</param>
66+
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
67+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="registryBuilder"/> is null.</exception>
68+
public static IResourceBuilder<T> WithAzureContainerRegistry<T>(this IResourceBuilder<T> builder, IResourceBuilder<AzureContainerRegistryResource> registryBuilder)
69+
where T : IResource, IComputeEnvironmentResource
70+
{
71+
ArgumentNullException.ThrowIfNull(builder);
72+
ArgumentNullException.ThrowIfNull(registryBuilder);
73+
74+
// Add a ContainerRegistryReferenceAnnotation to indicate that the resource is using a specific registry
75+
builder.WithAnnotation(new ContainerRegistryReferenceAnnotation(registryBuilder.Resource));
76+
77+
return builder;
78+
}
79+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
2+
3+
// Licensed to the .NET Foundation under one or more agreements.
4+
// The .NET Foundation licenses this file to you under the MIT license.
5+
6+
using Aspire.Hosting.ApplicationModel;
7+
using Azure.Provisioning.ContainerRegistry;
8+
using Azure.Provisioning.Primitives;
9+
10+
namespace Aspire.Hosting.Azure.ContainerRegistry;
11+
12+
/// <summary>
13+
/// Represents an Azure Container Registry resource.
14+
/// </summary>
15+
public class AzureContainerRegistryResource(string name, Action<AzureResourceInfrastructure> configureInfrastructure)
16+
: AzureProvisioningResource(name, configureInfrastructure), IContainerRegistry
17+
{
18+
/// <summary>
19+
/// The name of the Azure Container Registry.
20+
/// </summary>
21+
public BicepOutputReference RegistryName => new("name", this);
22+
23+
/// <summary>
24+
/// The endpoint of the Azure Container Registry.
25+
/// </summary>
26+
public BicepOutputReference RegistryEndpoint => new("loginServer", this);
27+
28+
/// <inheritdoc/>
29+
ReferenceExpression IContainerRegistry.Name => ReferenceExpression.Create($"{RegistryName}");
30+
31+
/// <inheritdoc/>
32+
ReferenceExpression IContainerRegistry.Endpoint => ReferenceExpression.Create($"{RegistryEndpoint}");
33+
34+
/// <inheritdoc/>
35+
public override ProvisionableResource AddAsExistingResource(AzureResourceInfrastructure infra)
36+
{
37+
var store = ContainerRegistryService.FromExisting(this.GetBicepIdentifier());
38+
store.Name = RegistryName.AsProvisioningParameter(infra);
39+
infra.Add(store);
40+
return store;
41+
}
42+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
2+
3+
// Licensed to the .NET Foundation under one or more agreements.
4+
// The .NET Foundation licenses this file to you under the MIT license.
5+
6+
using Aspire.Hosting.ApplicationModel;
7+
8+
namespace Aspire.Hosting.Azure;
9+
10+
/// <summary>
11+
/// Annotation that indicates a resource is using a specific container registry.
12+
/// </summary>
13+
/// <remarks>
14+
/// Initializes a new instance of the <see cref="ContainerRegistryReferenceAnnotation"/> class.
15+
/// </remarks>
16+
/// <param name="registry">The container registry resource.</param>
17+
public class ContainerRegistryReferenceAnnotation(IContainerRegistry registry) : IResourceAnnotation
18+
{
19+
/// <summary>
20+
/// Gets the container registry resource.
21+
/// </summary>
22+
public IContainerRegistry Registry { get; } = registry;
23+
}

src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<PackageReference Include="System.IO.Hashing" />
3434
<PackageReference Include="Azure.Provisioning" />
3535
<PackageReference Include="Azure.Provisioning.KeyVault" />
36+
<PackageReference Include="Azure.Provisioning.ContainerRegistry" />
3637
<PackageReference Include="Azure.ResourceManager.Authorization" />
3738
<PackageReference Include="Azure.ResourceManager.KeyVault" />
3839
<PackageReference Include="Azure.ResourceManager.Resources" />

0 commit comments

Comments
 (0)