Skip to content

Commit e0b0c99

Browse files
authored
Added a test for docker compose file generation (#8049)
1 parent 85c65b7 commit e0b0c99

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed

Aspire.sln

+15
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Kubernetes",
661661
EndProject
662662
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Docker", "src\Aspire.Hosting.Docker\Aspire.Hosting.Docker.csproj", "{3AE2ED5B-4EC7-4E6D-A61D-C68B837E5FA7}"
663663
EndProject
664+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Docker.Tests", "tests\Aspire.Hosting.Docker.Tests\Aspire.Hosting.Docker.Tests.csproj", "{43B560D6-F158-4A4C-8E43-981056EB9038}"
665+
EndProject
664666
Global
665667
GlobalSection(SolutionConfigurationPlatforms) = preSolution
666668
Debug|Any CPU = Debug|Any CPU
@@ -3863,6 +3865,18 @@ Global
38633865
{3AE2ED5B-4EC7-4E6D-A61D-C68B837E5FA7}.Release|x64.Build.0 = Release|Any CPU
38643866
{3AE2ED5B-4EC7-4E6D-A61D-C68B837E5FA7}.Release|x86.ActiveCfg = Release|Any CPU
38653867
{3AE2ED5B-4EC7-4E6D-A61D-C68B837E5FA7}.Release|x86.Build.0 = Release|Any CPU
3868+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3869+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Debug|Any CPU.Build.0 = Debug|Any CPU
3870+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Debug|x64.ActiveCfg = Debug|Any CPU
3871+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Debug|x64.Build.0 = Debug|Any CPU
3872+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Debug|x86.ActiveCfg = Debug|Any CPU
3873+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Debug|x86.Build.0 = Debug|Any CPU
3874+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Release|Any CPU.ActiveCfg = Release|Any CPU
3875+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Release|Any CPU.Build.0 = Release|Any CPU
3876+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Release|x64.ActiveCfg = Release|Any CPU
3877+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Release|x64.Build.0 = Release|Any CPU
3878+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Release|x86.ActiveCfg = Release|Any CPU
3879+
{43B560D6-F158-4A4C-8E43-981056EB9038}.Release|x86.Build.0 = Release|Any CPU
38663880
EndGlobalSection
38673881
GlobalSection(SolutionProperties) = preSolution
38683882
HideSolutionNode = FALSE
@@ -4180,6 +4194,7 @@ Global
41804194
{9A3D5C03-07EF-40DB-A56A-6AA5011E40C2} = {63110EF4-353F-DC1B-8101-C167B94FDED3}
41814195
{BF143BF7-7F0D-4247-BA74-D9583D5BD2F6} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47}
41824196
{3AE2ED5B-4EC7-4E6D-A61D-C68B837E5FA7} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47}
4197+
{43B560D6-F158-4A4C-8E43-981056EB9038} = {830A89EC-4029-4753-B25A-068BAE37DEC7}
41834198
EndGlobalSection
41844199
GlobalSection(ExtensibilityGlobals) = postSolution
41854200
SolutionGuid = {47DCFECF-5631-4BDE-A1EC-BE41E90F60C4}

src/Aspire.Hosting.Docker/Aspire.Hosting.Docker.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,8 @@
1919
<ProjectReference Include="..\Aspire.Hosting\Aspire.Hosting.csproj" />
2020
</ItemGroup>
2121

22+
<ItemGroup>
23+
<InternalsVisibleTo Include="Aspire.Hosting.Docker.Tests" />
24+
</ItemGroup>
25+
2226
</Project>

src/Aspire.Hosting.Docker/DockerComposePublishingContext.cs

+6
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ private async Task WriteDockerComposeOutput(DistributedApplicationModel model)
102102
Directory.CreateDirectory(publisherOptions.OutputPath!);
103103
await File.WriteAllTextAsync(outputFile, composeOutput, cancellationToken).ConfigureAwait(false);
104104

105+
if (_env.Count == 0)
106+
{
107+
// No environment variables to write, so we can skip creating the .env file
108+
return;
109+
}
110+
105111
// Write a .env file with the environment variable names
106112
// that are used in the compose file
107113
var envFile = Path.Combine(publisherOptions.OutputPath!, ".env");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
5+
<NoWarn>
6+
$(NoWarn);
7+
ASPIREHOSTINGPYTHON001;
8+
</NoWarn>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\src\Aspire.Hosting.AppHost\Aspire.Hosting.AppHost.csproj" />
13+
<ProjectReference Include="..\..\src\Aspire.Hosting.Docker\Aspire.Hosting.Docker.csproj" />
14+
</ItemGroup>
15+
16+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Hosting.ApplicationModel;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Logging.Abstractions;
7+
using Microsoft.Extensions.Options;
8+
using Xunit;
9+
10+
namespace Aspire.Hosting.Docker.Tests;
11+
12+
public class DockerComposePublisherTests
13+
{
14+
[Fact]
15+
public async Task PublishAsync_GeneratesValidDockerComposeFile()
16+
{
17+
using var tempDir = new TempDirectory();
18+
// Arrange
19+
var options = new OptionsMonitor(new DockerComposePublisherOptions { OutputPath = tempDir.Path });
20+
var builder = DistributedApplication.CreateBuilder();
21+
22+
var param0 = builder.AddParameter("param0");
23+
var param1 = builder.AddParameter("param1", secret: true);
24+
var param2 = builder.AddParameter("param2", "default", publishValueAsDefault: true);
25+
var cs = builder.AddConnectionString("cs", ReferenceExpression.Create($"Url={param0}, Secret={param1}"));
26+
27+
// Add a container to the application
28+
var api = builder.AddContainer("myapp", "mcr.microsoft.com/dotnet/aspnet:8.0")
29+
.WithEnvironment("ASPNETCORE_ENVIRONMENT", "Development")
30+
.WithHttpEndpoint(env: "PORT")
31+
.WithEnvironment("param0", param0)
32+
.WithEnvironment("param1", param1)
33+
.WithEnvironment("param2", param2)
34+
.WithReference(cs)
35+
.WithArgs("--cs", cs.Resource);
36+
37+
builder.AddProject<TestProject>("project1", launchProfileName: null)
38+
.WithReference(api.GetEndpoint("http"));
39+
40+
var app = builder.Build();
41+
42+
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
43+
44+
var publisher = new DockerComposePublisher("test", options,
45+
NullLogger<DockerComposePublisher>.Instance,
46+
new DistributedApplicationExecutionContext(DistributedApplicationOperation.Publish));
47+
48+
// Act
49+
await publisher.PublishAsync(model, default);
50+
51+
// Assert
52+
var composePath = Path.Combine(tempDir.Path, "docker-compose.yaml");
53+
var envPath = Path.Combine(tempDir.Path, ".env");
54+
Assert.True(File.Exists(composePath));
55+
Assert.True(File.Exists(envPath));
56+
57+
var content = await File.ReadAllTextAsync(composePath);
58+
var envContent = await File.ReadAllTextAsync(envPath);
59+
60+
Assert.Equal(
61+
"""
62+
services:
63+
myapp:
64+
image: "mcr.microsoft.com/dotnet/aspnet:8.0"
65+
command:
66+
- "--cs"
67+
- "Url=${PARAM0}, Secret=${PARAM1}"
68+
environment:
69+
ASPNETCORE_ENVIRONMENT: "Development"
70+
PORT: "8001"
71+
param0: "${PARAM0}"
72+
param1: "${PARAM1}"
73+
param2: "${PARAM2}"
74+
ConnectionStrings__cs: "Url=${PARAM0}, Secret=${PARAM1}"
75+
ports:
76+
- "8001:8000"
77+
networks:
78+
- "aspire"
79+
project1:
80+
image: "${PROJECT1_IMAGE}"
81+
environment:
82+
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
83+
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
84+
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
85+
services__myapp__http__0: "http://myapp:8000"
86+
networks:
87+
- "aspire"
88+
networks:
89+
aspire:
90+
driver: "bridge"
91+
92+
""",
93+
content, ignoreAllWhiteSpace: true, ignoreLineEndingDifferences: true);
94+
95+
Assert.Equal(
96+
"""
97+
# Parameter param0
98+
PARAM0=
99+
100+
# Parameter param1
101+
PARAM1=
102+
103+
# Parameter param2
104+
PARAM2=default
105+
106+
# Container image name for project1
107+
PROJECT1_IMAGE=project1:latest
108+
109+
110+
""",
111+
envContent, ignoreAllWhiteSpace: true, ignoreLineEndingDifferences: true);
112+
}
113+
114+
private sealed class OptionsMonitor(DockerComposePublisherOptions options) : IOptionsMonitor<DockerComposePublisherOptions>
115+
{
116+
public DockerComposePublisherOptions Get(string? name) => options;
117+
118+
public IDisposable OnChange(Action<DockerComposePublisherOptions, string> listener) => null!;
119+
120+
public DockerComposePublisherOptions CurrentValue => options;
121+
}
122+
123+
private sealed class TempDirectory : IDisposable
124+
{
125+
public TempDirectory()
126+
{
127+
Path = Directory.CreateTempSubdirectory(".aspire-compose").FullName;
128+
}
129+
130+
public string Path { get; }
131+
public void Dispose()
132+
{
133+
if (File.Exists(Path))
134+
{
135+
File.Delete(Path);
136+
}
137+
}
138+
}
139+
140+
private sealed class TestProject : IProjectMetadata
141+
{
142+
public string ProjectPath => "another-path";
143+
144+
public LaunchSettings? LaunchSettings { get; set; }
145+
}
146+
}

0 commit comments

Comments
 (0)