-
Today if you do something like: var cosmos = builder.AddAzureCosmosDB("ecoDriverCosmosDb"); You'll get a unique name generated in the bicep: It makes sense that it does the safe thing by default, but is there any way to override this in Aspire (without having to drop down into the Bicep) to a fixed name (no uniqueString)? It would be quite handy for provisioning a fixed instance within the scope of a resource group. Locally it could provision or use the existing resource with that name, like a shared development database. It would be handy in multi-solution scenarios, not having to copy a connection string round, it can just go off and find that fixed resource. Also it would look prettier in Azure, and I could make things a bit more readable for me. |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 3 replies
-
cc @eerhardt |
Beta Was this translation helpful? Give feedback.
-
.NET Aspire 9 added a better way to enable this. There are 2 ways to do this:
builder.AddAzureCosmosDB("ecoDriverCosmosDb")
.ConfigureInfrastructure(infrastructure =>
{
// set the resource's Name to a fixed value
var cosmosAccount = infrastructure.GetProvisionableResources().OfType<CosmosDBAccount>().Single();
cosmosAccount.Name = "ecoDriverCosmosDb";
})
var builder = DistributedApplication.CreateBuilder(args);
builder.Services.Configure<AzureProvisioningOptions>(options =>
{
options.ProvisioningBuildOptions.InfrastructureResolvers.Insert(0, new FixedNameInfrastructureResolver());
});
builder.AddAzureCosmosDB("ecoDriverCosmosDb")
// ...
internal sealed class FixedNameInfrastructureResolver : InfrastructureResolver
{
public override void ResolveProperties(ProvisionableConstruct construct, ProvisioningBuildOptions options)
{
if (construct is CosmosDBAccount account) // whatever check you want here
{
account.Name = "ecoDriverCosmosDb";
}
base.ResolveProperties(construct, options);
}
} NOTE: if you are using 9.0.0-rc.1, the above code won't work. We changed some names in between rc1 and 9.0 GA. To do this in 9.0-rc1, use the following code: builder.AddAzureCosmosDB("ecoDriverCosmosDb")
.ConfigureConstruct(infrastructure =>
{
// set the resource's Name to a fixed value
var cosmosAccount = infrastructure.GetResources().OfType<CosmosDBAccount>().Single();
cosmosAccount.Name = "ecoDriverCosmosDb";
}) and builder.Services.Configure<AzureResourceOptions>(options =>
{
options.ProvisioningContext.PropertyResolvers.Insert(0, new FixedNameInfrastructureResolver());
});
builder.AddAzureCosmosDB("ecoDriverCosmosDb")
// ...
internal sealed class FixedNameInfrastructureResolver : PropertyResolver
{
public override void ResolveProperties(ProvisioningContext context, ProvisioningConstruct construct)
{
if (construct is CosmosDBAccount account) // whatever check you want here
{
account.Name = "ecoDriverCosmosDb";
}
}
} |
Beta Was this translation helpful? Give feedback.
-
Hi @eerhardt Just to update that this has worked really well, especially in the context of a multi-repo/solution project. I've got one central Aspire 'Infrastructure' project that is the thing that the DevOps pipeline uses to provision/deploy which references projects in other repos/solutions (each of those has an aspire AppHost for only running the parts it needs in local development). With the fixed infrastructure resolver, I don't need any of the 'IsPublishMode' switches on the Azure resources, I just have the subscription and resource group configured on the apphost and it's able to just go off and find those fixed name things (rather than creating unique things when you debug) on all the different repos. I don't need to copy connection strings between solutions, it can just go off and resolve these shared resources, it's great. I just need to be able to do similar at the container app environment level so I can control (remove) the workload profile it puts in there (and give it a nice name), I think that's pretty much the last thing that's forcing me to need infra synth and dropping down into the bicep. One query I've got though: with CosmosDb, that seems to come with a key vault when it get's provisioned which has a connection string in it as a secret value. So when I debug locally, it provisions a key vault, when it's published it provisions a different one. If I open one of the other solutions using the same CosmosDb resource and debug, that creates another one. If another dev does the same, that creates another unique one. So basically for one CosmosDB I get n * s +1 key vaults 😄 (where n is the number of solutions referencing that db, s is the number of developers running that solution, +1 for the pipeline deployment), all with the same connection string. While it doesn't seem to cause any problems with things running, I'd rather not have this excess of key vaults. Is there a way with 'AddAzureCosmosDB' to tell it to use a particular kv (so I can point it at a fixed name one I set up), or get it to create one of a fixed name in the Infrastructure Resolver? I did try in the resolver things like case KeyVaultService keyVault
when keyVault.ParentInfrastructure is AzureResourceInfrastructure parentConstruct && parentConstruct.AspireResource is AzureCosmosDBResource:
keyVault.Name = $"kv-{UniqueNamePrefix}-{parentConstruct.AspireResource.Name.ToLowerInvariant()}{environmentSuffix}";
break; But in the infra synth output it just resulted in pointing at an 'existing' key vault resource that doesn't exist. I imagine if I pointed it at one that did, it wouldn't have the connection string in anyway. Any thoughts/input on whether getting a fixed kv for cosmos is possible? |
Beta Was this translation helpful? Give feedback.
-
For anyone else reading this thread who might want similar (single fixed name provisioned resources within the scope of an azure subscription/resource-group), I thought I'd share the code I used for my infrastructure resolver. This is unique to mysolution and the conventions of how I want things set up, but you could do something more general purpose if you're not concerned about getting a specific name for your resource but still want something singular/deterministic to the subscription/resource group, i.e. read the subscription id and resource group name from config and make a short hash of those things. You can also use this for more than just the name, I'm wanting CosmosDb to always be serverless for example, and OpenAI to be in a particular deployment region where more models/features are available for example. public sealed class FixedNameInfrastructureResolver(IConfiguration configuration) : InfrastructureResolver
{
private readonly IConfiguration configuration = configuration;
private const string UniqueNamePrefix = "<your company name here perhaps>";
/// <summary>
/// Resolves the configuration for cloud resources, including a fixed name for the resource at provisioning time.
/// This does require care with the resource names as they need to be compatible with Azure naming rules for
/// the resource type.
/// </summary>
public override void ResolveProperties(ProvisionableConstruct construct, ProvisioningBuildOptions options)
{
string resourceGroup = this.configuration["Azure:ResourceGroup"] ?? throw new AppHostConfigurationException("Missing 'Azure:ResourceGroup' configuration");
// Because some resource names need to be globally unique (beyong the scope of the subscription or resource group),
// the prefix and environment based suffix is used here to get a name unique to the environment.
// If additional environments are needed, that will need to be taken into an account here.
string environmentSuffix = resourceGroup.EndsWith("dev") ? "-dev" : string.Empty;
switch (construct)
{
case CosmosDBAccount cosmosAccount:
ConfigureCosmosDb(cosmosAccount, environmentSuffix);
break;
case StorageAccount storageAccount:
storageAccount.Name = $"{UniqueNamePrefix}{storageAccount.BicepIdentifier.ToLowerInvariant()}{environmentSuffix.Replace("-", string.Empty)}";
break;
case CognitiveServicesAccount cognitiveServicesAccount:
ConfigureOpenAi(cognitiveServicesAccount, environmentSuffix);
break;
case KeyVaultService keyVault when keyVault.BicepIdentifier == "secrets":
keyVault.Name = $"kv-{UniqueNamePrefix}-{keyVault.BicepIdentifier.ToLowerInvariant()}{environmentSuffix}";
break;
case ApplicationInsightsComponent insightsComponent:
insightsComponent.Name = $"{insightsComponent.BicepIdentifier.ToLowerInvariant()}{environmentSuffix}".Replace('_', '-');
break;
default:
break;
}
}
/// <summary>
/// Configures the Cosmos DB account with a fixed name and serverless mode.
/// </summary>
private static void ConfigureCosmosDb(CosmosDBAccount account, string? envSuffix)
{
string resourceName = $"{UniqueNamePrefix}-{account.BicepIdentifier}";
if (!string.IsNullOrWhiteSpace(envSuffix))
{
resourceName = $"{resourceName}{envSuffix}";
}
account.Name = resourceName.ToLowerInvariant();
account.DatabaseAccountOfferType = CosmosDBAccountOfferType.Standard;
account.ConsistencyPolicy = new ConsistencyPolicy()
{
DefaultConsistencyLevel = DefaultConsistencyLevel.Session
};
account.Capabilities.Add(new BicepValue<CosmosDBAccountCapability>(new CosmosDBAccountCapability()
{
Name = "EnableServerless"
}));
}
/// <summary>
/// Configures the OpenAI account with a fixed name and location.
/// </summary>
private static void ConfigureOpenAi(CognitiveServicesAccount account, string? envSuffix)
{
string resourceName = $"{UniqueNamePrefix}-{account.BicepIdentifier}";
if (!string.IsNullOrWhiteSpace(envSuffix))
{
resourceName = $"{resourceName}{envSuffix}";
}
account.Name = resourceName;
// Sweden Central used for wider model/feature availability.
account.Location = new BicepValue<AzureLocation>(AzureLocation.SwedenCentral);
}
} |
Beta Was this translation helpful? Give feedback.
-
Just to add to the above if anyone is trying similar, this does cause a bit of a challenge in a build pipeline if you're also using 'azd infra synth'. The resource names are locked into the bicep files when you run azd infra synth, so if you're doing this environment/configuration specific thing to set the names with a resolver, it means you need to regenerate your bicep in your build pipeline and some how re-recreate any of your customization there. I don't have many customizations, just removing the workload profile from the container apps environment ('Consumption only'), so for me it was possible in my build pipeline to set my production configuration and run infra synth, then run a powershell script to manually modify 'resources.bicep' to remove the workload profile (thank you co-pilot, I don't know powershell). Pretty hacky, especially looking forward to being able to do this through Aspire and ditching infra synth now. Steps in my Azure devops pipeline look like: - task: AzureCLI@2
displayName: Run infra synth
inputs:
azureSubscription: '${{ parameters.azureConnection }}'
keepAzSessionActive: true
scriptType: 'pscore'
scriptLocation: inlineScript
inlineScript: |
azd infra synth --environment $($env:AZURE_ENV_NAME) --force
env:
AZURE_SUBSCRIPTION_ID: $(Azure.SubscriptionId)
AZURE_ENV_NAME: $(Azure.EnvName)
AZURE_LOCATION: $(Azure.Location)
AZD_INITIAL_ENVIRONMENT_CONFIG: $(AZD_INITIAL_ENVIRONMENT_CONFIG)
- task: PowerShell@2
displayName: Remove container apps workload profile
inputs:
filePath: '$(System.DefaultWorkingDirectory)/infra/removeWorkloadProfiles.ps1'
failOnStderr: true
showWarnings: true And the powershell script: # Define the path to the resources.bicep file with a wildcard for folders
$basePath = ""
# Get all resources.bicep files matching the pattern recursively
$files = Get-ChildItem -Path $basePath -Filter "resources.bicep" -Recurse -File
foreach ($file in $files) {
# Read the content of the file
$fileContent = Get-Content -Path $file.FullName -Raw
# Define the regex pattern to match the workloadProfiles section
$pattern = @"
workloadProfiles: \[\s*{\s*workloadProfileType: 'Consumption'\s*name: 'consumption'\s*}\s*\]\s*
"@
# Remove the workloadProfiles section
$updatedContent = $fileContent -replace $pattern, ""
# Write the updated content back to the file
Set-Content -Path $file.FullName -Value $updatedContent
Write-Output "The workloadProfiles section has been removed from the file: $($file.FullName)"
} |
Beta Was this translation helpful? Give feedback.
.NET Aspire 9 added a better way to enable this. There are 2 ways to do this: