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

Add support for CosmosDB vnext-preview emulator #7048

Merged
merged 5 commits into from
Jan 14, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
app.MapGet("/", async (CosmosClient cosmosClient) =>
{
var db = (await cosmosClient.CreateDatabaseIfNotExistsAsync("db")).Database;
var container = (await db.CreateContainerIfNotExistsAsync("entries", "/Id")).Container;
var container = (await db.CreateContainerIfNotExistsAsync("entries", "/id")).Container;

// Add an entry to the database on each request.
var newEntry = new Entry() { Id = Guid.NewGuid().ToString() };
Expand Down Expand Up @@ -69,6 +69,12 @@ public class Entry
public class TestCosmosContext(DbContextOptions<TestCosmosContext> options) : DbContext(options)
{
public DbSet<EntityFrameworkEntry> Entries { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EntityFrameworkEntry>()
.HasPartitionKey(e => e.Id);
}
}

public class EntityFrameworkEntry
Expand Down
4 changes: 3 additions & 1 deletion playground/CosmosEndToEnd/CosmosEndToEnd.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

var builder = DistributedApplication.CreateBuilder(args);

#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")
.RunAsEmulator();
.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.

builder.AddProject<Projects.CosmosEndToEnd_ApiService>("api")
.WithExternalHttpEndpoints()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ namespace Aspire.Hosting.Azure;

internal static class AzureCosmosDBEmulatorConnectionString
{
public static ReferenceExpression Create(EndpointReference endpoint) => ReferenceExpression.Create($"AccountKey={CosmosConstants.EmulatorAccountKey};AccountEndpoint=https://{endpoint.Property(EndpointProperty.IPV4Host)}:{endpoint.Property(EndpointProperty.Port)};DisableServerCertificateValidation=True;");
public static ReferenceExpression Create(EndpointReference endpoint, bool isPreviewEmulator) =>
isPreviewEmulator
? ReferenceExpression.Create($"AccountKey={CosmosConstants.EmulatorAccountKey};AccountEndpoint={endpoint.Property(EndpointProperty.Url)}")
: ReferenceExpression.Create($"AccountKey={CosmosConstants.EmulatorAccountKey};AccountEndpoint=https://{endpoint.Property(EndpointProperty.IPV4Host)}:{endpoint.Property(EndpointProperty.Port)};DisableServerCertificateValidation=True;");
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ namespace Aspire.Hosting.Azure;
/// <param name="innerResource">The inner resource used to store annotations.</param>
public class AzureCosmosDBEmulatorResource(AzureCosmosDBResource innerResource) : ContainerResource(innerResource.Name), IResource
{
private readonly AzureCosmosDBResource _innerResource = innerResource;

/// <inheritdoc/>
public override string Name => _innerResource.Name;
internal AzureCosmosDBResource InnerResource { get; } = innerResource;

/// <inheritdoc />
public override ResourceAnnotationCollection Annotations => _innerResource.Annotations;
public override ResourceAnnotationCollection Annotations => InnerResource.Annotations;
}
63 changes: 58 additions & 5 deletions src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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 System.Globalization;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Aspire.Hosting.Azure.Cosmos;
Expand All @@ -12,7 +14,6 @@
using Azure.Provisioning.KeyVault;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.DependencyInjection;
using System.Globalization;

namespace Aspire.Hosting;

Expand Down Expand Up @@ -108,18 +109,36 @@ public static IResourceBuilder<AzureCosmosDBResource> AddAzureCosmosDB(this IDis
/// This version of the package defaults to the <inheritdoc cref="CosmosDBEmulatorContainerImageTags.Tag"/> tag of the <inheritdoc cref="CosmosDBEmulatorContainerImageTags.Registry"/>/<inheritdoc cref="CosmosDBEmulatorContainerImageTags.Image"/> container image.
/// </remarks>
public static IResourceBuilder<AzureCosmosDBResource> RunAsEmulator(this IResourceBuilder<AzureCosmosDBResource> builder, Action<IResourceBuilder<AzureCosmosDBEmulatorResource>>? configureContainer = null)
=> builder.RunAsEmulator(configureContainer, useVNextPreview: false);

/// <summary>
/// Configures an Azure Cosmos DB resource to be emulated using the Azure Cosmos DB Linux-based emulator (preview) with the NoSQL API. This resource requires an <see cref="AzureCosmosDBResource"/> to be added to the application model.
/// For more information on the Azure Cosmos DB emulator, see <a href="https://learn.microsoft.com/azure/cosmos-db/emulator-linux"></a>.
/// </summary>
/// <param name="builder">The Azure Cosmos DB resource builder.</param>
/// <param name="configureContainer">Callback that exposes underlying container used for emulation to allow for customization.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
/// <remarks>
/// This version of the package defaults to the <inheritdoc cref="CosmosDBEmulatorContainerImageTags.TagVNextPreview"/> tag of the <inheritdoc cref="CosmosDBEmulatorContainerImageTags.Registry"/>/<inheritdoc cref="CosmosDBEmulatorContainerImageTags.Image"/> container image.
/// </remarks>
[Experimental("ASPIRECOSMOS001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")]
public static IResourceBuilder<AzureCosmosDBResource> RunAsPreviewEmulator(this IResourceBuilder<AzureCosmosDBResource> builder, Action<IResourceBuilder<AzureCosmosDBEmulatorResource>>? configureContainer = null)
=> builder.RunAsEmulator(configureContainer, useVNextPreview: true);

private static IResourceBuilder<AzureCosmosDBResource> RunAsEmulator(this IResourceBuilder<AzureCosmosDBResource> builder, Action<IResourceBuilder<AzureCosmosDBEmulatorResource>>? configureContainer, bool useVNextPreview)
{
if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
return builder;
}

builder.WithEndpoint(name: "emulator", targetPort: 8081)
var scheme = useVNextPreview ? "http" : null;
builder.WithEndpoint(name: "emulator", scheme: scheme, targetPort: 8081)
.WithAnnotation(new ContainerImageAnnotation
{
Registry = CosmosDBEmulatorContainerImageTags.Registry,
Image = CosmosDBEmulatorContainerImageTags.Image,
Tag = CosmosDBEmulatorContainerImageTags.Tag
Tag = useVNextPreview ? CosmosDBEmulatorContainerImageTags.TagVNextPreview : CosmosDBEmulatorContainerImageTags.Tag
});

CosmosClient? cosmosClient = null;
Expand Down Expand Up @@ -182,8 +201,12 @@ static CosmosClient CreateCosmosClient(string connectionString)
/// <param name="name">The name of the volume. Defaults to an auto-generated name based on the application and resource names.</param>
/// <returns>A builder for the <see cref="AzureCosmosDBEmulatorResource"/>.</returns>
public static IResourceBuilder<AzureCosmosDBEmulatorResource> WithDataVolume(this IResourceBuilder<AzureCosmosDBEmulatorResource> builder, string? name = null)
=> builder.WithEnvironment("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "true")
.WithVolume(name ?? VolumeNameGenerator.Generate(builder, "data"), "/tmp/cosmos/appdata", isReadOnly: false);
{
var dataPath = builder.Resource.InnerResource.IsPreviewEmulator ? "/data": "/tmp/cosmos/appdata";

return builder.WithEnvironment("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "true")
.WithVolume(name ?? VolumeNameGenerator.Generate(builder, "data"), dataPath, isReadOnly: false);
}

/// <summary>
/// Configures the gateway port for the Azure Cosmos DB emulator.
Expand All @@ -210,6 +233,11 @@ public static IResourceBuilder<AzureCosmosDBEmulatorResource> WithGatewayPort(th
/// </remarks>
public static IResourceBuilder<AzureCosmosDBEmulatorResource> WithPartitionCount(this IResourceBuilder<AzureCosmosDBEmulatorResource> builder, int count)
{
if (builder.Resource.InnerResource.IsPreviewEmulator)
{
throw new NotSupportedException($"'{nameof(WithPartitionCount)}' does not work when using the preview version of the Azure Cosmos DB emulator.");
}

if (count < 1 || count > 250)
{
throw new ArgumentOutOfRangeException(nameof(count), count, "Count must be between 1 and 250.");
Expand All @@ -229,4 +257,29 @@ public static IResourceBuilder<AzureCosmosDBResource> AddDatabase(this IResource
builder.Resource.Databases.Add(databaseName);
return builder;
}

/// <summary>
/// Configures the Azure Cosmos DB preview emulator to expose the Data Explorer endpoint.
/// </summary>
/// <param name="builder">Builder for the Cosmos emulator container</param>
/// <param name="port">Optional host port to bind the Data Explorer to.</param>
/// <returns>Cosmos emulator resource builder.</returns>
/// <remarks>
/// The Data Explorer is only available with <see cref="RunAsPreviewEmulator"/>.
/// </remarks>
[Experimental("ASPIRECOSMOS001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")]
public static IResourceBuilder<AzureCosmosDBEmulatorResource> WithDataExplorer(this IResourceBuilder<AzureCosmosDBEmulatorResource> builder, int? port = null)
{
if (!builder.Resource.InnerResource.IsPreviewEmulator)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it doesn't work in the latest emulator. See Azure/azure-cosmos-db-emulator-docker#135.

{
throw new NotSupportedException($"The Data Explorer endpoint is only available when using the preview version of the Azure Cosmos DB emulator. Call '{nameof(RunAsPreviewEmulator)}' instead.");
}

return builder.WithEndpoint(endpointName: "data-explorer", endpoint =>
{
endpoint.UriScheme = "http";
endpoint.TargetPort = 1234;
endpoint.Port = port;
});
}
}
7 changes: 6 additions & 1 deletion src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Aspire.Hosting.Azure.Cosmos;

namespace Aspire.Hosting;

Expand All @@ -28,12 +29,16 @@ public class AzureCosmosDBResource(string name, Action<AzureResourceInfrastructu
/// </summary>
public bool IsEmulator => this.IsContainer();

internal bool IsPreviewEmulator =>
this.TryGetContainerImageName(out var imageName) &&
imageName == $"{CosmosDBEmulatorContainerImageTags.Registry}/{CosmosDBEmulatorContainerImageTags.Image}:{CosmosDBEmulatorContainerImageTags.TagVNextPreview}";

/// <summary>
/// Gets the connection string template for the manifest for the Azure Cosmos DB resource.
/// </summary>
public ReferenceExpression ConnectionStringExpression =>
IsEmulator
? AzureCosmosDBEmulatorConnectionString.Create(EmulatorEndpoint)
? AzureCosmosDBEmulatorConnectionString.Create(EmulatorEndpoint, IsPreviewEmulator)
: ReferenceExpression.Create($"{ConnectionString}");
}

Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ internal static class CosmosDBEmulatorContainerImageTags

/// <remarks>latest</remarks>
public const string Tag = "latest";

/// <remarks>vnext-preview</remarks>
public const string TagVNextPreview = "vnext-preview";
}
1 change: 0 additions & 1 deletion src/Aspire.Hosting.Azure.CosmosDB/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Aspire.Hosting.AzureCosmosDBResource.ConnectionStringExpression.get -> Aspire.Ho
Aspire.Hosting.AzureCosmosDBResource.IsEmulator.get -> bool
Aspire.Hosting.AzureCosmosExtensions
override Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource.Annotations.get -> Aspire.Hosting.ApplicationModel.ResourceAnnotationCollection!
override Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource.Name.get -> string!
static Aspire.Hosting.AzureCosmosExtensions.AddAzureCosmosDB(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>!
static Aspire.Hosting.AzureCosmosExtensions.AddAzureCosmosDB(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, System.Action<Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>!, Aspire.Hosting.ResourceModuleConstruct!, Azure.Provisioning.CosmosDB.CosmosDBAccount!, System.Collections.Generic.IEnumerable<Azure.Provisioning.CosmosDB.CosmosDBSqlDatabase!>!>? configureResource) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>!
static Aspire.Hosting.AzureCosmosExtensions.AddDatabase(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>! builder, string! databaseName) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>!
Expand Down
2 changes: 2 additions & 0 deletions src/Aspire.Hosting.Azure.CosmosDB/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
*REMOVED*static Aspire.Hosting.AzureCosmosExtensions.AddAzureCosmosDB(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, System.Action<Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>!, Aspire.Hosting.ResourceModuleConstruct!, Azure.Provisioning.CosmosDB.CosmosDBAccount!, System.Collections.Generic.IEnumerable<Azure.Provisioning.CosmosDB.CosmosDBSqlDatabase!>!>? configureResource) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>!
*REMOVED*Aspire.Hosting.AzureCosmosDBResource.AzureCosmosDBResource(string! name, System.Action<Aspire.Hosting.ResourceModuleConstruct!>! configureConstruct) -> void
Aspire.Hosting.AzureCosmosDBResource.AzureCosmosDBResource(string! name, System.Action<Aspire.Hosting.Azure.AzureResourceInfrastructure!>! configureInfrastructure) -> void
static Aspire.Hosting.AzureCosmosExtensions.RunAsPreviewEmulator(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>! builder, System.Action<Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource!>!>? configureContainer = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.AzureCosmosDBResource!>!
static Aspire.Hosting.AzureCosmosExtensions.WithDataExplorer(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource!>! builder, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource!>!
static Aspire.Hosting.AzureCosmosExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource!>! builder, string? name = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource!>!
static Aspire.Hosting.AzureCosmosExtensions.WithPartitionCount(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource!>! builder, int count) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Azure.AzureCosmosDBEmulatorResource!>!
Loading
Loading