Skip to content
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
94 changes: 94 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "C# (.NET)",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/dotnet:1-9.0",
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/azure/azure-dev/azd:0": {},
"ghcr.io/devcontainers/features/docker-in-docker": {},
"ghcr.io/devcontainers/features/dotnet": {
"additionalVersions": [
"8.0.403"
]
}
},

"hostRequirements": {
"cpus": 8,
"memory": "32gb",
"storage": "64gb"
},

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
15887,
5180,
7024,
15551,
33803,
5350,
41567,
15306
],
"portsAttributes": {
"5180": {
"label": "WaitFor Playground: ApiService",
"protocol": "http"
},
"5350": {
"label": "Redis Playground: Api Service"
},
"7024": {
"label": "WaitFor Playground: Frontend",
"protocol": "https"
},
"15306": {
"label": "Redis Playground: App Host"
},
"15551": {
"label": "WaitFor Playground: PGAdmin",
"protocol": "http"
},
"15887": {
"label": "WaitFor Playground: AppHost",
"protocol": "https"
},
"33803": {
"label": "Redis Playground: Redis Commander"
},
"41567": {
"label": "Redis Playground: Redis Insight"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},

// Use 'postCreateCommand' to run commands after the container is created.
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csdevkit",
"ms-azuretools.vscode-bicep",
"ms-azuretools.azure-dev"
],
"settings": {
"remote.autoForwardPorts": false,
"dotnet.defaultSolution": "Aspire.sln"
}
}
},
"onCreateCommand": "dotnet restore",
"postStartCommand": "dotnet dev-certs https --trust"

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
4 changes: 2 additions & 2 deletions playground/Redis/Redis.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

var redis = builder.AddRedis("redis")
.WithDataVolume()
.WithRedisCommander()
.WithRedisInsight();
.WithRedisCommander(c => c.WithHostPort(33803))
.WithRedisInsight(c => c.WithHostPort(41567));

var garnet = builder.AddGarnet("garnet")
.WithDataVolume();
Expand Down
5 changes: 4 additions & 1 deletion playground/waitfor/WaitForSandbox.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
.WithPasswordAuthentication()
.RunAsContainer(c =>
{
c.WithPgAdmin();
c.WithPgAdmin(c =>
{
c.WithHostPort(15551);
});
})
.AddDatabase("db");

Expand Down
11 changes: 11 additions & 0 deletions src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Postgres;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Aspire.Hosting;
Expand Down Expand Up @@ -333,6 +334,16 @@ private static void SetPgAdminEnvironmentVariables(EnvironmentCallbackContext co
// You need to define the PGADMIN_DEFAULT_EMAIL and PGADMIN_DEFAULT_PASSWORD or PGADMIN_DEFAULT_PASSWORD_FILE environment variables.
context.EnvironmentVariables.Add("PGADMIN_DEFAULT_EMAIL", "[email protected]");
context.EnvironmentVariables.Add("PGADMIN_DEFAULT_PASSWORD", "admin");

// When running in the context of Codespaces we need to set some additional environment
// varialbes so that PGAdmin will trust the forwarded headers that Codespaces port
// forwarding will send.
var config = context.ExecutionContext.ServiceProvider.GetRequiredService<IConfiguration>();
if (context.ExecutionContext.IsRunMode && config.GetValue<bool>("CODESPACES", false))
{
context.EnvironmentVariables["PGADMIN_CONFIG_PROXY_X_HOST_COUNT"] = "1";
context.EnvironmentVariables["PGADMIN_CONFIG_PROXY_X_PREFIX_COUNT"] = "1";
}
}

/// <summary>
Expand Down
66 changes: 66 additions & 0 deletions src/Aspire.Hosting/Codespaces/CodespacesOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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 Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace Aspire.Hosting.Codespaces;

/// <summary>
/// GitHub Codespaces configuration values.
/// </summary>
internal class CodespacesOptions
{
/// <summary>
/// When set to true, the app host is running in a GitHub Codespace.
/// </summary>
/// <remarks>
/// Maps to the CODESPACE environment variable.
/// </remarks>
public bool IsCodespace { get; set; }

/// <summary>
/// When set it is the domain suffix used when port forwarding services hosted on the Codespace.
/// </summary>
/// <remarks>
/// Maps to the GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN environment variable.
/// </remarks>
[MemberNotNullWhen(true, nameof(IsCodespace))]
public string? PortForwardingDomain { get; set; }

/// <summary>
/// When set it is the name of the GitHub Codespace in which the app host is running.
/// </summary>
/// <remarks>
/// Maps to the CODESPACE_NAME environment variable.
/// </remarks>
[MemberNotNullWhen(true, nameof(IsCodespace))]
public string? CodespaceName { get; set; }
}

internal class ConfigureCodespacesOptions(IConfiguration configuration) : IConfigureOptions<CodespacesOptions>
{
private const string CodespacesEnvironmentVariable = "CODESPACES";
private const string CodespaceNameEnvironmentVariable = "CODESPACE_NAME";
private const string GitHubCodespacesPortForwardingDomain = "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN";

private string GetRequiredCodespacesConfigurationValue(string key)
{
ArgumentNullException.ThrowIfNullOrEmpty(key);
return configuration.GetValue<string>(key) ?? throw new DistributedApplicationException($"Codespaces was detected but {key} environment missing.");
}

public void Configure(CodespacesOptions options)
{
if (!configuration.GetValue<bool>(CodespacesEnvironmentVariable, false))
{
options.IsCodespace = false;
return;
}

options.IsCodespace = true;
options.PortForwardingDomain = GetRequiredCodespacesConfigurationValue(GitHubCodespacesPortForwardingDomain);
options.CodespaceName = GetRequiredCodespacesConfigurationValue(CodespaceNameEnvironmentVariable);
}
}
21 changes: 4 additions & 17 deletions src/Aspire.Hosting/Codespaces/CodespacesUrlRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,22 @@

using System.Collections.Immutable;
using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Aspire.Hosting.Codespaces;

internal sealed class CodespacesUrlRewriter(ILogger<CodespacesUrlRewriter> logger, IConfiguration configuration, ResourceNotificationService resourceNotificationService) : BackgroundService
internal sealed class CodespacesUrlRewriter(ILogger<CodespacesUrlRewriter> logger, IOptions<CodespacesOptions> options, ResourceNotificationService resourceNotificationService) : BackgroundService
{
private const string CodespacesEnvironmentVariable = "CODESPACES";
private const string CodespaceNameEnvironmentVariable = "CODESPACE_NAME";
private const string GitHubCodespacesPortForwardingDomain = "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN";

private string GetRequiredCodespacesConfigurationValue(string key)
{
ArgumentNullException.ThrowIfNullOrEmpty(key);
return configuration.GetValue<string>(key) ?? throw new DistributedApplicationException($"Codespaces was detected but {key} environment missing.");
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!configuration.GetValue<bool>(CodespacesEnvironmentVariable, false))
if (!options.Value.IsCodespace)
{
logger.LogTrace("Not running in Codespaces, skipping URL rewriting.");
return;
}

var gitHubCodespacesPortForwardingDomain = GetRequiredCodespacesConfigurationValue(GitHubCodespacesPortForwardingDomain);
var codespaceName = GetRequiredCodespacesConfigurationValue(CodespaceNameEnvironmentVariable);

do
{
try
Expand All @@ -58,7 +45,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
// which is typically ".app.github.dev". The VSCode instance is typically
// hosted at codespacename.github.dev whereas the forwarded ports
// would be at codespacename-port.app.github.dev.
Url = $"{uri.Scheme}://{codespaceName}-{uri.Port}.{gitHubCodespacesPortForwardingDomain}{uri.AbsolutePath}"
Url = $"{uri.Scheme}://{options.Value.CodespaceName}-{uri.Port}.{options.Value.PortForwardingDomain}{uri.AbsolutePath}"
};

remappedUrls.Add(originalUrlSnapshot, newUrlSnapshot);
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Hosting/DistributedApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
_innerBuilder.Services.AddSingleton<IKubernetesService, KubernetesService>();

// Codespaces
_innerBuilder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CodespacesOptions>, ConfigureCodespacesOptions>());
_innerBuilder.Services.AddHostedService<CodespacesUrlRewriter>();

Eventing.Subscribe<BeforeStartEvent>(BuiltInDistributedApplicationEventSubscriptionHandlers.InitializeDcpAnnotations);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public class CodespacesUrlRewriterTests(ITestOutputHelper testOutputHelper)
public async Task VerifyUrlsRewriterStopsWhenNotInCodespaces()
{
using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper);

// Explicitly disable codespace behavior for this test.
builder.Configuration["CODESPACES"] = "false";

builder.Services.AddLogging(logging =>
{
logging.AddFakeLogging();
Expand Down
Loading