-
Notifications
You must be signed in to change notification settings - Fork 921
Azure cosmosdb support in aspire #359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 87 commits
d11b66a
3374fb6
1e60bf5
4eaae64
6421e60
07d8284
8dae4bd
f706813
3f8b528
0ac6633
ed5c74b
8b72bf0
a98aaee
3f0bb37
4bd8129
9d7442a
aff38a2
9cf1d52
a4a9b29
9b412bd
4709022
dcab2df
2067377
ce8f094
46db475
49908bd
e5c96ac
8d04a70
0af6420
c9f434c
c880d96
e495d03
f2feb2b
053e84b
2765016
6770b32
77d4b68
e8a6bda
447e1d3
64c0994
735035e
362baf5
254e82e
b0db3db
952f5fd
3f03c80
0f413bd
c491f20
bc01c19
bf0e4fa
e580324
484f53b
8573329
f1a8a79
2c9699e
7474fd1
3584144
5ab2c0b
1cb8dcd
49af7e4
5653395
bce48ec
828f5dd
54326ee
20138fa
6cbcac5
6ed5925
92ecb4c
61d05e9
85a66a8
4f01310
8d76cc1
0130296
f22856d
c86dec1
97aa291
b372614
7898ce9
932723c
6214639
24c71a4
138579d
3f53c4f
0a40231
3209cc5
2f89327
ea41461
2a17893
0058fa1
2a3d1cc
f58be30
9595b88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
|
Pilchie marked this conversation as resolved.
Outdated
|
||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Hosting.ApplicationModel; | ||
| using Aspire.Hosting.Azure.Data.Cosmos; | ||
| using System.Text.Json; | ||
|
|
||
| namespace Aspire.Hosting; | ||
|
|
||
| public static class AzureCosmosDBCloudApplicationBuilderExtensions | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have unit tests for these?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sigh - Now I moved them into Aspire.Hosting.Azure, and there is no test project at all for that :/ |
||
| { | ||
| public static IResourceBuilder<CosmosDBConnectionResource> AddAzureCosmosDB( | ||
|
Pilchie marked this conversation as resolved.
Outdated
|
||
| this IDistributedApplicationBuilder builder, | ||
| string name, | ||
| string? connectionString = null) | ||
| { | ||
| var connection = new CosmosDBConnectionResource(name, connectionString); | ||
| return builder.AddResource(connection) | ||
| .WithAnnotation(new ManifestPublishingCallbackAnnotation(jsonWriter => WriteCosmosDBConnectionToManifest(jsonWriter, connection))); | ||
| } | ||
|
|
||
| private static void WriteCosmosDBConnectionToManifest(Utf8JsonWriter jsonWriter, CosmosDBConnectionResource cosmosDbConnection) | ||
| { | ||
| jsonWriter.WriteString("type", "azure.data.cosmos.connection.v1"); | ||
|
Pilchie marked this conversation as resolved.
Outdated
|
||
| jsonWriter.WriteString("connectionString", cosmosDbConnection.GetConnectionString()); | ||
| } | ||
|
|
||
| private static void WriteCosmosDBDatabaseToManifest(Utf8JsonWriter jsonWriter, CosmosDatabaseResource cosmosDatabase) | ||
| { | ||
| jsonWriter.WriteString("type", "azure.data.cosmos.server.v1"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this type name correct? If this resource has a parent, then the server should be the parent resource, and this should be database or collection or something like that.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No idea. I have no intuition about what should be in the manifest, or what it's used for. Any docs/specs/etc that would help me reason about it would be great.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take a look at The idea is that the tool that processes the manifest can use this information to pre-create the database for the application because typically code running on the app server wouldn't have permissions to perform these kinds of operations.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So in this case the hierarchy is:
It would be ideal to be able to model all the way down to the Container level and use the control plane SDK to provision these. (for example, Managed Identities in the data plane SDK don't support control plane operations, though they do with keys). Today it's a bit of a mix. You need a connection string to connect to an account, but then you need a name to get the database. That's why there is that weird extra parameter to the I initially tried to model this better, but couldn't figure out how to flow the connection string from the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we'll need to write a provisioner to give reasonable feedback here. |
||
| jsonWriter.WriteString("parent", cosmosDatabase.Parent.Name); | ||
| jsonWriter.WriteString("databaseName", cosmosDatabase.Name); | ||
| } | ||
|
|
||
| public static IResourceBuilder<CosmosDatabaseResource> AddDatabase(this IResourceBuilder<CosmosDBConnectionResource> builder, string name) | ||
| { | ||
| var cosmosDatabase = new CosmosDatabaseResource(name, builder.Resource); | ||
| return builder | ||
| .ApplicationBuilder | ||
| .AddResource(cosmosDatabase) | ||
| .WithAnnotation(new ManifestPublishingCallbackAnnotation( | ||
| (json) => WriteCosmosDBDatabaseToManifest(json, cosmosDatabase))); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // 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.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting.Azure.Data.Cosmos; | ||
|
|
||
| public class CosmosDBConnectionResource(string name, string? connectionString) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll probably need to have
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate on the difference and the scenario?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @eerhardt thoughts on this one?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure. @mitchdenny - can you elaborate?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we are dealing with resource types like this where there is a child resource involved we'll typically end up dealing with three types:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per above, I think there is actually another level of nesting for Cosmos DB Account -> Database -> Container. However, the ConnectionString only uniquely identifies the Account, so I'm not sure how to fit that into Aspire right now.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, and to make this tricky - for the EF version, the containers it uses will have properties that depend on the code that sets up the (for example what the partition key is). |
||
| : Resource(name), IResourceWithConnectionString | ||
| { | ||
| public string? GetConnectionString() => connectionString; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // 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.ApplicationModel; | ||
|
|
||
| namespace Aspire.Hosting.Azure.Data.Cosmos; | ||
|
|
||
| public class CosmosDatabaseResource(string name, CosmosDBConnectionResource parent) | ||
| : Resource(name), IResourceWithParent<CosmosDBConnectionResource>, IResourceWithConnectionString | ||
| { | ||
| public CosmosDBConnectionResource Parent { get; } = parent; | ||
|
|
||
| public string? GetConnectionString() | ||
| { | ||
| return Parent.GetConnectionString(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>$(NetCurrent)</TargetFramework> | ||
| <IsPackable>true</IsPackable> | ||
| <EnableConfigurationBindingGenerator>false</EnableConfigurationBindingGenerator> | ||
| <IsAotCompatible>false</IsAotCompatible> | ||
|
Pilchie marked this conversation as resolved.
|
||
| <PackageTags>$(ComponentAzurePackageTags) cosmos cosmosdb data database db</PackageTags> | ||
| <Description>A client for Azure Cosmos DB that integrates with Aspire, including logging and telemetry.</Description> | ||
| <PackageIconFullPath>$(SharedDir)AzureCosmosDB_256x.png</PackageIconFullPath> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.Identity" /> | ||
| <PackageReference Include="Microsoft.Azure.Cosmos" /> | ||
| <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" /> | ||
| <PackageReference Include="OpenTelemetry.Extensions.Hosting" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Microsoft.Azure.Cosmos; | ||
| using Azure.Identity; | ||
| using Microsoft.Azure.Cosmos; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
|
|
||
| namespace Microsoft.Extensions.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Azure CosmosDB extension | ||
| /// </summary> | ||
| public static class AspireAzureCosmosDBExtensions | ||
| { | ||
| private const string DefaultConfigSectionName = "Aspire:Microsoft:Azure:Cosmos"; | ||
|
|
||
| /// <summary> | ||
| /// Registers <see cref="CosmosClient" /> as a singleton in the services provided by the <paramref name="builder"/>. | ||
| /// Configures logging and telemetry for the <see cref="CosmosClient" />. | ||
| /// </summary> | ||
| /// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param> | ||
| /// <param name="connectionName">The connection name to use to find a connection string.</param> | ||
| /// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureCosmosDBSettings"/>. It's invoked after the settings are read from the configuration.</param> | ||
| /// <param name="configureClientOptions">An optional method that can be used for customizing the <see cref="CosmosClientOptions"/>.</param> | ||
| /// <remarks>Reads the configuration from "Aspire:Microsoft:Azure:Cosmos" section.</remarks> | ||
| /// <exception cref="InvalidOperationException">If required ConnectionString is not provided in configuration section</exception> | ||
| public static void AddAzureCosmosDB( | ||
| this IHostApplicationBuilder builder, | ||
| string connectionName, | ||
| Action<AzureCosmosDBSettings>? configureSettings = null, | ||
| Action<CosmosClientOptions>? configureClientOptions = null) | ||
| { | ||
| AddAzureCosmosDB(builder, DefaultConfigSectionName, configureSettings, configureClientOptions, connectionName, serviceKey: null); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Registers <see cref="CosmosClient" /> as a singleton for given <paramref name="name" /> in the services provided by the <paramref name="builder"/>. | ||
| /// Configures logging and telemetry for the <see cref="CosmosClient" />. | ||
| /// </summary> | ||
| /// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param> | ||
| /// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection string from the ConnectionStrings configuration section.</param> | ||
| /// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureCosmosDBSettings"/>. It's invoked after the settings are read from the configuration.</param> | ||
| /// <param name="configureClientOptions">An optional method that can be used for customizing the <see cref="CosmosClientOptions"/>.</param> | ||
| /// <remarks>Reads the configuration from "Aspire:Microsoft:Azure:Cosmos:{name}" section.</remarks> | ||
| /// <exception cref="InvalidOperationException">If required ConnectionString is not provided in configuration section</exception> | ||
| public static void AddKeyedAzureCosmosDB( | ||
| this IHostApplicationBuilder builder, | ||
| string name, | ||
| Action<AzureCosmosDBSettings>? configureSettings = null, | ||
| Action<CosmosClientOptions>? configureClientOptions = null) | ||
| { | ||
| AddAzureCosmosDB(builder, $"{DefaultConfigSectionName}:{name}", configureSettings, configureClientOptions, connectionName: name, serviceKey: name); | ||
| } | ||
|
|
||
| private static void AddAzureCosmosDB( | ||
| this IHostApplicationBuilder builder, | ||
| string configurationSectionName, | ||
| Action<AzureCosmosDBSettings>? configureSettings, | ||
| Action<CosmosClientOptions>? configureClientOptions, | ||
| string connectionName, | ||
| string? serviceKey) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
|
|
||
| AzureCosmosDBSettings settings = new(); | ||
| builder.Configuration.GetSection(configurationSectionName).Bind(settings); | ||
|
|
||
| if (builder.Configuration.GetConnectionString(connectionName) is string connectionString) | ||
| { | ||
| if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) | ||
| { | ||
| settings.AccountEndpoint = uri; | ||
| } | ||
| else | ||
| { | ||
| settings.ConnectionString = connectionString; | ||
| } | ||
| } | ||
|
|
||
| configureSettings?.Invoke(settings); | ||
| var clientOptions = new CosmosClientOptions(); | ||
|
|
||
| // Needs to be enabled for either logging or tracing to work. | ||
| clientOptions.CosmosClientTelemetryOptions.DisableDistributedTracing = false; | ||
|
|
||
| if (settings.Tracing) | ||
| { | ||
|
|
||
| builder.Services.AddOpenTelemetry().WithTracing(tracerProviderBuilder => | ||
| { | ||
| tracerProviderBuilder.AddSource("Azure.Cosmos.Operation"); | ||
| }); | ||
| } | ||
|
|
||
| configureClientOptions?.Invoke(clientOptions); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's on my todolist, but it's not easy, since I don't think many parts of the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I've been doing is walking through the type and looking for simple properties. You can see all of them in the other Azure components schema, or in the RabbitMQ schema I just did a few days ago: And then add a test for it like so:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we consider merging this and I'll expand on that in a separate PR? |
||
|
|
||
| if (serviceKey is null) | ||
| { | ||
| builder.Services.AddSingleton(_ => ConfigureDb()); | ||
| } | ||
| else | ||
| { | ||
| builder.Services.AddKeyedSingleton(serviceKey, (sp, key) => ConfigureDb()); | ||
| } | ||
|
|
||
| CosmosClient ConfigureDb() | ||
| { | ||
| if (!string.IsNullOrEmpty(settings.ConnectionString)) | ||
| { | ||
| return new CosmosClient(settings.ConnectionString, clientOptions); | ||
| } | ||
| else if (settings.AccountEndpoint is not null) | ||
| { | ||
| var credential = settings.Credential ?? new DefaultAzureCredential(); | ||
| return new CosmosClient(settings.AccountEndpoint.OriginalString, credential, clientOptions); | ||
| } | ||
| else | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"A CosmosClient could not be configured. Ensure valid connection information was provided in 'ConnectionStrings:{connectionName}' or either " + | ||
| $"{nameof(settings.ConnectionString)} or {nameof(settings.AccountEndpoint)} must be provided " + | ||
| $"in the '{configurationSectionName}' configuration section."); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Azure.Core; | ||
|
|
||
| namespace Aspire.Microsoft.Azure.Cosmos; | ||
|
|
||
| /// <summary> | ||
| /// The settings relevant to accessing Azure Cosmos DB. | ||
| /// </summary> | ||
| public sealed class AzureCosmosDBSettings | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the connection string of the Azure Cosmos database to connect to. | ||
| /// </summary> | ||
| public string? ConnectionString { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// <para>Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is enabled or not.</para> | ||
| /// <para>Enabled by default.</para> | ||
| /// </summary> | ||
| public bool Tracing { get; set; } = true; | ||
|
|
||
| /// <summary> | ||
| /// A <see cref="Uri"/> referencing the Azure Cosmos DB Endpoint. | ||
| /// This is likely to be similar to "https://{account_name}.queue.core.windows.net". | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Must not contain shared access signature. | ||
| /// Used along with <see cref="Credential"/> to establish the connection. | ||
| /// </remarks> | ||
| public Uri? AccountEndpoint { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the credential used to authenticate to the Azure Cosmos DB endpoint. | ||
| /// </summary> | ||
| public TokenCredential? Credential { get; set; } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| { | ||
| "definitions": { | ||
| "logLevel": { | ||
| "properties": { | ||
| "Azure-Cosmos-Operation-Request-Diagnostics": { | ||
| "$ref": "#/definitions/logLevelThreshold" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "properties": { | ||
|
Pilchie marked this conversation as resolved.
|
||
| "Aspire": { | ||
| "type": "object", | ||
| "properties": { | ||
| "Microsoft": { | ||
| "type": "object", | ||
| "properties": { | ||
| "Azure": { | ||
| "type": "object", | ||
| "properties": { | ||
| "Cosmos": { | ||
| "type": "object", | ||
| "properties": { | ||
| "ConnectionString": { | ||
| "type": "string", | ||
| "description": "Gets or sets the connection string of the Azure Cosmos DB to connect to. If both are provided, 'ConnectionString' takes precedence over 'AccountEndpoint'." | ||
| }, | ||
| "AccountEndpoint": { | ||
| "type": "string", | ||
| "description": "Gets or sets the account endpoint of the Azure Cosmos DB to connect to. If both are provided, 'ConnectionString' takes precedence over 'AccountEndpoint'." | ||
| }, | ||
| "Tracing": { | ||
|
Pilchie marked this conversation as resolved.
|
||
| "type": "boolean", | ||
| "description": "Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is enabled or not." | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like 3.36.0 is out now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CosmosDB does versioning wrong right now :(. 3.36.0-preview is actually 3.36.0 + preview stuff. In this case it's the difference between distributed tracing being on or off by default, so I'm happy to change if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is some messed up versioning for sure. Definitely not how the rest of the ecosystem does it.