Skip to content
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

[release/8.0-preview1] Azure cosmosdb support in aspire (#359) #669

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Azure.Provis
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eShopLite", "eShopLite", "{A68BA1A5-1604-433D-9778-DC0199831C2A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.Azure.Cosmos", "src\Components\Aspire.Microsoft.Azure.Cosmos\Aspire.Microsoft.Azure.Cosmos.csproj", "{23298562-C1D4-41CD-83FE-426C94FEE35F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.EntityFrameworkCore.Cosmos", "src\Components\Aspire.Microsoft.EntityFrameworkCore.Cosmos\Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj", "{00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.Azure.Cosmos.Tests", "tests\Aspire.Microsoft.Azure.Cosmos.Tests\Aspire.Microsoft.Azure.Cosmos.Tests.csproj", "{A5836BC1-6A45-4BB6-9D22-A7F750890AB8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests", "tests\Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests\Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests.csproj", "{FDA02617-9C49-4DA8-A43A-A34DBA9B8596}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CatalogDb", "samples\eShopLite\CatalogDb\CatalogDb.csproj", "{A84C4EE3-2601-4804-BCDC-E9948E164A22}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{991DB378-6CB5-4441-BFC3-657400690FC3}"
Expand Down Expand Up @@ -370,6 +378,22 @@ Global
{D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Release|Any CPU.Build.0 = Release|Any CPU
{23298562-C1D4-41CD-83FE-426C94FEE35F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23298562-C1D4-41CD-83FE-426C94FEE35F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23298562-C1D4-41CD-83FE-426C94FEE35F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23298562-C1D4-41CD-83FE-426C94FEE35F}.Release|Any CPU.Build.0 = Release|Any CPU
{00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}.Release|Any CPU.Build.0 = Release|Any CPU
{A5836BC1-6A45-4BB6-9D22-A7F750890AB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5836BC1-6A45-4BB6-9D22-A7F750890AB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5836BC1-6A45-4BB6-9D22-A7F750890AB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5836BC1-6A45-4BB6-9D22-A7F750890AB8}.Release|Any CPU.Build.0 = Release|Any CPU
{FDA02617-9C49-4DA8-A43A-A34DBA9B8596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDA02617-9C49-4DA8-A43A-A34DBA9B8596}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDA02617-9C49-4DA8-A43A-A34DBA9B8596}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDA02617-9C49-4DA8-A43A-A34DBA9B8596}.Release|Any CPU.Build.0 = Release|Any CPU
{A84C4EE3-2601-4804-BCDC-E9948E164A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A84C4EE3-2601-4804-BCDC-E9948E164A22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A84C4EE3-2601-4804-BCDC-E9948E164A22}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -446,6 +470,10 @@ Global
{E2EC79D0-80F7-4471-9613-D7C8C3D52F95} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47}
{D4BD974F-6505-43FC-A94E-2019F0DB5D5D} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47}
{A68BA1A5-1604-433D-9778-DC0199831C2A} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0}
{23298562-C1D4-41CD-83FE-426C94FEE35F} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
{00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
{A5836BC1-6A45-4BB6-9D22-A7F750890AB8} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{FDA02617-9C49-4DA8-A43A-A34DBA9B8596} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{A84C4EE3-2601-4804-BCDC-E9948E164A22} = {A68BA1A5-1604-433D-9778-DC0199831C2A}
{4D8A92AB-4E77-4965-AD8E-8E206DCE66A4} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
{165411FE-755E-4869-A756-F87F455860AC} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
Expand Down
4 changes: 3 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.5.0" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.18.0" />
<PackageVersion Include="Azure.Storage.Queues" Version="12.16.0" />
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.36.0-preview" />
<PackageVersion Include="Microsoft.Extensions.Azure" Version="1.7.1" />
<!-- Azure Management SDK for .NET dependencies -->
<PackageVersion Include="Azure.ResourceManager.KeyVault" Version="1.2.0-beta.2" />
Expand Down Expand Up @@ -44,6 +45,7 @@
<!-- sql client dependencies -->
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.1" />
<!-- efcore dependencies -->
<PackageVersion Include="Microsoft.EntityFrameworkCore.Cosmos" Version="$(EfCoreVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(EfCoreVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(EfCoreVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="$(EfCoreVersion)" />
Expand Down Expand Up @@ -100,4 +102,4 @@
<PackageVersion Include="Microsoft.Signed.Wix" Version="1.0.0-v3.14.0.5722" />
<PackageVersion Include="Microsoft.DotNet.Build.Tasks.Installers" Version="8.0.0-beta.23371.1" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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;
using Aspire.Hosting.Azure.Data.Cosmos;
using System.Text.Json;

namespace Aspire.Hosting;

/// <summary>
/// Provides extension methods for adding Azure Cosmos DB resources to an <see cref="IDistributedApplicationBuilder"/>.
/// </summary>
public static class AzureCosmosDBCloudApplicationBuilderExtensions
{
/// <summary>
/// Adds an Azure Cosmos DB connection to the application model.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="connectionString">The connection string.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureCosmosDatabaseResource}"/>.</returns>
public static IResourceBuilder<AzureCosmosDBConnectionResource> AddAzureCosmosDB(
this IDistributedApplicationBuilder builder,
string name,
string? connectionString = null)
{
var connection = new AzureCosmosDBConnectionResource(name, connectionString);
return builder.AddResource(connection)
.WithAnnotation(new ManifestPublishingCallbackAnnotation(jsonWriter => WriteCosmosDBConnectionToManifest(jsonWriter, connection)));
}

private static void WriteCosmosDBConnectionToManifest(Utf8JsonWriter jsonWriter, AzureCosmosDBConnectionResource cosmosDbConnection)
{
jsonWriter.WriteString("type", "azure.cosmosdb.connection.v0");
jsonWriter.WriteString("connectionString", cosmosDbConnection.GetConnectionString());
}

private static void WriteCosmosDBDatabaseToManifest(Utf8JsonWriter jsonWriter, AzureCosmosDatabaseResource cosmosDatabase)
{
jsonWriter.WriteString("type", "azure.cosmosdb.database.v0");
jsonWriter.WriteString("parent", cosmosDatabase.Parent.Name);
jsonWriter.WriteString("databaseName", cosmosDatabase.Name);
}

/// <summary>
/// Adds an Azure Cosmos DB database to a <see cref="IResourceBuilder{AzureCosmosDatabaseResource}"/>.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureCosmosDatabaseResource}"/>.</returns>
public static IResourceBuilder<AzureCosmosDatabaseResource> AddDatabase(this IResourceBuilder<AzureCosmosDBConnectionResource> builder, string name)
{
var cosmosDatabase = new AzureCosmosDatabaseResource(name, builder.Resource);
return builder
.ApplicationBuilder
.AddResource(cosmosDatabase)
.WithAnnotation(new ManifestPublishingCallbackAnnotation(
(json) => WriteCosmosDBDatabaseToManifest(json, cosmosDatabase)));
}
}
21 changes: 21 additions & 0 deletions src/Aspire.Hosting.Azure/AzureCosmosDBConnectionResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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;

/// <summary>
/// Represents a connection to an Azure Cosmos DB account.
/// </summary>
/// <param name="name">The resource name.</param>
/// <param name="connectionString">The connection string to use to connect.</param>
public class AzureCosmosDBConnectionResource(string name, string? connectionString)
: Resource(name), IResourceWithConnectionString
{
/// <summary>
/// Gets the connection string to use for this database.
/// </summary>
/// <returns>The connection string to use for this database.</returns>
public string? GetConnectionString() => connectionString;
}
29 changes: 29 additions & 0 deletions src/Aspire.Hosting.Azure/AzureCosmosDatabaseResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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;

/// <summary>
/// Represents an Azure Cosmos DB database.
/// </summary>
/// <param name="name">The database name.</param>
/// <param name="parent">The parent <see cref="AzureCosmosDBConnectionResource"/>.</param>
public class AzureCosmosDatabaseResource(string name, AzureCosmosDBConnectionResource parent)
: Resource(name), IResourceWithParent<AzureCosmosDBConnectionResource>, IResourceWithConnectionString
{
/// <summary>
/// Gets the parent <see cref="AzureCosmosDBConnectionResource"/>.
/// </summary>
public AzureCosmosDBConnectionResource Parent { get; } = parent;

/// <summary>
/// Gets the connection string to use for this database.
/// </summary>
/// <returns>The connection string to use for this database.</returns>
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>
<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,126 @@
// 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);

var settings = new AzureCosmosDBSettings();
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);

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

Loading