aspire icon indicating copy to clipboard operation
aspire copied to clipboard

Expose container app constants

Open FullStackChef opened this issue 8 months ago • 9 comments

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

FullStackChef avatar Apr 23 '25 00:04 FullStackChef

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.

davidfowl avatar Apr 23 '25 01:04 davidfowl

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;
 }

FullStackChef avatar Apr 24 '25 01:04 FullStackChef

Can you explain what you’re trying to do and why?

davidfowl avatar Apr 24 '25 02:04 davidfowl

I'm decoupling my container registry from my container app environment because I want one registry but more than one environment (across multiple projects)

FullStackChef avatar Apr 24 '25 02:04 FullStackChef

I’d file an issue asking for that more directly

davidfowl avatar Apr 24 '25 02:04 davidfowl

Ok

FullStackChef avatar Apr 24 '25 02:04 FullStackChef

I believe @captainsafia has plans to support associating an existing acr with the ACA resource. Seems like it fits in well

davidfowl avatar Apr 24 '25 03:04 davidfowl

@FullStackChef TODO: rewrite issue to be more useful per suggestion

FullStackChef avatar Apr 27 '25 22:04 FullStackChef

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.

captainsafia avatar Apr 29 '25 00:04 captainsafia

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.

davidfowl avatar May 01 '25 05:05 davidfowl

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.

eerhardt avatar May 01 '25 15:05 eerhardt

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.

davidfowl avatar May 31 '25 05:05 davidfowl