diff --git a/Directory.Packages.props b/Directory.Packages.props
index 770e4268567..5a56c51ff1b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -25,7 +25,7 @@
-
+
@@ -56,7 +56,7 @@
-
+
diff --git a/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs b/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs
index b8d590e6505..23de037dc33 100644
--- a/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs
+++ b/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs
@@ -19,7 +19,7 @@
// Testing secret outputs
var cosmosDb = builder.AddAzureCosmosDB("account")
.RunAsEmulator(c => c.WithLifetime(ContainerLifetime.Persistent))
- .AddDatabase("db");
+ .WithDatabase("db");
// Testing a connection string
var blobs = builder.AddAzureStorage("storage")
diff --git a/playground/AzureContainerApps/AzureContainerApps.AppHost/account.module.bicep b/playground/AzureContainerApps/AzureContainerApps.AppHost/account.module.bicep
index bb9ff75e6bf..792c34dffdd 100644
--- a/playground/AzureContainerApps/AzureContainerApps.AppHost/account.module.bicep
+++ b/playground/AzureContainerApps/AzureContainerApps.AppHost/account.module.bicep
@@ -1,11 +1,9 @@
@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
-param keyVaultName string
+param principalType string
-resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
- name: keyVaultName
-}
+param principalId string
resource account 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
name: take('account-${uniqueString(resourceGroup().id)}', 44)
@@ -21,6 +19,7 @@ resource account 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
defaultConsistencyLevel: 'Session'
}
databaseAccountOfferType: 'Standard'
+ disableLocalAuth: true
}
kind: 'GlobalDocumentDB'
tags: {
@@ -39,10 +38,4 @@ resource db 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-08-15' = {
parent: account
}
-resource connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
- name: 'connectionString'
- properties: {
- value: 'AccountEndpoint=${account.properties.documentEndpoint};AccountKey=${account.listKeys().primaryMasterKey}'
- }
- parent: keyVault
-}
\ No newline at end of file
+output connectionString string = account.properties.documentEndpoint
\ No newline at end of file
diff --git a/playground/AzureContainerApps/AzureContainerApps.AppHost/api.module.bicep b/playground/AzureContainerApps/AzureContainerApps.AppHost/api.module.bicep
index b634633c7c2..fddffaab0d6 100644
--- a/playground/AzureContainerApps/AzureContainerApps.AppHost/api.module.bicep
+++ b/playground/AzureContainerApps/AzureContainerApps.AppHost/api.module.bicep
@@ -5,13 +5,13 @@ param api_containerport string
param storage_outputs_blobendpoint string
-param account_secretoutputs string
-
-param outputs_azure_container_registry_managed_identity_id string
+param account_outputs_connectionstring string
@secure()
param secretparam_value string
+param outputs_azure_container_registry_managed_identity_id string
+
param outputs_managed_identity_client_id string
param outputs_azure_container_apps_environment_id string
@@ -24,26 +24,12 @@ param certificateName string
param customDomain string
-resource account_secretoutputs_kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
- name: account_secretoutputs
-}
-
-resource account_secretoutputs_kv_connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' existing = {
- name: 'connectionString'
- parent: account_secretoutputs_kv
-}
-
resource api 'Microsoft.App/containerApps@2024-03-01' = {
name: 'api'
location: location
properties: {
configuration: {
secrets: [
- {
- name: 'connectionstrings--account'
- identity: outputs_azure_container_registry_managed_identity_id
- keyVaultUrl: account_secretoutputs_kv_connectionString.properties.secretUri
- }
{
name: 'value'
value: secretparam_value
@@ -106,7 +92,7 @@ resource api 'Microsoft.App/containerApps@2024-03-01' = {
}
{
name: 'ConnectionStrings__account'
- secretRef: 'connectionstrings--account'
+ value: account_outputs_connectionstring
}
{
name: 'VALUE'
diff --git a/playground/AzureContainerApps/AzureContainerApps.AppHost/aspire-manifest.json b/playground/AzureContainerApps/AzureContainerApps.AppHost/aspire-manifest.json
index 7687074415d..3a818b8f4b7 100644
--- a/playground/AzureContainerApps/AzureContainerApps.AppHost/aspire-manifest.json
+++ b/playground/AzureContainerApps/AzureContainerApps.AppHost/aspire-manifest.json
@@ -66,10 +66,11 @@
},
"account": {
"type": "azure.bicep.v0",
- "connectionString": "{account.secretOutputs.connectionString}",
+ "connectionString": "{account.outputs.connectionString}",
"path": "account.module.bicep",
"params": {
- "keyVaultName": ""
+ "principalType": "",
+ "principalId": ""
}
},
"storage": {
@@ -111,9 +112,9 @@
"params": {
"api_containerport": "{api.containerPort}",
"storage_outputs_blobendpoint": "{storage.outputs.blobEndpoint}",
- "account_secretoutputs": "{account.secretOutputs}",
- "outputs_azure_container_registry_managed_identity_id": "{.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}",
+ "account_outputs_connectionstring": "{account.outputs.connectionString}",
"secretparam_value": "{secretparam.value}",
+ "outputs_azure_container_registry_managed_identity_id": "{.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}",
"outputs_managed_identity_client_id": "{.outputs.MANAGED_IDENTITY_CLIENT_ID}",
"outputs_azure_container_apps_environment_id": "{.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_ID}",
"outputs_azure_container_registry_endpoint": "{.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT}",
diff --git a/playground/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs b/playground/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs
index a6b1bcaf748..3abca853a9b 100644
--- a/playground/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs
+++ b/playground/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs
@@ -19,8 +19,8 @@
app.MapDefaultEndpoints();
app.MapGet("/", async (CosmosClient cosmosClient) =>
{
- var db = (await cosmosClient.CreateDatabaseIfNotExistsAsync("db")).Database;
- var container = (await db.CreateContainerIfNotExistsAsync("entries", "/id")).Container;
+ var db = cosmosClient.GetDatabase("db");
+ var container = db.GetContainer("entries");
// Add an entry to the database on each request.
var newEntry = new Entry() { Id = Guid.NewGuid().ToString() };
diff --git a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs
index 5e8cc180076..c277e3d5071 100644
--- a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs
+++ b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs
@@ -5,7 +5,7 @@
#pragma warning disable ASPIRECOSMOS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
var db = builder.AddAzureCosmosDB("cosmos")
- .AddDatabase("db")
+ .WithDatabase("db", database => database.Containers.Add(new("entries", "/Id")))
.RunAsPreviewEmulator(e => e.WithDataExplorer());
#pragma warning restore ASPIRECOSMOS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
diff --git a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/aspire-manifest.json b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/aspire-manifest.json
index 3de94f0fc92..06d1c611200 100644
--- a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/aspire-manifest.json
+++ b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/aspire-manifest.json
@@ -3,10 +3,11 @@
"resources": {
"cosmos": {
"type": "azure.bicep.v0",
- "connectionString": "{cosmos.secretOutputs.connectionString}",
+ "connectionString": "{cosmos.outputs.connectionString}",
"path": "cosmos.module.bicep",
"params": {
- "keyVaultName": ""
+ "principalType": "",
+ "principalId": ""
}
},
"api": {
diff --git a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/cosmos.module.bicep b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/cosmos.module.bicep
index fac0f210b44..89c37b331aa 100644
--- a/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/cosmos.module.bicep
+++ b/playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/cosmos.module.bicep
@@ -1,11 +1,9 @@
@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
-param keyVaultName string
+param principalType string
-resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
- name: keyVaultName
-}
+param principalId string
resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
name: take('cosmos-${uniqueString(resourceGroup().id)}', 44)
@@ -21,6 +19,7 @@ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
defaultConsistencyLevel: 'Session'
}
databaseAccountOfferType: 'Standard'
+ disableLocalAuth: true
}
kind: 'GlobalDocumentDB'
tags: {
@@ -39,10 +38,20 @@ resource db 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-08-15' = {
parent: cosmos
}
-resource connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
- name: 'connectionString'
+resource entries 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-08-15' = {
+ name: 'entries'
+ location: location
properties: {
- value: 'AccountEndpoint=${cosmos.properties.documentEndpoint};AccountKey=${cosmos.listKeys().primaryMasterKey}'
+ resource: {
+ id: 'entries'
+ partitionKey: {
+ paths: [
+ '/Id'
+ ]
+ }
+ }
}
- parent: keyVault
-}
\ No newline at end of file
+ parent: db
+}
+
+output connectionString string = cosmos.properties.documentEndpoint
\ No newline at end of file
diff --git a/playground/bicep/BicepSample.AppHost/Program.cs b/playground/bicep/BicepSample.AppHost/Program.cs
index 43e2d3d2e27..b366b6cad6a 100644
--- a/playground/bicep/BicepSample.AppHost/Program.cs
+++ b/playground/bicep/BicepSample.AppHost/Program.cs
@@ -42,7 +42,7 @@
.AddDatabase("db2");
var cosmosDb = builder.AddAzureCosmosDB("cosmos")
- .AddDatabase("db3");
+ .WithDatabase("db3");
var logAnalytics = builder.AddAzureLogAnalyticsWorkspace("lawkspc");
var appInsights = builder.AddAzureApplicationInsights("ai", logAnalytics);
diff --git a/playground/bicep/BicepSample.AppHost/aspire-manifest.json b/playground/bicep/BicepSample.AppHost/aspire-manifest.json
index 1343ee3843f..89658dfba17 100644
--- a/playground/bicep/BicepSample.AppHost/aspire-manifest.json
+++ b/playground/bicep/BicepSample.AppHost/aspire-manifest.json
@@ -113,10 +113,11 @@
},
"cosmos": {
"type": "azure.bicep.v0",
- "connectionString": "{cosmos.secretOutputs.connectionString}",
+ "connectionString": "{cosmos.outputs.connectionString}",
"path": "cosmos.module.bicep",
"params": {
- "keyVaultName": ""
+ "principalType": "",
+ "principalId": ""
}
},
"lawkspc": {
diff --git a/playground/bicep/BicepSample.AppHost/cosmos.module.bicep b/playground/bicep/BicepSample.AppHost/cosmos.module.bicep
index 2b0d2b63d68..f3c25bf0a81 100644
--- a/playground/bicep/BicepSample.AppHost/cosmos.module.bicep
+++ b/playground/bicep/BicepSample.AppHost/cosmos.module.bicep
@@ -1,11 +1,9 @@
@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
-param keyVaultName string
+param principalType string
-resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
- name: keyVaultName
-}
+param principalId string
resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
name: take('cosmos-${uniqueString(resourceGroup().id)}', 44)
@@ -21,6 +19,7 @@ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
defaultConsistencyLevel: 'Session'
}
databaseAccountOfferType: 'Standard'
+ disableLocalAuth: true
}
kind: 'GlobalDocumentDB'
tags: {
@@ -39,10 +38,4 @@ resource db3 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-08-15' = {
parent: cosmos
}
-resource connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
- name: 'connectionString'
- properties: {
- value: 'AccountEndpoint=${cosmos.properties.documentEndpoint};AccountKey=${cosmos.listKeys().primaryMasterKey}'
- }
- parent: keyVault
-}
\ No newline at end of file
+output connectionString string = cosmos.properties.documentEndpoint
\ No newline at end of file
diff --git a/playground/cdk/CdkSample.AppHost/Program.cs b/playground/cdk/CdkSample.AppHost/Program.cs
index 062f101322a..a04718791d3 100644
--- a/playground/cdk/CdkSample.AppHost/Program.cs
+++ b/playground/cdk/CdkSample.AppHost/Program.cs
@@ -9,7 +9,7 @@
var builder = DistributedApplication.CreateBuilder(args);
-var cosmosdb = builder.AddAzureCosmosDB("cosmos").AddDatabase("cosmosdb");
+var cosmosdb = builder.AddAzureCosmosDB("cosmos").WithDatabase("cosmosdb");
var sku = builder.AddParameter("storagesku");
var locationOverride = builder.AddParameter("locationOverride");
diff --git a/playground/cdk/CdkSample.AppHost/aspire-manifest.json b/playground/cdk/CdkSample.AppHost/aspire-manifest.json
index 8429876c9de..156d8eb6549 100644
--- a/playground/cdk/CdkSample.AppHost/aspire-manifest.json
+++ b/playground/cdk/CdkSample.AppHost/aspire-manifest.json
@@ -3,10 +3,11 @@
"resources": {
"cosmos": {
"type": "azure.bicep.v0",
- "connectionString": "{cosmos.secretOutputs.connectionString}",
+ "connectionString": "{cosmos.outputs.connectionString}",
"path": "cosmos.module.bicep",
"params": {
- "keyVaultName": ""
+ "principalType": "",
+ "principalId": ""
}
},
"storagesku": {
diff --git a/playground/cdk/CdkSample.AppHost/cosmos.module.bicep b/playground/cdk/CdkSample.AppHost/cosmos.module.bicep
index e491d282571..734d6e041b0 100644
--- a/playground/cdk/CdkSample.AppHost/cosmos.module.bicep
+++ b/playground/cdk/CdkSample.AppHost/cosmos.module.bicep
@@ -1,11 +1,9 @@
@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
-param keyVaultName string
+param principalType string
-resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
- name: keyVaultName
-}
+param principalId string
resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
name: take('cosmos-${uniqueString(resourceGroup().id)}', 44)
@@ -21,6 +19,7 @@ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
defaultConsistencyLevel: 'Session'
}
databaseAccountOfferType: 'Standard'
+ disableLocalAuth: true
}
kind: 'GlobalDocumentDB'
tags: {
@@ -39,10 +38,4 @@ resource cosmosdb 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-08-15
parent: cosmos
}
-resource connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
- name: 'connectionString'
- properties: {
- value: 'AccountEndpoint=${cosmos.properties.documentEndpoint};AccountKey=${cosmos.listKeys().primaryMasterKey}'
- }
- parent: keyVault
-}
\ No newline at end of file
+output connectionString string = cosmos.properties.documentEndpoint
\ No newline at end of file
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj b/src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj
index 68eab3d8ea5..35d4b232c21 100644
--- a/src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj
+++ b/src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj
@@ -20,7 +20,8 @@
-
+
+
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBEmulatorConnectionString.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBEmulatorConnectionString.cs
index f8cd05afe85..b8e02e2ab15 100644
--- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBEmulatorConnectionString.cs
+++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBEmulatorConnectionString.cs
@@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Hosting.ApplicationModel;
-using Aspire.Hosting.Azure.Cosmos;
+using Aspire.Hosting.Azure.CosmosDB;
namespace Aspire.Hosting.Azure;
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBEmulatorHealthCheck.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBEmulatorHealthCheck.cs
new file mode 100644
index 00000000000..19665026f89
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBEmulatorHealthCheck.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Azure.Cosmos;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace Aspire.Hosting.Azure.CosmosDB;
+
+///
+/// This health check also creates default databases and containers for the Azure CosmosDB Emulator.
+///
+internal sealed class AzureCosmosDBEmulatorHealthCheck : IHealthCheck
+{
+ private readonly Func _clientFactory;
+ private readonly Func _databasesFactory;
+ private bool _resourcesCreated;
+
+ public AzureCosmosDBEmulatorHealthCheck(Func clientFactory, Func databasesFactory)
+ {
+ ArgumentNullException.ThrowIfNull(clientFactory);
+ ArgumentNullException.ThrowIfNull(databasesFactory);
+
+ _clientFactory = clientFactory;
+ _databasesFactory = databasesFactory;
+ }
+
+ ///
+ public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ var cosmosClient = _clientFactory();
+
+ await cosmosClient.ReadAccountAsync().WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ // Create the databases and containers if they do not exist. This is only performed once.
+ if (!_resourcesCreated)
+ {
+ var databases = _databasesFactory();
+
+ foreach (var database in databases)
+ {
+ var db = (await cosmosClient.CreateDatabaseIfNotExistsAsync(database.Name, cancellationToken: cancellationToken).ConfigureAwait(false)).Database;
+
+ foreach (var container in database.Containers)
+ {
+ await db.CreateContainerIfNotExistsAsync(container.Name, container.PartitionKeyPath, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ _resourcesCreated = true;
+ }
+
+ return HealthCheckResult.Healthy();
+ }
+ catch (Exception ex)
+ {
+ return new HealthCheckResult(context.Registration.FailureStatus, exception: ex);
+ }
+ }
+}
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs
index 613651079b9..899f42b73b9 100644
--- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs
+++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs
@@ -5,7 +5,7 @@
using System.Globalization;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
-using Aspire.Hosting.Azure.Cosmos;
+using Aspire.Hosting.Azure.CosmosDB;
using Aspire.Hosting.Utils;
using Azure.Identity;
using Azure.Provisioning;
@@ -14,6 +14,7 @@
using Azure.Provisioning.KeyVault;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace Aspire.Hosting;
@@ -32,66 +33,7 @@ public static IResourceBuilder AddAzureCosmosDB(this IDis
{
builder.AddAzureProvisioning();
- var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
- {
- var kvNameParam = new ProvisioningParameter(AzureBicepResource.KnownParameters.KeyVaultName, typeof(string));
- infrastructure.Add(kvNameParam);
-
- var keyVault = KeyVaultService.FromExisting("keyVault");
- keyVault.Name = kvNameParam;
- infrastructure.Add(keyVault);
-
- var cosmosAccount = new CosmosDBAccount(infrastructure.AspireResource.GetBicepIdentifier())
- {
- Kind = CosmosDBAccountKind.GlobalDocumentDB,
- ConsistencyPolicy = new ConsistencyPolicy()
- {
- DefaultConsistencyLevel = DefaultConsistencyLevel.Session
- },
- DatabaseAccountOfferType = CosmosDBAccountOfferType.Standard,
- Locations =
- {
- new CosmosDBAccountLocation
- {
- LocationName = new IdentifierExpression("location"),
- FailoverPriority = 0
- }
- },
- Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
- };
- infrastructure.Add(cosmosAccount);
-
- var azureResource = (AzureCosmosDBResource)infrastructure.AspireResource;
- var azureResourceBuilder = builder.CreateResourceBuilder(azureResource);
- List cosmosSqlDatabases = new List();
- foreach (var databaseName in azureResource.Databases)
- {
- var cosmosSqlDatabase = new CosmosDBSqlDatabase(Infrastructure.NormalizeBicepIdentifier(databaseName))
- {
- Parent = cosmosAccount,
- Name = databaseName,
- Resource = new CosmosDBSqlDatabaseResourceInfo()
- {
- DatabaseName = databaseName
- }
- };
- infrastructure.Add(cosmosSqlDatabase);
- cosmosSqlDatabases.Add(cosmosSqlDatabase);
- }
-
- var secret = new KeyVaultSecret("connectionString")
- {
- Parent = keyVault,
- Name = "connectionString",
- Properties = new SecretProperties
- {
- Value = BicepFunction.Interpolate($"AccountEndpoint={cosmosAccount.DocumentEndpoint};AccountKey={cosmosAccount.GetKeys().PrimaryMasterKey}")
- }
- };
- infrastructure.Add(secret);
- };
-
- var resource = new AzureCosmosDBResource(name, configureInfrastructure);
+ var resource = new AzureCosmosDBResource(name, ConfigureCosmosDBInfrastructure);
return builder.AddResource(resource)
.WithManifestPublishingCallback(resource.WriteToManifest);
}
@@ -155,11 +97,18 @@ private static IResourceBuilder RunAsEmulator(this IResou
cosmosClient = CreateCosmosClient(connectionString);
});
+ // Use custom health check that also seeds the databases and containers
var healthCheckKey = $"{builder.Resource.Name}_check";
- builder.ApplicationBuilder.Services.AddHealthChecks().AddAzureCosmosDB(sp =>
- {
- return cosmosClient ?? throw new InvalidOperationException("CosmosClient is not initialized.");
- }, name: healthCheckKey);
+ builder.ApplicationBuilder.Services.AddHealthChecks().Add(
+ new HealthCheckRegistration(
+ name: healthCheckKey,
+ new AzureCosmosDBEmulatorHealthCheck(
+ () => cosmosClient ?? throw new InvalidOperationException("CosmosClient is not initialized."),
+ builder.Resource.Databases.ToArray
+ ),
+ failureStatus: null,
+ tags: null)
+ );
builder.WithHealthCheck(healthCheckKey);
@@ -228,7 +177,7 @@ public static IResourceBuilder WithGatewayPort(th
/// Builder for the Cosmos emulator container
/// Desired partition count.
/// Cosmos emulator resource builder.
- /// Not calling this method will result in the default of 25 partitions. The actual started partitions is always one more than specified.
+ /// Not calling this method will result in the default of 10 partitions. The actual started partitions is always one more than specified.
/// See this documentation about setting the partition count.
///
public static IResourceBuilder WithPartitionCount(this IResourceBuilder builder, int count)
@@ -252,9 +201,30 @@ public static IResourceBuilder WithPartitionCount
/// AzureCosmosDB resource builder.
/// Name of database.
/// A reference to the .
+ [Obsolete($"This method is obsolete and will be removed in a future version. Use {nameof(WithDatabase)} instead to add a Cosmos DB database.")]
public static IResourceBuilder AddDatabase(this IResourceBuilder builder, string databaseName)
{
- builder.Resource.Databases.Add(databaseName);
+ return builder.WithDatabase(databaseName);
+ }
+
+ ///
+ /// Adds a database to the associated Cosmos DB account resource.
+ ///
+ /// AzureCosmosDB resource builder.
+ /// Name of database.
+ /// An optional method that can be used for customizing the .
+ /// A reference to the .
+ public static IResourceBuilder WithDatabase(this IResourceBuilder builder, string name, Action? configure = null)
+ {
+ var database = builder.Resource.Databases.FirstOrDefault(x => x.Name == name);
+
+ if (database == null)
+ {
+ database = new CosmosDBDatabase(name);
+ builder.Resource.Databases.Add(database);
+ }
+
+ configure?.Invoke(database);
return builder;
}
@@ -282,4 +252,128 @@ public static IResourceBuilder WithDataExplorer(t
endpoint.Port = port;
});
}
+
+ ///
+ /// Configures the resource to use access key authentication with Azure Cosmos DB.
+ ///
+ /// The Azure Cosmos DB resource builder.
+ /// A reference to the builder.
+ ///
+ /// The following example creates an Azure Cosmos DB resource that uses access key authentication.
+ ///
+ /// var builder = DistributedApplication.CreateBuilder(args);
+ ///
+ /// var cosmosdb = builder.AddAzureCosmosDB("cache")
+ /// .WithAccessKeyAuthentication();
+ ///
+ /// builder.AddProject<Projects.ProductService>()
+ /// .WithReference(cosmosdb);
+ ///
+ /// builder.Build().Run();
+ ///
+ ///
+ public static IResourceBuilder WithAccessKeyAuthentication(
+ this IResourceBuilder builder)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ var azureResource = builder.Resource;
+ azureResource.ConnectionStringSecretOutput = new BicepSecretOutputReference("connectionString", azureResource);
+
+ return builder;
+ }
+
+ private static void ConfigureCosmosDBInfrastructure(AzureResourceInfrastructure infrastructure)
+ {
+ var azureResource = (AzureCosmosDBResource)infrastructure.AspireResource;
+
+ var cosmosAccount = new CosmosDBAccount(infrastructure.AspireResource.GetBicepIdentifier())
+ {
+ Kind = CosmosDBAccountKind.GlobalDocumentDB,
+ ConsistencyPolicy = new ConsistencyPolicy()
+ {
+ DefaultConsistencyLevel = DefaultConsistencyLevel.Session
+ },
+ DatabaseAccountOfferType = CosmosDBAccountOfferType.Standard,
+ Locations =
+ {
+ new CosmosDBAccountLocation
+ {
+ LocationName = new IdentifierExpression("location"),
+ FailoverPriority = 0
+ }
+ },
+ Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
+ };
+ infrastructure.Add(cosmosAccount);
+
+ foreach (var database in azureResource.Databases)
+ {
+ var cosmosSqlDatabase = new CosmosDBSqlDatabase(Infrastructure.NormalizeBicepIdentifier(database.Name))
+ {
+ Parent = cosmosAccount,
+ Name = database.Name,
+ Resource = new CosmosDBSqlDatabaseResourceInfo()
+ {
+ DatabaseName = database.Name
+ }
+ };
+ infrastructure.Add(cosmosSqlDatabase);
+
+ foreach (var container in database.Containers)
+ {
+ var cosmosContainer = new CosmosDBSqlContainer(Infrastructure.NormalizeBicepIdentifier(container.Name))
+ {
+ Parent = cosmosSqlDatabase,
+ Name = container.Name,
+ Resource = new CosmosDBSqlContainerResourceInfo()
+ {
+ ContainerName = container.Name,
+ PartitionKey = new CosmosDBContainerPartitionKey { Paths = [container.PartitionKeyPath] }
+ }
+ };
+ infrastructure.Add(cosmosContainer);
+ }
+ }
+
+ if (azureResource.UseAccessKeyAuthentication)
+ {
+ cosmosAccount.DisableLocalAuth = false;
+
+ var kvNameParam = new ProvisioningParameter(AzureBicepResource.KnownParameters.KeyVaultName, typeof(string));
+ infrastructure.Add(kvNameParam);
+
+ var keyVault = KeyVaultService.FromExisting("keyVault");
+ keyVault.Name = kvNameParam;
+ infrastructure.Add(keyVault);
+
+ var secret = new KeyVaultSecret("connectionString")
+ {
+ Parent = keyVault,
+ Name = "connectionString",
+ Properties = new SecretProperties
+ {
+ Value = BicepFunction.Interpolate($"AccountEndpoint={cosmosAccount.DocumentEndpoint};AccountKey={cosmosAccount.GetKeys().PrimaryMasterKey}")
+ }
+ };
+ infrastructure.Add(secret);
+ }
+ else
+ {
+ cosmosAccount.DisableLocalAuth = true;
+
+ var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string));
+ infrastructure.Add(principalTypeParameter);
+
+ var principalIdParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string));
+ infrastructure.Add(principalIdParameter);
+
+ cosmosAccount.CreateRoleAssignment(CosmosDBBuiltInRole.DocumentDBAccountContributor, principalTypeParameter, principalIdParameter);
+
+ infrastructure.Add(new ProvisioningOutput("connectionString", typeof(string))
+ {
+ Value = cosmosAccount.DocumentEndpoint
+ });
+ }
+ }
}
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs
index 94b1691a0e3..b1ec26a7a3c 100644
--- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs
+++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs
@@ -1,9 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
-using Aspire.Hosting.Azure.Cosmos;
+using Aspire.Hosting.Azure.CosmosDB;
namespace Aspire.Hosting;
@@ -15,7 +16,7 @@ public class AzureCosmosDBResource(string name, Action Databases { get; } = [];
+ internal List Databases { get; } = [];
internal EndpointReference EmulatorEndpoint => new(this, "emulator");
@@ -24,6 +25,23 @@ public class AzureCosmosDBResource(string name, Action
public BicepSecretOutputReference ConnectionString => new("connectionString", this);
+ ///
+ /// Gets the "connectionString" output reference from the bicep template for the Azure Cosmos DB resource.
+ ///
+ /// This is used when Entra ID authentication is used. The connection string is an output of the bicep template.
+ ///
+ public BicepOutputReference ConnectionStringOutput => new("connectionString", this);
+
+ ///
+ /// Gets the "connectionString" secret output reference from the bicep template for the Azure Redis resource.
+ ///
+ /// This is set when access key authentication is used. The connection string is stored in a secret in the Azure Key Vault.
+ ///
+ internal BicepSecretOutputReference? ConnectionStringSecretOutput { get; set; }
+
+ [MemberNotNullWhen(true, nameof(ConnectionStringSecretOutput))]
+ internal bool UseAccessKeyAuthentication => ConnectionStringSecretOutput is not null;
+
///
/// Gets a value indicating whether the Azure Cosmos DB resource is running in the local emulator.
///
@@ -39,6 +57,8 @@ public class AzureCosmosDBResource(string name, Action
IsEmulator
? AzureCosmosDBEmulatorConnectionString.Create(EmulatorEndpoint, IsPreviewEmulator)
- : ReferenceExpression.Create($"{ConnectionString}");
-}
+ : UseAccessKeyAuthentication ?
+ ReferenceExpression.Create($"{ConnectionStringSecretOutput}") :
+ ReferenceExpression.Create($"{ConnectionStringOutput}");
+}
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBContainer.cs b/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBContainer.cs
new file mode 100644
index 00000000000..0bb49bd1b62
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBContainer.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Hosting.Azure.CosmosDB;
+
+///
+/// Represents an Azure Cosmos DB Database Container.
+///
+///
+/// Use to configure specific properties.
+///
+public class CosmosDBContainer
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CosmosDBContainer(string name, string partitionKeyPath)
+ {
+ Name = name;
+ PartitionKeyPath = partitionKeyPath;
+ }
+
+ ///
+ /// The container name.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The partition key path.
+ ///
+ public string PartitionKeyPath { get; set; }
+}
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBDatabase.cs b/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBDatabase.cs
new file mode 100644
index 00000000000..ce0aefe7b9a
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBDatabase.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Hosting.Azure.CosmosDB;
+
+///
+/// Represents an Azure Cosmos DB Database.
+///
+///
+/// Use to configure specific properties.
+///
+public class CosmosDBDatabase
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CosmosDBDatabase(string name)
+ {
+ Name = name;
+ }
+
+ ///
+ /// The database name.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The containers for this database.
+ ///
+ public List Containers { get; } = [];
+}
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBEmulatorContainerImageTags.cs b/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBEmulatorContainerImageTags.cs
index 7ca91640b66..5644cbf278b 100644
--- a/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBEmulatorContainerImageTags.cs
+++ b/src/Aspire.Hosting.Azure.CosmosDB/CosmosDBEmulatorContainerImageTags.cs
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Aspire.Hosting.Azure.Cosmos;
+namespace Aspire.Hosting.Azure.CosmosDB;
internal static class CosmosDBEmulatorContainerImageTags
{
diff --git a/src/Aspire.Hosting.Azure.CosmosDB/PublicAPI.Unshipped.txt b/src/Aspire.Hosting.Azure.CosmosDB/PublicAPI.Unshipped.txt
index a830d73815d..503185ecaf3 100644
--- a/src/Aspire.Hosting.Azure.CosmosDB/PublicAPI.Unshipped.txt
+++ b/src/Aspire.Hosting.Azure.CosmosDB/PublicAPI.Unshipped.txt
@@ -1,8 +1,22 @@
#nullable enable
*REMOVED*static Aspire.Hosting.AzureCosmosExtensions.AddAzureCosmosDB(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, System.Action!, Aspire.Hosting.ResourceModuleConstruct!, Azure.Provisioning.CosmosDB.CosmosDBAccount!, System.Collections.Generic.IEnumerable!>? configureResource) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
*REMOVED*Aspire.Hosting.AzureCosmosDBResource.AzureCosmosDBResource(string! name, System.Action! configureConstruct) -> void
+Aspire.Hosting.Azure.CosmosDB.CosmosDBContainer
+Aspire.Hosting.Azure.CosmosDB.CosmosDBContainer.CosmosDBContainer(string! name, string! partitionKeyPath) -> void
+Aspire.Hosting.Azure.CosmosDB.CosmosDBContainer.Name.get -> string!
+Aspire.Hosting.Azure.CosmosDB.CosmosDBContainer.Name.set -> void
+Aspire.Hosting.Azure.CosmosDB.CosmosDBContainer.PartitionKeyPath.get -> string!
+Aspire.Hosting.Azure.CosmosDB.CosmosDBContainer.PartitionKeyPath.set -> void
+Aspire.Hosting.Azure.CosmosDB.CosmosDBDatabase
+Aspire.Hosting.Azure.CosmosDB.CosmosDBDatabase.Containers.get -> System.Collections.Generic.List!
+Aspire.Hosting.Azure.CosmosDB.CosmosDBDatabase.CosmosDBDatabase(string! name) -> void
+Aspire.Hosting.Azure.CosmosDB.CosmosDBDatabase.Name.get -> string!
+Aspire.Hosting.Azure.CosmosDB.CosmosDBDatabase.Name.set -> void
Aspire.Hosting.AzureCosmosDBResource.AzureCosmosDBResource(string! name, System.Action! configureInfrastructure) -> void
+Aspire.Hosting.AzureCosmosDBResource.ConnectionStringOutput.get -> Aspire.Hosting.Azure.BicepOutputReference!
static Aspire.Hosting.AzureCosmosExtensions.RunAsPreviewEmulator(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.Action!>? configureContainer = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
static Aspire.Hosting.AzureCosmosExtensions.WithDataExplorer(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.AzureCosmosExtensions.WithAccessKeyAuthentication(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.AzureCosmosExtensions.WithDatabase(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, System.Action? configure = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
static Aspire.Hosting.AzureCosmosExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? name = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
static Aspire.Hosting.AzureCosmosExtensions.WithPartitionCount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, int count) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
diff --git a/src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj b/src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj
index 74fcb5bf550..d06aca9e711 100644
--- a/src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj
+++ b/src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/Components/Aspire.Microsoft.Azure.Cosmos/AspireMicrosoftAzureCosmosExtensions.cs b/src/Components/Aspire.Microsoft.Azure.Cosmos/AspireMicrosoftAzureCosmosExtensions.cs
index 6339b8bf382..d34ca6bf4e0 100644
--- a/src/Components/Aspire.Microsoft.Azure.Cosmos/AspireMicrosoftAzureCosmosExtensions.cs
+++ b/src/Components/Aspire.Microsoft.Azure.Cosmos/AspireMicrosoftAzureCosmosExtensions.cs
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Aspire.Hosting.Azure.Cosmos;
+using Aspire.Hosting.Azure.CosmosDB;
using Aspire.Microsoft.Azure.Cosmos;
using Azure.Identity;
using Microsoft.Azure.Cosmos;
diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj
index bc103e16bc8..acbc4dc1e26 100644
--- a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj
+++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj
@@ -23,6 +23,7 @@
+
diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/AspireAzureEFCoreCosmosExtensions.cs b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/AspireAzureEFCoreCosmosExtensions.cs
index a46b6620c86..9f170f8748f 100644
--- a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/AspireAzureEFCoreCosmosExtensions.cs
+++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/AspireAzureEFCoreCosmosExtensions.cs
@@ -3,7 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using Aspire;
-using Aspire.Hosting.Azure.Cosmos;
+using Aspire.Hosting.Azure.CosmosDB;
using Aspire.Microsoft.EntityFrameworkCore.Cosmos;
using Azure.Identity;
using Microsoft.Azure.Cosmos;
diff --git a/src/Shared/Cosmos/CosmosConstants.cs b/src/Shared/Cosmos/CosmosConstants.cs
index b5f85949874..b9c00c14ed9 100644
--- a/src/Shared/Cosmos/CosmosConstants.cs
+++ b/src/Shared/Cosmos/CosmosConstants.cs
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Aspire.Hosting.Azure.Cosmos;
+namespace Aspire.Hosting.Azure.CosmosDB;
internal static class CosmosConstants
{
diff --git a/src/Shared/Cosmos/CosmosUtils.cs b/src/Shared/Cosmos/CosmosUtils.cs
index 0e603fe88f0..5c571bdd756 100644
--- a/src/Shared/Cosmos/CosmosUtils.cs
+++ b/src/Shared/Cosmos/CosmosUtils.cs
@@ -3,7 +3,7 @@
using System.Data.Common;
-namespace Aspire.Hosting.Azure.Cosmos;
+namespace Aspire.Hosting.Azure.CosmosDB;
internal static class CosmosUtils
{
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs
index 96dcf1d0397..d0639c0746d 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs
@@ -238,7 +238,7 @@ public async Task AddAzureCosmosDBEmulator()
}
[Fact]
- public async Task AddAzureCosmosDBViaRunMode()
+ public async Task AddAzureCosmosDBViaRunMode_WithAccessKeyAuthentication()
{
using var builder = TestDistributedApplicationBuilder.Create();
@@ -247,8 +247,8 @@ public async Task AddAzureCosmosDBViaRunMode()
.ConfigureInfrastructure(infrastructure =>
{
callbackDatabases = infrastructure.GetProvisionableResources().OfType();
- });
- cosmos.AddDatabase("mydatabase");
+ }).WithAccessKeyAuthentication();
+ cosmos.WithDatabase("mydatabase", db => db.Containers.Add(new("mycontainer", "mypartitionkeypath")));
cosmos.Resource.SecretOutputs["connectionString"] = "mycosmosconnectionstring";
@@ -272,10 +272,6 @@ public async Task AddAzureCosmosDBViaRunMode()
param keyVaultName string
- resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
- name: keyVaultName
- }
-
resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
name: take('cosmos-${uniqueString(resourceGroup().id)}', 44)
location: location
@@ -290,6 +286,7 @@ param keyVaultName string
defaultConsistencyLevel: 'Session'
}
databaseAccountOfferType: 'Standard'
+ disableLocalAuth: false
}
kind: 'GlobalDocumentDB'
tags: {
@@ -308,6 +305,26 @@ param keyVaultName string
parent: cosmos
}
+ resource mycontainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-08-15' = {
+ name: 'mycontainer'
+ location: location
+ properties: {
+ resource: {
+ id: 'mycontainer'
+ partitionKey: {
+ paths: [
+ 'mypartitionkeypath'
+ ]
+ }
+ }
+ }
+ parent: mydatabase
+ }
+
+ resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
+ name: keyVaultName
+ }
+
resource connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
name: 'connectionString'
properties: {
@@ -332,9 +349,9 @@ param keyVaultName string
}
[Fact]
- public async Task AddAzureCosmosDBViaPublishMode()
+ public async Task AddAzureCosmosDBViaRunMode_NoAccessKeyAuthentication()
{
- using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+ using var builder = TestDistributedApplicationBuilder.Create();
IEnumerable? callbackDatabases = null;
var cosmos = builder.AddAzureCosmosDB("cosmos")
@@ -342,7 +359,113 @@ public async Task AddAzureCosmosDBViaPublishMode()
{
callbackDatabases = infrastructure.GetProvisionableResources().OfType();
});
- cosmos.AddDatabase("mydatabase");
+ cosmos.WithDatabase("mydatabase", db => db.Containers.Add(new("mycontainer", "mypartitionkeypath")));
+
+ cosmos.Resource.Outputs["connectionString"] = "mycosmosconnectionstring";
+
+ var manifest = await ManifestUtils.GetManifestWithBicep(cosmos.Resource);
+
+ var expectedManifest = """
+ {
+ "type": "azure.bicep.v0",
+ "connectionString": "{cosmos.outputs.connectionString}",
+ "path": "cosmos.module.bicep",
+ "params": {
+ "principalType": "",
+ "principalId": ""
+ }
+ }
+ """;
+
+ output.WriteLine(manifest.ManifestNode.ToString());
+ Assert.Equal(expectedManifest, manifest.ManifestNode.ToString());
+
+ var expectedBicep = """
+ @description('The location for the resource(s) to be deployed.')
+ param location string = resourceGroup().location
+
+ param principalType string
+
+ param principalId string
+
+ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
+ name: take('cosmos-${uniqueString(resourceGroup().id)}', 44)
+ location: location
+ properties: {
+ locations: [
+ {
+ locationName: location
+ failoverPriority: 0
+ }
+ ]
+ consistencyPolicy: {
+ defaultConsistencyLevel: 'Session'
+ }
+ databaseAccountOfferType: 'Standard'
+ disableLocalAuth: true
+ }
+ kind: 'GlobalDocumentDB'
+ tags: {
+ 'aspire-resource-name': 'cosmos'
+ }
+ }
+
+ resource mydatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-08-15' = {
+ name: 'mydatabase'
+ location: location
+ properties: {
+ resource: {
+ id: 'mydatabase'
+ }
+ }
+ parent: cosmos
+ }
+
+ resource mycontainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-08-15' = {
+ name: 'mycontainer'
+ location: location
+ properties: {
+ resource: {
+ id: 'mycontainer'
+ partitionKey: {
+ paths: [
+ 'mypartitionkeypath'
+ ]
+ }
+ }
+ }
+ parent: mydatabase
+ }
+
+ output connectionString string = cosmos.properties.documentEndpoint
+ """;
+ output.WriteLine(manifest.BicepText);
+ Assert.Equal(expectedBicep, manifest.BicepText);
+
+ Assert.NotNull(callbackDatabases);
+ Assert.Collection(
+ callbackDatabases,
+ (database) => Assert.Equal("mydatabase", database.Name.Value)
+ );
+
+ var connectionStringResource = (IResourceWithConnectionString)cosmos.Resource;
+
+ Assert.Equal("cosmos", cosmos.Resource.Name);
+ Assert.Equal("mycosmosconnectionstring", await connectionStringResource.GetConnectionStringAsync());
+ }
+
+ [Fact]
+ public async Task AddAzureCosmosDBViaPublishMode_WithAccessKeyAuthentication()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ IEnumerable? callbackDatabases = null;
+ var cosmos = builder.AddAzureCosmosDB("cosmos")
+ .ConfigureInfrastructure(infrastructure =>
+ {
+ callbackDatabases = infrastructure.GetProvisionableResources().OfType();
+ }).WithAccessKeyAuthentication();
+ cosmos.WithDatabase("mydatabase", db => db.Containers.Add(new("mycontainer", "mypartitionkeypath")));
cosmos.Resource.SecretOutputs["connectionString"] = "mycosmosconnectionstring";
@@ -366,10 +489,6 @@ public async Task AddAzureCosmosDBViaPublishMode()
param keyVaultName string
- resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
- name: keyVaultName
- }
-
resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
name: take('cosmos-${uniqueString(resourceGroup().id)}', 44)
location: location
@@ -384,6 +503,7 @@ param keyVaultName string
defaultConsistencyLevel: 'Session'
}
databaseAccountOfferType: 'Standard'
+ disableLocalAuth: false
}
kind: 'GlobalDocumentDB'
tags: {
@@ -402,6 +522,26 @@ param keyVaultName string
parent: cosmos
}
+ resource mycontainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-08-15' = {
+ name: 'mycontainer'
+ location: location
+ properties: {
+ resource: {
+ id: 'mycontainer'
+ partitionKey: {
+ paths: [
+ 'mypartitionkeypath'
+ ]
+ }
+ }
+ }
+ parent: mydatabase
+ }
+
+ resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
+ name: keyVaultName
+ }
+
resource connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
name: 'connectionString'
properties: {
@@ -425,6 +565,110 @@ param keyVaultName string
Assert.Equal("mycosmosconnectionstring", await connectionStringResource.GetConnectionStringAsync());
}
+ [Fact]
+ public async Task AddAzureCosmosDBViaPublishMode_NoAccessKeyAuthentication()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ IEnumerable? callbackDatabases = null;
+ var cosmos = builder.AddAzureCosmosDB("cosmos")
+ .ConfigureInfrastructure(infrastructure =>
+ {
+ callbackDatabases = infrastructure.GetProvisionableResources().OfType();
+ });
+ cosmos.WithDatabase("mydatabase", db => db.Containers.Add(new("mycontainer", "mypartitionkeypath")));
+
+ cosmos.Resource.Outputs["connectionString"] = "mycosmosconnectionstring";
+
+ var manifest = await ManifestUtils.GetManifestWithBicep(cosmos.Resource);
+
+ var expectedManifest = """
+ {
+ "type": "azure.bicep.v0",
+ "connectionString": "{cosmos.outputs.connectionString}",
+ "path": "cosmos.module.bicep",
+ "params": {
+ "principalType": "",
+ "principalId": ""
+ }
+ }
+ """;
+ Assert.Equal(expectedManifest, manifest.ManifestNode.ToString());
+
+ var expectedBicep = """
+ @description('The location for the resource(s) to be deployed.')
+ param location string = resourceGroup().location
+
+ param principalType string
+
+ param principalId string
+
+ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = {
+ name: take('cosmos-${uniqueString(resourceGroup().id)}', 44)
+ location: location
+ properties: {
+ locations: [
+ {
+ locationName: location
+ failoverPriority: 0
+ }
+ ]
+ consistencyPolicy: {
+ defaultConsistencyLevel: 'Session'
+ }
+ databaseAccountOfferType: 'Standard'
+ disableLocalAuth: true
+ }
+ kind: 'GlobalDocumentDB'
+ tags: {
+ 'aspire-resource-name': 'cosmos'
+ }
+ }
+
+ resource mydatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-08-15' = {
+ name: 'mydatabase'
+ location: location
+ properties: {
+ resource: {
+ id: 'mydatabase'
+ }
+ }
+ parent: cosmos
+ }
+
+ resource mycontainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-08-15' = {
+ name: 'mycontainer'
+ location: location
+ properties: {
+ resource: {
+ id: 'mycontainer'
+ partitionKey: {
+ paths: [
+ 'mypartitionkeypath'
+ ]
+ }
+ }
+ }
+ parent: mydatabase
+ }
+
+ output connectionString string = cosmos.properties.documentEndpoint
+ """;
+ output.WriteLine(manifest.BicepText);
+ Assert.Equal(expectedBicep, manifest.BicepText);
+
+ Assert.NotNull(callbackDatabases);
+ Assert.Collection(
+ callbackDatabases,
+ (database) => Assert.Equal("mydatabase", database.Name.Value)
+ );
+
+ var connectionStringResource = (IResourceWithConnectionString)cosmos.Resource;
+
+ Assert.Equal("cosmos", cosmos.Resource.Name);
+ Assert.Equal("mycosmosconnectionstring", await connectionStringResource.GetConnectionStringAsync());
+ }
+
[Fact]
public async Task AddAzureAppConfiguration()
{
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs
index 62d651204b0..86b877fa746 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs
@@ -471,7 +471,7 @@ public async Task ProjectWithManyReferenceTypes()
builder.AddAzureContainerAppsInfrastructure();
// CosmosDB uses secret outputs
- var db = builder.AddAzureCosmosDB("mydb").AddDatabase("db");
+ var db = builder.AddAzureCosmosDB("mydb").WithDatabase("db");
// Postgres uses secret outputs + a literal connection string
var pgdb = builder.AddAzurePostgresFlexibleServer("pg").WithPasswordAuthentication().AddDatabase("db");
@@ -536,10 +536,10 @@ public async Task ProjectWithManyReferenceTypes()
"path": "api.module.bicep",
"params": {
"api_containerport": "{api.containerPort}",
- "mydb_secretoutputs": "{mydb.secretOutputs}",
- "outputs_azure_container_registry_managed_identity_id": "{.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}",
+ "mydb_outputs_connectionstring": "{mydb.outputs.connectionString}",
"storage_outputs_blobendpoint": "{storage.outputs.blobEndpoint}",
"pg_secretoutputs": "{pg.secretOutputs}",
+ "outputs_azure_container_registry_managed_identity_id": "{.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}",
"value0_value": "{value0.value}",
"value1_value": "{value1.value}",
"outputs_azure_container_apps_environment_default_domain": "{.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN}",
@@ -560,14 +560,14 @@ public async Task ProjectWithManyReferenceTypes()
param api_containerport string
- param mydb_secretoutputs string
-
- param outputs_azure_container_registry_managed_identity_id string
+ param mydb_outputs_connectionstring string
param storage_outputs_blobendpoint string
param pg_secretoutputs string
+ param outputs_azure_container_registry_managed_identity_id string
+
@secure()
param value0_value string
@@ -583,19 +583,10 @@ param outputs_azure_container_registry_endpoint string
param api_containerimage string
- resource mydb_secretoutputs_kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
- name: mydb_secretoutputs
- }
-
resource pg_secretoutputs_kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
name: pg_secretoutputs
}
- resource mydb_secretoutputs_kv_connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' existing = {
- name: 'connectionString'
- parent: mydb_secretoutputs_kv
- }
-
resource pg_secretoutputs_kv_db_connectionString 'Microsoft.KeyVault/vaults/secrets@2023-07-01' existing = {
name: 'db-connectionString'
parent: pg_secretoutputs_kv
@@ -607,11 +598,6 @@ param api_containerimage string
properties: {
configuration: {
secrets: [
- {
- name: 'connectionstrings--mydb'
- identity: outputs_azure_container_registry_managed_identity_id
- keyVaultUrl: mydb_secretoutputs_kv_connectionString.properties.secretUri
- }
{
name: 'connectionstrings--db'
identity: outputs_azure_container_registry_managed_identity_id
@@ -678,7 +664,7 @@ param api_containerimage string
}
{
name: 'ConnectionStrings__mydb'
- secretRef: 'connectionstrings--mydb'
+ value: mydb_outputs_connectionstring
}
{
name: 'ConnectionStrings__blobs'
@@ -1105,7 +1091,7 @@ public async Task SecretOutputHandling()
builder.AddAzureContainerAppsInfrastructure();
- var db = builder.AddAzureCosmosDB("mydb").AddDatabase("db");
+ var db = builder.AddAzureCosmosDB("mydb").WithAccessKeyAuthentication().WithDatabase("db");
builder.AddContainer("api", "image")
.WithReference(db)
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBEmulatorFunctionalTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBEmulatorFunctionalTests.cs
index d1156b9b1bc..bd9476f10a4 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBEmulatorFunctionalTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBEmulatorFunctionalTests.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Net;
using Aspire.Components.Common.Tests;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
@@ -25,7 +26,7 @@ public class AzureCosmosDBEmulatorFunctionalTests(ITestOutputHelper testOutputHe
public async Task VerifyWaitForOnCosmosDBEmulatorBlocksDependentResources(bool usePreview)
{
// Cosmos can be pretty slow to spin up, lets give it plenty of time.
- var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
+ var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10));
using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper);
var healthCheckTcs = new TaskCompletionSource();
@@ -68,7 +69,7 @@ public async Task VerifyWaitForOnCosmosDBEmulatorBlocksDependentResources(bool u
[RequiresDocker(Reason = "CosmosDB emulator is needed for this test")]
public async Task VerifyCosmosResource(bool usePreview)
{
- var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
+ var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10));
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new()
{
@@ -86,12 +87,12 @@ public async Task VerifyCosmosResource(bool usePreview)
var containerName = "container1";
var cosmos = builder.AddAzureCosmosDB("cosmos");
- var db = cosmos.AddDatabase(databaseName)
+ var db = cosmos.WithDatabase(databaseName)
.RunAsEmulator(usePreview);
using var app = builder.Build();
- await app.StartAsync();
+ await app.StartAsync(cts.Token);
var rns = app.Services.GetRequiredService();
await rns.WaitForResourceHealthyAsync(db.Resource.Name, cts.Token);
@@ -140,7 +141,7 @@ public async Task WithDataVolumeShouldPersistStateBetweenUsages(bool usePreview)
{
// Use a volume to do a snapshot save
- var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
+ var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10));
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new()
{
@@ -160,7 +161,7 @@ public async Task WithDataVolumeShouldPersistStateBetweenUsages(bool usePreview)
// Use a deterministic volume name to prevent them from exhausting the machines if deletion fails
var volumeName = VolumeNameGenerator.Generate(cosmos1, nameof(WithDataVolumeShouldPersistStateBetweenUsages));
- var db1 = cosmos1.AddDatabase(databaseName)
+ var db1 = cosmos1.WithDatabase(databaseName)
.RunAsEmulator(usePreview, volumeName);
// if the volume already exists (because of a crashing previous run), delete it
@@ -170,7 +171,7 @@ public async Task WithDataVolumeShouldPersistStateBetweenUsages(bool usePreview)
using (var app = builder1.Build())
{
- await app.StartAsync();
+ await app.StartAsync(cts.Token);
var rns = app.Services.GetRequiredService();
await rns.WaitForResourceHealthyAsync(db1.Resource.Name, cts.Token);
@@ -213,12 +214,12 @@ await pipeline.ExecuteAsync(async token =>
using var builder2 = TestDistributedApplicationBuilder.Create(options => { }, testOutputHelper);
var cosmos2 = builder2.AddAzureCosmosDB("cosmos");
- var db2 = cosmos2.AddDatabase(databaseName)
+ var db2 = cosmos2.WithDatabase(databaseName)
.RunAsEmulator(usePreview, volumeName);
using (var app = builder2.Build())
{
- await app.StartAsync();
+ await app.StartAsync(cts.Token);
var rns = app.Services.GetRequiredService();
await rns.WaitForResourceHealthyAsync(db2.Resource.Name, cts.Token);
@@ -266,6 +267,54 @@ await pipeline.ExecuteAsync(async token =>
DockerUtils.AttemptDeleteDockerVolume(volumeName);
}
+
+ [Fact]
+ [RequiresDocker]
+ public async Task AddAzureCosmosDB_RunAsEmulator_CreatesDatabase()
+ {
+ var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10));
+
+ using var builder = TestDistributedApplicationBuilder.Create(options => { }, testOutputHelper);
+
+ var databaseName = "db1";
+ var containerName = "container1";
+ var partitionKeyPath = "/id";
+
+ var cosmos = builder.AddAzureCosmosDB("cosmos")
+ .WithDatabase(databaseName, db => db.Containers.Add(new(containerName, partitionKeyPath)))
+ .RunAsEmulator();
+
+ using var app = builder.Build();
+
+ await app.StartAsync(cts.Token);
+
+ var rns = app.Services.GetRequiredService();
+ await rns.WaitForResourceHealthyAsync(cosmos.Resource.Name, cts.Token);
+
+ var hb = Host.CreateApplicationBuilder();
+ hb.Configuration[$"ConnectionStrings:{cosmos.Resource.Name}"] = await cosmos.Resource.ConnectionStringExpression.GetValueAsync(default);
+ hb.AddAzureCosmosClient(cosmos.Resource.Name);
+
+ using var host = hb.Build();
+
+ await host.StartAsync(cts.Token);
+
+ using var cosmosClient = host.Services.GetRequiredService();
+
+ var database = cosmosClient.GetDatabase(databaseName);
+ var result1 = await database.ReadAsync(cancellationToken: cts.Token);
+
+ var container = database.GetContainer(containerName);
+ var result2 = await container.ReadContainerAsync(cancellationToken: cts.Token);
+
+ Assert.True(IsSuccess(result1.StatusCode));
+ Assert.True(IsSuccess(result2.StatusCode));
+
+ static bool IsSuccess(HttpStatusCode httpStatusCode)
+ {
+ return ((int)httpStatusCode >= 200) && ((int)httpStatusCode <= 299);
+ }
+ }
}
public class EFCoreCosmosDbContext(DbContextOptions options) : DbContext(options)