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
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,4 @@ private async Task ProcessArgumentsAsync(DockerComposeServiceResource serviceRes
}
}
}

public void AddEnv(string name, string description, string? defaultValue = null)
{
environment.CapturedEnvironmentVariables[name] = (description, defaultValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class DockerComposeEnvironmentResource : Resource, IComputeEnvironmentRes
/// Gets the collection of environment variables captured from the Docker Compose environment.
/// These will be populated into a top-level .env file adjacent to the Docker Compose file.
/// </summary>
internal Dictionary<string, (string Description, string? DefaultValue)> CapturedEnvironmentVariables { get; } = [];
internal Dictionary<string, (string? Description, string? DefaultValue, object? Source)> CapturedEnvironmentVariables { get; } = [];

/// <param name="name">The name of the Docker Compose environment.</param>
public DockerComposeEnvironmentResource(string name) : base(name)
Expand All @@ -62,4 +62,11 @@ private Task PublishAsync(PublishingContext context)

return dockerComposePublishingContext.WriteModelAsync(context.Model, this);
}

internal string AddEnvironmentVariable(string name, string? description = null, string? defaultValue = null, object? source = null)
{
CapturedEnvironmentVariables[name] = (description, defaultValue, source);

return $"${{{name}}}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private async Task WriteDockerComposeOutputAsync(DistributedApplicationModel mod

foreach (var entry in environment.CapturedEnvironmentVariables ?? [])
{
var (key, (description, defaultValue)) = entry;
var (key, (description, defaultValue, _)) = entry;
envFile.AddIfMissing(key, defaultValue, description);
}

Expand Down
54 changes: 54 additions & 0 deletions src/Aspire.Hosting.Docker/DockerComposeServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,58 @@ public static IResourceBuilder<T> PublishAsDockerComposeService<T>(this IResourc

return builder;
}

/// <summary>
Copy link
Member Author

@davidfowl davidfowl May 9, 2025

Choose a reason for hiding this comment

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

@prom3theu5 This is the pattern we want for helm values for the k8s manifest. I am thinking that before we GA either docker compose or the k8s resource types, the underlying model should be more typed so these aren't strings but something like YamlValue<string> (or maybe we go specific and say it's ComposeValue<T> and KubeValue<T>), that way it's type safe.

We need to:

  • Allow going from app model to cdk model
  • Preserve the association between the cdk value and the aspire app model somewhere (for later)

/// Creates a placeholder for an environment variable in the Docker Compose file.
/// </summary>
/// <param name="manifestExpressionProvider">The manifest expression provider.</param>
/// <param name="dockerComposeService">The Docker Compose service resource to associate the environment variable with.</param>
/// <returns>A string representing the environment variable placeholder in Docker Compose syntax (e.g., <c>${ENV_VAR}</c>).</returns>
public static string AsEnvironmentPlaceholder(this IManifestExpressionProvider manifestExpressionProvider, DockerComposeServiceResource dockerComposeService)
{
var env = manifestExpressionProvider.ValueExpression.Replace("{", "")
.Replace("}", "")
.Replace(".", "_")
.Replace("-", "_")
.ToUpperInvariant();

return dockerComposeService.Parent.AddEnvironmentVariable(
env,
source: manifestExpressionProvider
);
}

/// <summary>
/// Creates a Docker Compose environment variable placeholder for the specified <see cref="ParameterResource"/>.
/// </summary>
/// <param name="builder">The resource builder for the parameter resource.</param>
/// <param name="dockerComposeService">The Docker Compose service resource to associate the environment variable with.</param>
/// <returns>A string representing the environment variable placeholder in Docker Compose syntax (e.g., <c>${ENV_VAR}</c>).</returns>
public static string AsEnvironmentPlaceholder(this IResourceBuilder<ParameterResource> builder, DockerComposeServiceResource dockerComposeService)
{
return builder.Resource.AsEnvironmentPlaceholder(dockerComposeService);
}

/// <summary>
/// Creates a Docker Compose environment variable placeholder for this <see cref="ParameterResource"/>.
/// </summary>
/// <param name="parameter">The parameter resource for which to create the environment variable placeholder.</param>
/// <param name="dockerComposeService">The Docker Compose service resource to associate the environment variable with.</param>
/// <returns>A string representing the environment variable placeholder in Docker Compose syntax (e.g., <c>${ENV_VAR}</c>).</returns>
public static string AsEnvironmentPlaceholder(this ParameterResource parameter, DockerComposeServiceResource dockerComposeService)
{
// Placeholder for resolving the actual parameter value
// https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/#interpolation-syntax

var env = parameter.Name.ToUpperInvariant().Replace("-", "_");

// Treat secrets as environment variable placeholders as for now
// this doesn't handle generation of parameter values with defaults
return dockerComposeService.Parent.AddEnvironmentVariable(
env,
description: $"Parameter {parameter.Name}",
defaultValue: parameter.Secret || parameter.Default is null ? null : parameter.Value,
source: parameter
);
}
}
9 changes: 6 additions & 3 deletions src/Aspire.Hosting.Docker/DockerComposeServiceResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,12 @@ private bool TryGetContainerImageName(IResource resourceInstance, out string? co
{
var imageEnvName = $"{resourceInstance.Name.ToUpperInvariant().Replace("-", "_")}_IMAGE";

composeEnvironmentResource.CapturedEnvironmentVariables.Add(imageEnvName, ($"Container image name for {resourceInstance.Name}", $"{resourceInstance.Name}:latest"));

containerImageName = $"${{{imageEnvName}}}";
containerImageName = composeEnvironmentResource.AddEnvironmentVariable(
imageEnvName,
description: $"Container image name for {resourceInstance.Name}",
defaultValue: $"{resourceInstance.Name}:latest",
source: new ContainerImageReference(resourceInstance)
);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal static async Task<object> ProcessValueAsync(this DockerComposeServiceRe

if (value is ParameterResource param)
{
return AllocateParameter(param, context);
return param.AsEnvironmentPlaceholder(resource);
}

if (value is ConnectionStringReference cs)
Expand Down Expand Up @@ -82,7 +82,7 @@ internal static async Task<object> ProcessValueAsync(this DockerComposeServiceRe
// If we don't know how to process the value, we just return it as an external reference
if (value is IManifestExpressionProvider r)
{
return ResolveUnknownValue(r, resource);
return r.AsEnvironmentPlaceholder(resource);
}

return value; // todo: we need to never get here really...
Expand All @@ -107,42 +107,4 @@ string GetHostValue(string? prefix = null, string? suffix = null)
return $"{prefix}{mapping.Host}{suffix}";
}
}

private static string ResolveParameterValue(ParameterResource parameter, DockerComposeEnvironmentContext context)
{
// Placeholder for resolving the actual parameter value
// https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/#interpolation-syntax

// Treat secrets as environment variable placeholders as for now
// this doesn't handle generation of parameter values with defaults
var env = parameter.Name.ToUpperInvariant().Replace("-", "_");

context.AddEnv(env, $"Parameter {parameter.Name}",
parameter.Secret || parameter.Default is null ? null : parameter.Value);

return $"${{{env}}}";
}

private static string AllocateParameter(ParameterResource parameter, DockerComposeEnvironmentContext context)
{
return ResolveParameterValue(parameter, context);
}

private static string ResolveUnknownValue(IManifestExpressionProvider parameter, DockerComposeServiceResource serviceResource)
{
// Placeholder for resolving the actual parameter value
// https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/#interpolation-syntax

// Treat secrets as environment variable placeholders as for now
// this doesn't handle generation of parameter values with defaults
var env = parameter.ValueExpression.Replace("{", "")
.Replace("}", "")
.Replace(".", "_")
.Replace("-", "_")
.ToUpperInvariant();

serviceResource.EnvironmentVariables.Add(env, $"Unknown reference {parameter.ValueExpression}");

return $"${{{env}}}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ public async Task DockerComposeAppliesServiceCustomizations()

builder.Services.AddSingleton<IResourceContainerImageBuilder, MockImageBuilder>();

var containerNameParam = builder.AddParameter("param-1", "default-name", publishValueAsDefault: true);

builder.AddDockerComposeEnvironment("docker-compose")
.WithProperties(e => e.DefaultNetworkName = "default-network")
.ConfigureComposeFile(file =>
Expand All @@ -160,18 +162,24 @@ public async Task DockerComposeAppliesServiceCustomizations()
// Set a restart policy
composeService.Restart = "always";

composeService.ContainerName = containerNameParam.AsEnvironmentPlaceholder(serviceResource);

// Add a custom network
composeService.Networks.Add("custom-network");
});

var app = builder.Build();

app.Run();

// Assert
var composePath = Path.Combine(tempDir.Path, "docker-compose.yaml");
Assert.True(File.Exists(composePath));
var envPath = Path.Combine(tempDir.Path, ".env");
Assert.True(File.Exists(envPath));

await Verify(File.ReadAllText(composePath), "yaml")
.AppendContentAsFile(File.ReadAllText(envPath), "env")
.UseHelixAwareDirectory();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Parameter param-1
PARAM_1=default-name

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
services:
service:
image: "nginx:latest"
container_name: "${PARAM_1}"
environment:
ORIGINAL_ENV: "value"
CUSTOM_ENV: "custom-value"
Expand Down