Skip to content

Commit c012290

Browse files
committed
Model DefaultRegistry in resource and keep using environment as registry
1 parent b77c5b3 commit c012290

File tree

8 files changed

+149
-53
lines changed

8 files changed

+149
-53
lines changed

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,21 @@ await context.ReportingStep.CompleteAsync(
165165

166166
internal Dictionary<string, (IResource resource, ContainerMountAnnotation volume, int index, BicepOutputReference outputReference)> VolumeNames { get; } = [];
167167

168-
private IAzureContainerRegistry GetAssociatedRegistry()
168+
internal AzureContainerRegistryResource? DefaultContainerRegistry { get; set; }
169+
170+
private IContainerRegistry GetAssociatedRegistry()
169171
{
170172
if (this.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var annotation) &&
171-
annotation.Registry is IAzureContainerRegistry registry)
173+
annotation.Registry is IContainerRegistry registry)
172174
{
173175
return registry;
174176
}
175177

178+
if (DefaultContainerRegistry is not null)
179+
{
180+
return DefaultContainerRegistry;
181+
}
182+
176183
throw new InvalidOperationException($"No Azure container registry associated with environment '{Name}'");
177184
}
178185

@@ -181,7 +188,7 @@ private IAzureContainerRegistry GetAssociatedRegistry()
181188

182189
ReferenceExpression IContainerRegistry.Endpoint => GetAssociatedRegistry().Endpoint;
183190

184-
ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => GetAssociatedRegistry().ManagedIdentityId;
191+
ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => ReferenceExpression.Create($"{ContainerRegistryManagedIdentityId}");
185192

186193
ReferenceExpression IComputeEnvironmentResource.GetHostAddressExpression(EndpointReference endpointReference)
187194
{

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

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,20 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
8989

9090
infra.Add(identity);
9191

92-
if (!appEnvResource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var registryReferenceAnnotation) || registryReferenceAnnotation.Registry is not AzureProvisioningResource registry)
92+
AzureProvisioningResource? registry = null;
93+
if (appEnvResource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var registryReferenceAnnotation) &&
94+
registryReferenceAnnotation.Registry is AzureProvisioningResource explicitRegistry)
9395
{
94-
throw new InvalidOperationException($"Container registry reference annotation not found on environment '{appEnvResource.Name}'. This should have been added automatically.");
96+
registry = explicitRegistry;
97+
}
98+
else if (appEnvResource.DefaultContainerRegistry is not null)
99+
{
100+
registry = appEnvResource.DefaultContainerRegistry;
101+
}
102+
103+
if (registry is null)
104+
{
105+
throw new InvalidOperationException($"No container registry associated with environment '{appEnvResource.Name}'. This should have been added automatically.");
95106
}
96107

97108
var containerRegistry = (ContainerRegistryService)registry.AddAsExistingResource(infra);
@@ -316,23 +327,29 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
316327
});
317328
});
318329

319-
if (!containerAppEnvResource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out _))
330+
// Create the default container registry resource without adding to the model
331+
var registryName = $"{containerAppEnvResource.Name}-acr";
332+
var defaultRegistry = CreateDefaultContainerRegistry(builder, registryName);
333+
containerAppEnvResource.DefaultContainerRegistry = defaultRegistry;
334+
335+
builder.Eventing.Subscribe<BeforeStartEvent>((data, token) =>
320336
{
321-
var registryName = $"{containerAppEnvResource.Name}-acr";
322-
var registryBuilder = builder.AddAzureContainerRegistry(registryName);
337+
if (!containerAppEnvResource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out _))
338+
{
339+
data.Model.Resources.Add(defaultRegistry);
340+
}
323341

324-
var appEnvBuilder = builder.CreateResourceBuilder(containerAppEnvResource);
325-
appEnvBuilder.WithAzureContainerRegistry(registryBuilder);
326-
}
342+
return Task.CompletedTask;
343+
});
327344

328-
if (builder.ExecutionContext.IsRunMode)
329-
{
345+
// Create the resource builder first, then attach the registry to avoid recreating builders
346+
var appEnvBuilder = builder.ExecutionContext.IsRunMode
330347
// HACK: We need to return a valid resource builder for the container app environment
331348
// but in run mode, we don't want to add the resource to the builder.
332-
return builder.CreateResourceBuilder(containerAppEnvResource);
333-
}
349+
? builder.CreateResourceBuilder(containerAppEnvResource)
350+
: builder.AddResource(containerAppEnvResource);
334351

335-
return builder.AddResource(containerAppEnvResource);
352+
return appEnvBuilder;
336353
}
337354

338355
/// <summary>
@@ -381,4 +398,32 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> WithAzureLo
381398

382399
return builder;
383400
}
401+
402+
private static AzureContainerRegistryResource CreateDefaultContainerRegistry(IDistributedApplicationBuilder builder, string name)
403+
{
404+
var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
405+
{
406+
var registry = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infrastructure,
407+
(identifier, resourceName) =>
408+
{
409+
var resource = ContainerRegistryService.FromExisting(identifier);
410+
resource.Name = resourceName;
411+
return resource;
412+
},
413+
(infra) => new ContainerRegistryService(infra.AspireResource.GetBicepIdentifier())
414+
{
415+
Sku = new ContainerRegistrySku { Name = ContainerRegistrySkuName.Basic },
416+
Tags = { { "aspire-resource-name", infra.AspireResource.Name } }
417+
});
418+
419+
infrastructure.Add(registry);
420+
infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = registry.Name });
421+
infrastructure.Add(new ProvisioningOutput("loginServer", typeof(string)) { Value = registry.LoginServer });
422+
};
423+
424+
var resource = new AzureContainerRegistryResource(name, configureInfrastructure);
425+
builder.CreateResourceBuilder(resource);
426+
427+
return resource;
428+
}
384429
}

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,12 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
4040
{
4141
var containerApp = await containerAppEnvironmentContext.CreateContainerAppAsync(r, options.Value, cancellationToken).ConfigureAwait(false);
4242

43-
IContainerRegistry? registry = null;
44-
if (environment.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var registryAnnotation))
45-
{
46-
registry = registryAnnotation.Registry;
47-
}
48-
4943
// Capture information about the container registry used by the
5044
// container app environment in the deployment target information
5145
// associated with each compute resource that needs an image
5246
r.Annotations.Add(new DeploymentTargetAnnotation(containerApp)
5347
{
54-
ContainerRegistry = registry,
48+
ContainerRegistry = environment,
5549
ComputeEnvironment = environment
5650
});
5751
}

src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,20 @@ public static IResourceBuilder<AzureAppServiceEnvironmentResource> AddAzureAppSe
6767

6868
infra.Add(identity);
6969

70-
if (!resource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var registryReferenceAnnotation) || registryReferenceAnnotation.Registry is not AzureProvisioningResource registry)
70+
AzureProvisioningResource? registry = null;
71+
if (resource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var registryReferenceAnnotation) &&
72+
registryReferenceAnnotation.Registry is AzureProvisioningResource explicitRegistry)
7173
{
72-
throw new InvalidOperationException($"Container registry reference annotation not found on environment '{resource.Name}'. This should have been added automatically.");
74+
registry = explicitRegistry;
75+
}
76+
else if (resource.DefaultContainerRegistry is not null)
77+
{
78+
registry = resource.DefaultContainerRegistry;
79+
}
80+
81+
if (registry is null)
82+
{
83+
throw new InvalidOperationException($"No container registry associated with environment '{resource.Name}'. This should have been added automatically.");
7384
}
7485

7586
var containerRegistry = (ContainerRegistryService)registry.AddAsExistingResource(infra);
@@ -202,21 +213,27 @@ public static IResourceBuilder<AzureAppServiceEnvironmentResource> AddAzureAppSe
202213
}
203214
});
204215

205-
if (!resource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out _))
216+
// Create the default container registry resource without adding to the model
217+
var registryName = $"{resource.Name}-acr";
218+
var defaultRegistry = CreateDefaultContainerRegistry(builder, registryName);
219+
resource.DefaultContainerRegistry = defaultRegistry;
220+
221+
builder.Eventing.Subscribe<BeforeStartEvent>((data, token) =>
206222
{
207-
var registryName = $"{resource.Name}-acr";
208-
var registryBuilder = builder.AddAzureContainerRegistry(registryName);
223+
if (!resource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out _))
224+
{
225+
data.Model.Resources.Add(defaultRegistry);
226+
}
209227

210-
var appServiceEnvBuilder = builder.CreateResourceBuilder(resource);
211-
appServiceEnvBuilder.WithAzureContainerRegistry(registryBuilder);
212-
}
228+
return Task.CompletedTask;
229+
});
213230

214-
if (!builder.ExecutionContext.IsPublishMode)
215-
{
216-
return builder.CreateResourceBuilder(resource);
217-
}
231+
// Create the resource builder first, then attach the registry to avoid recreating builders
232+
var appServiceEnvBuilder = builder.ExecutionContext.IsPublishMode
233+
? builder.AddResource(resource)
234+
: builder.CreateResourceBuilder(resource);
218235

219-
return builder.AddResource(resource);
236+
return appServiceEnvBuilder;
220237
}
221238

222239
/// <summary>
@@ -292,4 +309,32 @@ public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithAutomatic
292309
builder.Resource.EnableAutomaticScaling = true;
293310
return builder;
294311
}
312+
313+
private static AzureContainerRegistryResource CreateDefaultContainerRegistry(IDistributedApplicationBuilder builder, string name)
314+
{
315+
var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
316+
{
317+
var registry = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infrastructure,
318+
(identifier, resourceName) =>
319+
{
320+
var resource = ContainerRegistryService.FromExisting(identifier);
321+
resource.Name = resourceName;
322+
return resource;
323+
},
324+
(infra) => new ContainerRegistryService(infra.AspireResource.GetBicepIdentifier())
325+
{
326+
Sku = new ContainerRegistrySku { Name = ContainerRegistrySkuName.Basic },
327+
Tags = { { "aspire-resource-name", infra.AspireResource.Name } }
328+
});
329+
330+
infrastructure.Add(registry);
331+
infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = registry.Name });
332+
infrastructure.Add(new ProvisioningOutput("loginServer", typeof(string)) { Value = registry.LoginServer });
333+
};
334+
335+
var resource = new AzureContainerRegistryResource(name, configureInfrastructure);
336+
builder.CreateResourceBuilder(resource);
337+
338+
return resource;
339+
}
295340
}

src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,19 +203,26 @@ await context.ReportingStep.CompleteAsync(
203203
internal static BicepValue<string> GetWebSiteSuffixBicep() =>
204204
BicepFunction.GetUniqueString(BicepFunction.GetResourceGroup().Id);
205205

206-
private IAzureContainerRegistry GetAssociatedRegistry()
206+
internal AzureContainerRegistryResource? DefaultContainerRegistry { get; set; }
207+
208+
private IContainerRegistry GetAssociatedRegistry()
207209
{
208210
if (this.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var annotation) &&
209-
annotation.Registry is IAzureContainerRegistry registry)
211+
annotation.Registry is IContainerRegistry registry)
210212
{
211213
return registry;
212214
}
213215

216+
if (DefaultContainerRegistry is not null)
217+
{
218+
return DefaultContainerRegistry;
219+
}
220+
214221
throw new InvalidOperationException($"No Azure container registry associated with environment '{Name}'");
215222
}
216223

217224
// Implement IAzureContainerRegistry interface by delegating to the associated registry
218-
ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => GetAssociatedRegistry().ManagedIdentityId;
225+
ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => ReferenceExpression.Create($"{ContainerRegistryManagedIdentityId}");
219226

220227
ReferenceExpression IContainerRegistry.Name => GetAssociatedRegistry().Name;
221228

src/Aspire.Hosting.Azure.AppService/AzureAppServiceInfrastructure.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,9 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
4848

4949
var website = await appServiceEnvironmentContext.CreateAppServiceAsync(resource, provisioningOptions.Value, cancellationToken).ConfigureAwait(false);
5050

51-
IContainerRegistry? registry = null;
52-
if (appServiceEnvironment.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var registryAnnotation))
53-
{
54-
registry = registryAnnotation.Registry;
55-
}
56-
5751
resource.Annotations.Add(new DeploymentTargetAnnotation(website)
5852
{
59-
ContainerRegistry = registry,
53+
ContainerRegistry = appServiceEnvironment,
6054
ComputeEnvironment = appServiceEnvironment
6155
});
6256
}

tests/Aspire.Hosting.Azure.Tests/AzureUserAssignedIdentityTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public async Task AddAzureUserAssignedIdentity_WithRoleAssignments_Works()
7272
Assert.Collection(model.Resources.OrderBy(r => r.Name),
7373
r => Assert.IsType<AzureEnvironmentResource>(r),
7474
r => Assert.IsType<AzureContainerAppEnvironmentResource>(r),
75+
r => Assert.IsType<AzureContainerRegistryResource>(r),
7576
r => Assert.IsType<AzureUserAssignedIdentityResource>(r),
7677
r =>
7778
{
@@ -83,7 +84,7 @@ public async Task AddAzureUserAssignedIdentity_WithRoleAssignments_Works()
8384
var identityResource = Assert.Single(model.Resources.OfType<AzureUserAssignedIdentityResource>());
8485
var (_, identityBicep) = await GetManifestWithBicep(identityResource, skipPreparer: true);
8586

86-
var registryResource = Assert.Single(model.Resources.OfType<AzureContainerRegistryResource>());
87+
var registryResource = Assert.Single(model.Resources.OfType<AzureContainerRegistryResource>(), r => r.Name == "myregistry");
8788
var (_, registryBicep) = await GetManifestWithBicep(registryResource, skipPreparer: true);
8889

8990
var identityRoleAssignments = Assert.Single(model.Resources.OfType<AzureProvisioningResource>(), r => r.Name == "myidentity-roles-myregistry");
@@ -154,6 +155,7 @@ public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_Works()
154155
Assert.Collection(model.Resources,
155156
r => Assert.IsType<AzureEnvironmentResource>(r),
156157
r => Assert.IsType<AzureContainerAppEnvironmentResource>(r),
158+
r => Assert.IsType<AzureContainerRegistryResource>(r),
157159
r => Assert.IsType<AzureStorageResource>(r),
158160
r => Assert.IsType<AzureUserAssignedIdentityResource>(r),
159161
r => Assert.IsType<ProjectResource>(r),
@@ -209,6 +211,7 @@ public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_AzureAppServ
209211
Assert.Collection(model.Resources,
210212
r => Assert.IsType<AzureEnvironmentResource>(r),
211213
r => Assert.IsType<AzureAppServiceEnvironmentResource>(r),
214+
r => Assert.IsType<AzureContainerRegistryResource>(r),
212215
r => Assert.IsType<AzureStorageResource>(r),
213216
r => Assert.IsType<AzureUserAssignedIdentityResource>(r),
214217
r => Assert.IsType<ProjectResource>(r),
@@ -285,6 +288,7 @@ public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_MultipleProj
285288
Assert.Collection(model.Resources,
286289
r => Assert.IsType<AzureEnvironmentResource>(r),
287290
r => Assert.IsType<AzureContainerAppEnvironmentResource>(r),
291+
r => Assert.IsType<AzureContainerRegistryResource>(r),
288292
r => Assert.IsType<AzureStorageResource>(r),
289293
r => Assert.IsType<AzureUserAssignedIdentityResource>(r),
290294
r => Assert.IsType<ProjectResource>(r),

tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleAzureContainerAppEnvironmentsSupported.verified.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
{
22
"$schema": "https://json.schemastore.org/aspire-8.0.json",
33
"resources": {
4-
"env1-acr": {
5-
"type": "azure.bicep.v0",
6-
"path": "env1-acr.module.bicep"
7-
},
84
"env1": {
95
"type": "azure.bicep.v0",
106
"path": "env1.module.bicep",
@@ -13,9 +9,9 @@
139
"userPrincipalId": ""
1410
}
1511
},
16-
"env2-acr": {
12+
"env1-acr": {
1713
"type": "azure.bicep.v0",
18-
"path": "env2-acr.module.bicep"
14+
"path": "env1-acr.module.bicep"
1915
},
2016
"env2": {
2117
"type": "azure.bicep.v0",
@@ -25,6 +21,10 @@
2521
"userPrincipalId": ""
2622
}
2723
},
24+
"env2-acr": {
25+
"type": "azure.bicep.v0",
26+
"path": "env2-acr.module.bicep"
27+
},
2828
"api1": {
2929
"type": "container.v1",
3030
"image": "myimage:latest",

0 commit comments

Comments
 (0)