aspire icon indicating copy to clipboard operation
aspire copied to clipboard

[Aspire.Microsoft.Azure.Cosmos] How to access the CosmosDB emulator data explorer?

Open hansmbakker opened this issue 1 year ago • 1 comments

The Aspire.Microsoft.Azure.Cosmos component provides the

cosmosdb.RunAsEmulator();

method. Aspire.NET does recognize the 8081 port in the container (see screenshot). However, without any additional settings the emulator will only be exposed via a TCP endpoint which is not enough to access the data explorer dashboard from the emulator.

How can I expose the data explorer inside the emulator container best, during development? Can you please add this to the documentation?

image

hansmbakker avatar Aug 01 '24 14:08 hansmbakker

A possible option I saw was doing the following:

var cosmos = builder.AddAzureCosmosDB("CosmosDb")
                                  .AddDatabase("mydatabase");

if (builder.Environment.IsDevelopment())
{
    cosmos.WithHttpsEndpoint(8081, 8081, "emulator-port")
          .RunAsEmulator();
}

However, doing the thing above results in 8081 being listed twice and it adds another emulator-port target port than I specified (see screenshot).

I would like to be confirmed that this is the correct approach, and have it in the documentation for reference.

image

hansmbakker avatar Aug 01 '24 14:08 hansmbakker

A possible option I saw was doing the following:

var cosmos = builder.AddAzureCosmosDB("CosmosDb") .AddDatabase("mydatabase");

if (builder.Environment.IsDevelopment()) { cosmos.WithHttpsEndpoint(8081, 8081, "emulator-port") .RunAsEmulator(); } ...

I did that but when I go to the endpoint in my browser it complains I need an authorization header. How? Can I disable that somewhere?

obiwanjacobi avatar Nov 27 '24 10:11 obiwanjacobi

@obiwanjacobi per the docs for the Linux based emulator (which is what Aspire runs):

The emulator is comprised of two components:

  • Data explorer - interactively explore the data in the emulator. By default this runs on port 1234
  • Azure Cosmos DB emulator - a local version of the Azure Cosmos DB database service. By default, this runs on port 8081.

The emulator gateway endpoint is typically available on port 8081 at the address http://localhost:8081/. To navigate to the data explorer, use the address http://localhost:1234/ in your web browser. It may take a few seconds for data explorer to be available. The gateway endpoint is typically available immediately.

So mapping 8081 exposes emulator, not the dashboard, which is why you get that error. You have to map 1234 for the dashboard, using an HTTP endpoint, not an HTTPS endpoint. However, that still doesn't work, and it turns out it's due to an issue with the Cosmos DB emulator, not with Aspire. See: https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/135

That problem has actually been fixed as of yesterday, however the version with the fix is tagged as vnext-preview. So you'll need to wait until that gets promoted to latest. Or in the meantime you can do this:

var cosmos = builder.AddAzureCosmosDB("cosmos")
    .WithHttpEndpoint(51234, 1234, "explorer-port")
    .WithExternalHttpEndpoints()
    .RunAsEmulator(cfgContainer =>
    {
        cfgContainer
        .WithImageRegistry("mcr.microsoft.com")
        .WithImage("cosmosdb/linux/azure-cosmos-emulator")
        .WithImageTag("vnext-preview");
    });

Don't get me started on the kilometers of rabbit hole I've crawled through today to figure this out and get it to work - but it works:

Image

matt-goldman avatar Dec 06 '24 08:12 matt-goldman

Slight tweak:

var cosmos = builder.AddAzureCosmosDB("cosmos")
    .RunAsEmulator(cfgContainer =>
    {
        cfgContainer
        .WithHttpEndpoint(targetPort: 1234, "explorer-port")
        .WithImageRegistry("mcr.microsoft.com")
        .WithImage("cosmosdb/linux/azure-cosmos-emulator")
        .WithImageTag("vnext-preview");
    });

davidfowl avatar Dec 06 '24 09:12 davidfowl

This is what I got (AppHost)

var cosmos = builder.AddAzureCosmosDB("myaccount");
var cosmosDb = cosmos.AddDatabase("mydatabase");
if (builder.Environment.IsDevelopment())
{
    cosmos.RunAsEmulator(config => config
        .WithHttpEndpoint(targetPort: 1234, name: "explorer-port")
        .WithImageRegistry("mcr.microsoft.com")
        .WithImage("cosmosdb/linux/azure-cosmos-emulator")
        .WithImageTag("vnext-preview")
        //.WithLifetime(ContainerLifetime.Persistent)
        );
}

The container running cosmosDB is marked as unhealthy.

This is showing in the dashboard for the resource details:

Image

I would expect to see port 1234 in there somewhere in the endpoints...?

(I commented out the WithLifetime call because at first it just hung on 'starting...')

obiwanjacobi avatar Dec 07 '24 09:12 obiwanjacobi

https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/networking-overview

davidfowl avatar Dec 07 '24 16:12 davidfowl

https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/networking-overview

Ah - so there's a proxy in front of it. Right, thanks.

But still, why does my container read unhealthy when I configure this? Any suggestions where I could start to look?

obiwanjacobi avatar Dec 08 '24 08:12 obiwanjacobi

EDIT: I think this is probably wrong. It's more likely related to the SSL issue - I've spent a little bit of time on it but need to look into it more.

Original:

I'm still trying to get my head around why exactly this happens and what the fix might be, but the problem seems to be that when using a custom container image it doesn't publish the connection string, and Aspire is waiting for the publish event to mark it as healthy. Or more specifically, waiting for a CosmosClient to be created, which waits for the connection string publish event. See: https://github.com/dotnet/aspire/blob/28ded72163532b892e809a1e62e1f93bcea88b20/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs#L127

Without debugging Apsire locally, I can't determine whether the issue is caused by a difference in the container image, or a difference in the way Aspire instantiates it when you provide a custom container spec. See: https://github.com/dotnet/aspire/blob/28ded72163532b892e809a1e62e1f93bcea88b20/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs#L147

I can't see anything here that would cause it inherently, but it's possible that because this code is adding it as AzureCosmosDBEmulatorResource which doesn't implement IResourceWithConnectionString (required to publish the event, see: https://github.com/dotnet/aspire/blob/28ded72163532b892e809a1e62e1f93bcea88b20/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs#L1285).

But I'm a little out of my depth here and mostly speculating. Either way I'm fairly sure that when using this image the connection string is either not published or the resource is not registered as an implementation of IResourceWithConnectionString and therefore even if it is published the event is not raised.

Maybe @davidfowl can help?

Obviously aside from the health check, we need the connection string and/or client to use the resource, but there's definitely a workaround for that. I don't have time to figure that out right now but I might get some time on the weekend to poke around at this a bit more.

matt-goldman avatar Dec 08 '24 22:12 matt-goldman

Got it working for me with an update to set https on container startup as detailed in notes on the docs for the linux emulator. Both explorer, health check, persistent lifetime and also client side with connection string resolving work as expected. The explorer however gives unsafe warning. https://learn.microsoft.com/en-us/azure/cosmos-db/emulator-linux#running

Would be great for others to verify. Should any defaults change to use https or is this only part of the preview emulator container?

Example code:

var cosmos = builder.AddAzureCosmosDB("cosmos")
    .RunAsEmulator(cfgContainer =>
    {
        cfgContainer
            .WithHttpsEndpoint(targetPort: 1234, name: "explorer-port")
            .WithImageRegistry("mcr.microsoft.com")
            .WithImage("cosmosdb/linux/azure-cosmos-emulator")
            .WithImageTag("vnext-preview")
            .WithLifetime(ContainerLifetime.Persistent)
            .WithArgs("--protocol", "https");
    });

trulsmp avatar Dec 09 '24 18:12 trulsmp

@trulsmp working for me too!

matt-goldman avatar Dec 09 '24 18:12 matt-goldman

Ok so the dashboard works and the emu reports healthy. But I can't get any queries to work with the SDK. I spent a lot of time on this today and had to switch to an instance on Azure to get unblocked with my day job. Obviously not an Aspire issue, but @trulsmp just wondering whether you had this issue too? If it's not just me I'll try to find some time to create a small repro and report it on the emulator repo.

matt-goldman avatar Dec 10 '24 06:12 matt-goldman

@matt-goldman I got queries to work and tested with item create and retrieval. Using the client integration in Azure Function isolated project orchestrated by Aspire.

I can share some example codebits, nothing special here just following standard setup.

Program.cs in Functions project using the client from Aspire. Using same name for the resource as in AppHost. Package used is Aspire.Microsoft.Azure.Cosmos according to docs: https://learn.microsoft.com/en-us/dotnet/aspire/database/azure-cosmos-db-integration?tabs=dotnet-cli#get-started

using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.AddAzureCosmosClient("cosmos");

builder.ConfigureFunctionsWebApplication();

var host = builder.Build();

await host.RunAsync();

Using the client in a function. Checking and possibly creating database/container if needed. Note that "id" needs to match case in cosmos db.

record User(string userId, string id, string name, string address);

public class HttpTriggers(CosmosClient client)
{
    [Function("CreateItem")]
    public async Task<IActionResult> CreateItem([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req)
    {
        await client.CreateDatabaseIfNotExistsAsync("cosmosdb");
        var db = client.GetDatabase("cosmosdb");
        await db.CreateContainerIfNotExistsAsync("user", "/userId");
        var container = db.GetContainer("user");

        var partitionKey = Guid.CreateVersion7().ToString();

        await container.CreateItemAsync(
            item: new User(partitionKey, "profile", "John Doe", "123 Main St"),
            partitionKey: new PartitionKey(partitionKey)
        );

        return new OkResult();
    }

trulsmp avatar Dec 10 '24 11:12 trulsmp

@trulsmp I can insert (or upsert) items, but I can't query anything. I think it's an SDK issue though, as I can via the explorer (with vnext-preview). But the SDK works with Azure Cosmos. So I'm not sure. This is all in a client project but if I get time over the weekend I'll see if I can replicate it in a minimal repro.

matt-goldman avatar Dec 10 '24 22:12 matt-goldman

You guys got the DataExplorer to work (port 1234)?

I use the same code as posted above earlier with only setting the isProxied to false. This made the port appear in docker and the dashboard. But going there is a no-show (empty response).

var cosmos = builder.AddAzureCosmosDB("cosmos")
    .RunAsEmulator(cfgContainer =>
    {
        cfgContainer
            .WithHttpsEndpoint(targetPort: 1234, name: "explorer-port", isProxied: false)
            .WithImageRegistry("mcr.microsoft.com")
            .WithImage("cosmosdb/linux/azure-cosmos-emulator")
            .WithImageTag("vnext-preview")
            .WithArgs("--protocol", "https");
            .WithLifetime(ContainerLifetime.Persistent)
    });

Upsert and reading of a document works fine and the instance is also healthy now (not sure what I did wrong before).

obiwanjacobi avatar Dec 11 '24 14:12 obiwanjacobi

I just noticed in the environment variables of the cosmos container that the protocol is set a weird value.

EXPLORER_PORT=1234
EXPLORER_PROTOCOL=PROTOCOL

Shouldn't that be http or https?

obiwanjacobi avatar Dec 21 '24 11:12 obiwanjacobi

@davidortinau FYI

matt-goldman avatar Dec 23 '24 10:12 matt-goldman

Adding .WithArgs("--explorer-protocol", "http") seem to have fixed it for me. The EXPLORER_PROTOCOL environment variable still has that weird PROTOCOL value - but the data explorer works now.

(Thanks to Egil Hansen on the Orleans Discord group.)

obiwanjacobi avatar Jan 04 '25 18:01 obiwanjacobi