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

Enable devcontainers in repo. #6491

Merged
merged 16 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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": {},
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
"ghcr.io/devcontainers/features/docker-in-docker": {},
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
"ghcr.io/devcontainers/features/dotnet": {
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
"additionalVersions": [
"8.0.403"
]
}
},

"hostRequirements": {
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
"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": [
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
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"
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
},

// 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,
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
"dotnet.defaultSolution": "Aspire.sln"
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
}
}
},
"postCreateCommand": "dotnet restore",
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
"postStartCommand": "dotnet dev-certs https --trust"
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved

// 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);
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
});
})
.AddDatabase("db");

Expand Down
12 changes: 12 additions & 0 deletions src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
using System.Text;
using System.Text.Json;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Codespaces;
using Aspire.Hosting.Postgres;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Aspire.Hosting;

Expand Down Expand Up @@ -333,6 +335,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 codespaceOptions = context.ExecutionContext.ServiceProvider.GetRequiredService<IOptions<CodespacesOptions>>();
if (context.ExecutionContext.IsRunMode && codespaceOptions.Value.IsCodespace)
{
context.EnvironmentVariables["PGADMIN_CONFIG_PROXY_X_HOST_COUNT"] = "1";
context.EnvironmentVariables["PGADMIN_CONFIG_PROXY_X_PREFIX_COUNT"] = "1";
}
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
}

/// <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 valies.
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public class CodespacesOptions
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
{
/// <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>
[MemberNotNullWhenAttribute(true, nameof(IsCodespace))]
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
public string? PortForwardingDomain { get; set; } = null;
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved

/// <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>
[MemberNotNullWhenAttribute(true, nameof(IsCodespace))]
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
public string? CodespaceName { get; set; } = null;
mitchdenny marked this conversation as resolved.
Show resolved Hide resolved
}

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
8 changes: 8 additions & 0 deletions src/Aspire.Hosting/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ Aspire.Hosting.ApplicationModel.WaitAnnotation.WaitType.get -> Aspire.Hosting.Ap
Aspire.Hosting.ApplicationModel.WaitType
Aspire.Hosting.ApplicationModel.WaitType.WaitForCompletion = 1 -> Aspire.Hosting.ApplicationModel.WaitType
Aspire.Hosting.ApplicationModel.WaitType.WaitUntilHealthy = 0 -> Aspire.Hosting.ApplicationModel.WaitType
Aspire.Hosting.Codespaces.CodespacesOptions
Aspire.Hosting.Codespaces.CodespacesOptions.CodespaceName.get -> string?
Aspire.Hosting.Codespaces.CodespacesOptions.CodespaceName.set -> void
Aspire.Hosting.Codespaces.CodespacesOptions.CodespacesOptions() -> void
Aspire.Hosting.Codespaces.CodespacesOptions.IsCodespace.get -> bool
Aspire.Hosting.Codespaces.CodespacesOptions.IsCodespace.set -> void
Aspire.Hosting.Codespaces.CodespacesOptions.PortForwardingDomain.get -> string?
Aspire.Hosting.Codespaces.CodespacesOptions.PortForwardingDomain.set -> void
Aspire.Hosting.DistributedApplicationBuilder.AppHostPath.get -> string!
Aspire.Hosting.DistributedApplicationBuilder.Eventing.get -> Aspire.Hosting.Eventing.IDistributedApplicationEventing!
Aspire.Hosting.Eventing.DistributedApplicationEventing
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