Skip to content

Commit

Permalink
Add support for CosmosDB vnext-preview emulator
Browse files Browse the repository at this point in the history
Added a new experimental API - RunAsPreviewEmulator. This will use the new Linux-based
emulator, which starts faster. And it also has support for a built-in Data Explorer
which can be enabled by calling WithDataExplorer on the emulator.

Fix dotnet#5163
  • Loading branch information
eerhardt committed Jan 8, 2025
1 parent aed9d43 commit 42cdc7b
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 13 deletions.
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
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;
}
54 changes: 51 additions & 3 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 @@ -210,6 +229,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 +253,28 @@ 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>
public static IResourceBuilder<AzureCosmosDBEmulatorResource> WithDataExplorer(this IResourceBuilder<AzureCosmosDBEmulatorResource> builder, int? port = null)
{
if (!builder.Resource.InnerResource.IsPreviewEmulator)
{
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!>!

0 comments on commit 42cdc7b

Please sign in to comment.