Expose container app constants
Background and Motivation
AddAzureContainerAppEnvironment outputs a number of provisioning parameters that can be referenced using their string names - these values can be useful within infrastructure provisioning; however it currently relies on using the string names.
Proposed API
Simply expos a constants object that includes the parameters exposed in AddAzureContainerAppEnvironment
infra.Add(new ProvisioningOutput("MANAGED_IDENTITY_NAME", typeof(string))
{
Value = identity.Name
});
infra.Add(new ProvisioningOutput("MANAGED_IDENTITY_PRINCIPAL_ID", typeof(string))
{
Value = identity.PrincipalId
});
infra.Add(new ProvisioningOutput("AZURE_LOG_ANALYTICS_WORKSPACE_NAME", typeof(string))
{
Value = laWorkspace.Name
});
infra.Add(new ProvisioningOutput("AZURE_LOG_ANALYTICS_WORKSPACE_ID", typeof(string))
{
Value = laWorkspace.Id
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_REGISTRY_NAME", typeof(string))
{
Value = containerRegistry.Name
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_REGISTRY_ENDPOINT", typeof(string))
{
Value = containerRegistry.LoginServer
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID", typeof(string))
{
Value = identity.Id
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_APPS_ENVIRONMENT_NAME", typeof(string))
{
Value = containerAppEnvironment.Name
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_APPS_ENVIRONMENT_ID", typeof(string))
{
Value = containerAppEnvironment.Id
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN", typeof(string))
{
Value = containerAppEnvironment.DefaultDomain
});
namespace Aspire.Hosting;
public class AzureContainerConstants
{
public const string MANAGED_IDENTITY_NAME = nameof(MANAGED_IDENTITY_NAME);
public const string MANAGED_IDENTITY_PRINCIPAL_ID = nameof(MANAGED_IDENTITY_PRINCIPAL_ID);
public const string AZURE_LOG_ANALYTICS_WORKSPACE_NAME = nameof(AZURE_LOG_ANALYTICS_WORKSPACE_NAME);
public const string AZURE_LOG_ANALYTICS_WORKSPACE_ID = nameof(AZURE_LOG_ANALYTICS_WORKSPACE_ID);
public const string AZURE_CONTAINER_REGISTRY_NAME = nameof(AZURE_CONTAINER_REGISTRY_NAME);
public const string AZURE_CONTAINER_REGISTRY_ENDPOINT = nameof(AZURE_CONTAINER_REGISTRY_ENDPOINT);
public const string AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID = nameof(AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID);
public const string AZURE_CONTAINER_APPS_ENVIRONMENT_NAME = nameof(AZURE_CONTAINER_APPS_ENVIRONMENT_NAME);
public const string AZURE_CONTAINER_APPS_ENVIRONMENT_ID = nameof(AZURE_CONTAINER_APPS_ENVIRONMENT_ID);
public const string AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN = nameof(AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN);
}
Usage Examples
var acrImageRegistry = builder.Resources.OfType<ProvisioningOutput>().FirstOrDefault(_ => _.BicepIdentifier == AzureContainerConstants.AZURE_CONTAINER_REGISTRY_ENDPOINT);
Alternative Designs
Risks
We made these private until further notice. What are you using it for?
https://github.com/dotnet/aspire/pull/8529
For now you can manually create a BicepOutputReference from it with right name.
I feel that the fact that these variables may change over time supports exposing them - yes, if you change it, it breaks my code but it breaks it loud and ugly - the type of breaking change that is easy to address vs what I currently have - as you say manually creating it with the right name means if you change them, my code still breaks but I'm left wondering why.
for reference - yes I have manually created the constants in my project - I'm using them for this:
public static IResourceBuilder<AzureContainerRegistryResource> AddAzureContainerRegistry(this IDistributedApplicationBuilder builder, string name)
{
var registry = new AzureContainerRegistryResource(name, static infrastructure =>
{
var registryResource = (AzureContainerRegistryResource)infrastructure.AspireResource;
var containerRegistry = new ContainerRegistryService(registryResource.GetBicepIdentifier())
{
Sku = new() { Name = ContainerRegistrySkuName.Basic },
};
infrastructure.Add(containerRegistry);
infrastructure.Add(new ProvisioningOutput(AzureContainerConstants.AZURE_CONTAINER_REGISTRY_NAME, typeof(string))
{
Value = containerRegistry.Name
});
infrastructure.Add(new ProvisioningOutput(AzureContainerConstants.AZURE_CONTAINER_REGISTRY_ENDPOINT, typeof(string))
{
Value = containerRegistry.LoginServer
});
});
return builder.AddResource(registry);
}
public static IResourceBuilder<AzureContainerAppEnvironmentResource> WithReference(this IResourceBuilder<AzureContainerAppEnvironmentResource> builder, IResourceBuilder<AzureContainerRegistryResource> registryBuilder)
{
var managedIdentity = builder.GetOutput(AzureContainerConstants.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID);
var roleDefinitionId = BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", ContainerRegistryBuiltInRole.AcrPull.ToString());
registryBuilder.ConfigureInfrastructure(infrastructure =>
{
if (infrastructure.GetProvisionableResources().OfType<ContainerRegistryService>().FirstOrDefault() is { } containerRegistry)
{
var principalId = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string));
infrastructure.Add(principalId);
var pullRa = containerRegistry.CreateRoleAssignment(ContainerRegistryBuiltInRole.AcrPull,
RoleManagementPrincipalType.ServicePrincipal, principalId, "test");
pullRa.Name = BicepFunction.CreateGuid(containerRegistry.Id, principalId, pullRa.RoleDefinitionId);
infrastructure.Add(pullRa);
}
}).WithParameter(AzureBicepResource.KnownParameters.PrincipalId, managedIdentity);
builder.ConfigureInfrastructure(infrastructure =>
{
if (infrastructure.GetProvisionableResources().OfType<ContainerRegistryService>().FirstOrDefault() is { } containerRegistry)
{
if (infrastructure.GetProvisionableResources().OfType<RoleAssignment>().FirstOrDefault(_ =>
{
return _.RoleDefinitionId.Value! == roleDefinitionId.Value!;
}) is { } roleAssignment)
{
infrastructure.Remove(roleAssignment);
}
infrastructure.Remove(containerRegistry);
}
var outputParameters = infrastructure.GetProvisionableResources().OfType<ProvisioningOutput>();
if (outputParameters.FirstOrDefault(_ => _.BicepIdentifier == AzureContainerConstants.AZURE_CONTAINER_REGISTRY_NAME) is { } registryName)
{
infrastructure.Remove(registryName);
}
if (outputParameters.FirstOrDefault(_ => _.BicepIdentifier == AzureContainerConstants.AZURE_CONTAINER_REGISTRY_ENDPOINT) is { } registryEndpoint)
{
infrastructure.Remove(registryEndpoint);
}
});
return builder;
}
Can you explain what you’re trying to do and why?
I'm decoupling my container registry from my container app environment because I want one registry but more than one environment (across multiple projects)
I’d file an issue asking for that more directly
Ok
I believe @captainsafia has plans to support associating an existing acr with the ACA resource. Seems like it fits in well
@FullStackChef TODO: rewrite issue to be more useful per suggestion
I believe @captainsafia has plans to support associating an existing acr with the ACA resource. Seems like it fits in well
https://github.com/dotnet/aspire/issues/9005 tracks this.
I will re-purpose this issue to expose container app specific properties on this resource, but not the other unrelated resources. Those feel like they shouldn't be exposed on this resource.
Thoughts @captainsafia and @eerhardt ? I think we should expose some outputs on this resource as properties once we're done with all of the other changes.
I'm in support of exposing outputs that we know there are scenarios for consuming them. I wouldn't expose them all "just because" since we are still making changes in these areas and may want to refactor/change/remove some in the future.
We've exposed the container app name. The other properties are considered private right now. @FullStackChef If you want access to other properties we can make them public. Generally you only really need the name of various resources as a gateway to using "existing" in bicep. I will close this for now. Feel free to comment with what you are looking for after the latest changes.