Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
12 changes: 7 additions & 5 deletions src/Aspire.Hosting.Docker/DockerComposeEnvironmentResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -515,14 +515,16 @@ private async Task PrepareAsync(PipelineStepContext context)
var envVar = entry.Value;
var defaultValue = envVar.DefaultValue;

if (defaultValue is null && envVar.Source is ParameterResource parameter)
// Only resolve from the parameter if no static default is already set;
// a caller that provides an explicit default intends to skip parameter resolution.
if (envVar.Source is ParameterResource parameter)
{
defaultValue = await parameter.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
defaultValue ??= await parameter.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
}

if (envVar.Source is ContainerImageReference cir)
else if (envVar.Source is IValueProvider vp)
{
defaultValue = await ((IValueProvider)cir).GetValueAsync(context.CancellationToken).ConfigureAwait(false);
// IValueProvider sources are always resolved dynamically — a static default is never used.
defaultValue = await vp.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
}

envFile.Add(entry.Key, defaultValue, envVar.Description, onlyIfMissing: false);
Expand Down
85 changes: 85 additions & 0 deletions tests/Aspire.Hosting.Docker.Tests/DockerComposePublisherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,91 @@ public void PrepareStep_OverwritesExistingEnvFileWithCustomEnvironmentName()
Assert.DoesNotContain("OLD_STAGING_KEY", envFileContent);
}

[Fact]
public async Task PrepareStep_ResolvesArbitraryIValueProviderSource()
{
using var tempDir = new TestTempDirectory();

var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, tempDir.Path, step: "prepare-docker-compose");
builder.Services.AddSingleton<IResourceContainerImageManager, MockImageBuilder>();

builder.AddDockerComposeEnvironment("docker-compose");

builder.AddContainer("testapp", "testimage")
.WithEnvironment(context =>
{
context.EnvironmentVariables["MY_VAR"] = new TestConditionProvider("resolved-value");
});

var app = builder.Build();
app.Run();

// The compose file uses the user-specified container env var name; the .env file uses the
// name derived from the provider's ValueExpression. Docker Compose interpolates between them.
var composeContent = await File.ReadAllTextAsync(Path.Combine(tempDir.Path, "docker-compose.yaml"));
Assert.Contains("MY_VAR: \"${TEST_CONDITION}\"", composeContent);

var envFileContent = await File.ReadAllTextAsync(Path.Combine(tempDir.Path, ".env.Production"));
Assert.Contains("TEST_CONDITION=resolved-value", envFileContent);
}

[Fact]
public async Task PrepareStep_SkipsParameterResolutionWhenStaticDefaultIsSet()
{
using var tempDir = new TestTempDirectory();

var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, tempDir.Path, step: "prepare-docker-compose");
builder.Services.AddSingleton<IResourceContainerImageManager, MockImageBuilder>();

var environment = builder.AddDockerComposeEnvironment("docker-compose");

var param = builder.AddParameter("myparam", "dynamic-value");

builder.AddContainer("testapp", "testimage")
.WithEnvironment("MY_PARAM", param);

// Set a static default before PrepareAsync runs; parameter resolution should be skipped.
environment.ConfigureEnvFile(vars =>
{
if (vars.TryGetValue("MYPARAM", out var envVar))
{
envVar.DefaultValue = "static-override";
}
});

var app = builder.Build();
app.Run();

var envFileContent = await File.ReadAllTextAsync(Path.Combine(tempDir.Path, ".env.Production"));
Assert.Contains("MYPARAM=static-override", envFileContent);
Assert.DoesNotContain("MYPARAM=dynamic-value", envFileContent);
}

[Fact]
public async Task PrepareStep_ResolvesContainerImageReferenceViaIValueProvider()
{
using var tempDir = new TestTempDirectory();

var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, tempDir.Path, step: "prepare-docker-compose");
builder.Services.AddSingleton<IResourceContainerImageManager, MockImageBuilder>();

builder.AddDockerComposeEnvironment("docker-compose");

// ProjectResource triggers AsContainerImagePlaceholder, which creates a CapturedEnvironmentVariable
// with Source=ContainerImageReference and DefaultValue="project1:latest".
// PrepareAsync should call GetValueAsync() via the IValueProvider branch; with no registry
// configured in the test, GetValueAsync() returns null and the entry is written empty —
// not "project1:latest". If the IValueProvider branch were skipped, the static default would appear.
builder.AddProject<TestProjectWithLaunchSettings>("project1");

var app = builder.Build();
app.Run();

var envFileContent = await File.ReadAllTextAsync(Path.Combine(tempDir.Path, ".env.Production"));
Assert.Contains("PROJECT1_IMAGE=", envFileContent);
Comment thread
sliekens marked this conversation as resolved.
Outdated
Assert.DoesNotContain("PROJECT1_IMAGE=project1:latest", envFileContent);
}

[Fact]
public async Task PublishAsync_BindMounts_ReplacedWithEnvironmentPlaceholders()
{
Expand Down
Loading