Skip to content

Commit ea5c963

Browse files
committed
started to implement compose mapping - needs cli call to dotnet to get project container image meta etc
1 parent e9f92d5 commit ea5c963

File tree

17 files changed

+435
-126
lines changed

17 files changed

+435
-126
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
</ItemGroup>
1616

1717
<ItemGroup>
18-
<Compile Include="$(SharedDir)ContainerReferenceParser.cs" Link="Utils\ContainerReferenceParser.cs" />
18+
<Compile Include="$(SharedDir)ContainerReferenceParser.cs" Link="Shared\ContainerReferenceParser.cs" />
19+
<Compile Include="$(SharedDir)PublishingExtensions.cs" Link="Shared\PublishingExtensions.cs" />
20+
<Compile Include="$(SharedDir)PortAllocator.cs" Link="Shared\PortAllocator.cs" />
1921
</ItemGroup>
2022

2123
</Project>

src/Aspire.Hosting.Docker/DockerComposePublisherLoggerExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ internal static partial class DockerComposePublisherLoggerExtensions
2424

2525
[LoggerMessage(LogLevel.Information, "Successfully generated Compose output in '{OutputPath}'")]
2626
internal static partial void FinishGeneratingDockerCompose(this ILogger logger, string outputPath);
27+
28+
[LoggerMessage(LogLevel.Warning, "Failed to get container image for resource '{ResourceName}', it will be skipped in the output.")]
29+
internal static partial void FailedToGetContainerImage(this ILogger logger, string resourceName);
2730
}

src/Aspire.Hosting.Docker/DockerComposePublishingContext.cs

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Aspire.Hosting.ApplicationModel;
5+
using Aspire.Hosting.Docker.Resources;
6+
using Aspire.Hosting.Publishing;
57
using Microsoft.Extensions.Logging;
68

79
namespace Aspire.Hosting.Docker;
@@ -15,21 +17,120 @@ namespace Aspire.Hosting.Docker;
1517
/// <param name="cancellationToken">Cancellation token for this operation.</param>
1618
internal sealed class DockerComposePublishingContext(DistributedApplicationExecutionContext executionContext, string outputPath, ILogger logger, CancellationToken cancellationToken = default)
1719
{
18-
internal Task WriteModel(DistributedApplicationModel model)
20+
internal async Task WriteModel(DistributedApplicationModel model)
1921
{
20-
// TODO will be USING THESE
21-
_ = executionContext;
22-
_ = cancellationToken;
23-
2422
logger.StartGeneratingDockerCompose();
2523

2624
ArgumentNullException.ThrowIfNull(model);
2725
ArgumentNullException.ThrowIfNull(outputPath);
2826

27+
if (model.Resources.Count == 0)
28+
{
29+
logger.WriteMessage("No resources found in the model.");
30+
return;
31+
}
32+
33+
var outputFile = await WriteDockerComposeOutput(model).ConfigureAwait(false);
34+
35+
logger.FinishGeneratingDockerCompose(outputFile);
36+
}
37+
38+
private async Task<string> WriteDockerComposeOutput(DistributedApplicationModel model)
39+
{
40+
var composeFile = new ComposeFile();
41+
42+
foreach (var resource in model.Resources)
43+
{
44+
await HandleResourceAsync(resource, composeFile).ConfigureAwait(false);
45+
}
46+
47+
var composeOutput = composeFile.ToYamlString();
48+
var outputFile = Path.Combine(outputPath, "docker-compose.yaml");
2949
Directory.CreateDirectory(outputPath);
50+
await File.WriteAllTextAsync(outputFile, composeOutput, cancellationToken).ConfigureAwait(false);
51+
return outputFile;
52+
}
53+
54+
private async Task HandleResourceAsync(IResource resource, ComposeFile composeFile)
55+
{
56+
var composeService = resource switch
57+
{
58+
ContainerResource containerResource => await HandleContainerResource(containerResource).ConfigureAwait(false),
59+
ProjectResource projectResource => await HandleProjectResource(projectResource).ConfigureAwait(false),
60+
_ => null,
61+
};
62+
63+
if (composeService is null)
64+
{
65+
return;
66+
}
67+
68+
composeFile.AddService(composeService.Name!, composeService);
69+
}
70+
71+
private async Task<ComposeService?> HandleContainerResource(ContainerResource containerResource)
72+
{
73+
if (!containerResource.TryGetContainerImageName(out var containerImage))
74+
{
75+
logger.FailedToGetContainerImage(containerResource.Name);
76+
return null;
77+
}
78+
79+
var service = await AddResourceAsService(containerResource).ConfigureAwait(false);
80+
return service?.WithImage(containerImage);
81+
}
82+
83+
private async Task<ComposeService?> HandleProjectResource(ProjectResource projectResource)
84+
{
85+
var service = await AddResourceAsService(projectResource).ConfigureAwait(false);
86+
var projectMetadata = projectResource.GetProjectImageMetadata(logger); // TODO: Simply returning path to project for now, the extension will call dotnet to grab project properties for Container image details.
87+
88+
if (string.IsNullOrEmpty(projectMetadata))
89+
{
90+
logger.FailedToGetContainerImage(projectResource.Name);
91+
return null;
92+
}
93+
94+
return service?.WithImage(projectMetadata);
95+
}
96+
97+
private async Task<ComposeService?> AddResourceAsService(IResource resource)
98+
{
99+
var service = new ComposeService(resource.Name.ToLowerInvariant());
100+
101+
await PopulateServiceEnvironmentVariables(resource, service).ConfigureAwait(false);
102+
await PopulateServiceArguments(resource, service).ConfigureAwait(false);
103+
104+
return service;
105+
}
106+
107+
private async Task PopulateServiceEnvironmentVariables(IResource resource, ComposeService service)
108+
{
109+
var env = await executionContext.GetEnvironmentalVariablesForResource(resource).ConfigureAwait(false);
110+
111+
if (env.Count == 0)
112+
{
113+
return;
114+
}
115+
116+
foreach (var (key, value) in env)
117+
{
118+
service.AddEnvironmentVariable(key, value.Item2); // TODO: Support processing the unprocessed values
119+
}
120+
}
121+
122+
private async Task PopulateServiceArguments(IResource resource, ComposeService service)
123+
{
124+
var args = await executionContext.GetCommandLineArgumentsForResource(resource).ConfigureAwait(false);
30125

31-
logger.FinishGeneratingDockerCompose(outputPath);
126+
if (args.Count == 0)
127+
{
128+
return;
129+
}
32130

33-
return Task.CompletedTask;
131+
foreach (var value in args)
132+
{
133+
service.AddCommand(value.Item2); // TODO: Support processing the unprocessed values
134+
}
34135
}
35136
}

src/Aspire.Hosting.Docker/DockerComposeYamlKeys.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ internal static class DockerComposeYamlKeys
1212
internal const string Image = "image";
1313
internal const string Ports = "ports";
1414
internal const string Environment = "environment";
15+
internal const string Command = "command";
16+
internal const string ContainerName = "containerName";
1517
}

src/Aspire.Hosting.Docker/Resources/ComposeFile.cs

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ public sealed class ComposeFile : YamlObject
2121
/// </summary>
2222
public ComposeFile()
2323
{
24-
Add(DockerComposeYamlKeys.Services, new YamlObject());
25-
Add(DockerComposeYamlKeys.Networks, new YamlObject());
26-
Add(DockerComposeYamlKeys.Volumes, new YamlObject());
27-
Add(DockerComposeYamlKeys.Profiles, new YamlObject());
2824
}
2925

3026
/// <summary>
@@ -70,19 +66,8 @@ public ComposeFile()
7066
/// <returns>The current instance of <see cref="ComposeFile"/> for method chaining.</returns>
7167
public ComposeFile AddService(string serviceName, ComposeService service)
7268
{
73-
(Get(DockerComposeYamlKeys.Services) as YamlObject)?.Add(serviceName, service);
74-
return this;
75-
}
76-
77-
/// <summary>
78-
/// Replaces an existing service in the Compose file with a new service configuration.
79-
/// </summary>
80-
/// <param name="serviceName">The name of the service to be replaced.</param>
81-
/// <param name="service">The new service configuration to replace the existing one.</param>
82-
/// <returns>The updated <see cref="ComposeFile"/> instance with the replaced service.</returns>
83-
public ComposeFile ReplaceService(string serviceName, ComposeService service)
84-
{
85-
(Get(DockerComposeYamlKeys.Services) as YamlObject)?.Replace(serviceName, service);
69+
var services = GetOrCreate<YamlObject>(DockerComposeYamlKeys.Services);
70+
services.Add(serviceName, service);
8671
return this;
8772
}
8873

@@ -92,7 +77,8 @@ public ComposeFile ReplaceService(string serviceName, ComposeService service)
9277
/// <returns>Returns the updated ComposeFile instance with the new network added.</returns>
9378
public ComposeFile AddNetwork(string networkName, YamlObject network)
9479
{
95-
(Get(DockerComposeYamlKeys.Networks) as YamlObject)?.Add(networkName, network);
80+
var networks = GetOrCreate<YamlObject>(DockerComposeYamlKeys.Networks);
81+
networks.Add(networkName, network);
9682
return this;
9783
}
9884

@@ -104,7 +90,8 @@ public ComposeFile AddNetwork(string networkName, YamlObject network)
10490
/// <returns>The updated <see cref="ComposeFile"/> instance.</returns>
10591
public ComposeFile AddVolume(string volumeName, YamlObject volume)
10692
{
107-
(Get(DockerComposeYamlKeys.Volumes) as YamlObject)?.Add(volumeName, volume);
93+
var volumes = GetOrCreate<YamlObject>(DockerComposeYamlKeys.Volumes);
94+
volumes.Add(volumeName, volume);
10895
return this;
10996
}
11097

@@ -114,7 +101,8 @@ public ComposeFile AddVolume(string volumeName, YamlObject volume)
114101
/// <returns>The updated ComposeFile instance.</returns>
115102
public ComposeFile AddProfile(string profileName, YamlObject profile)
116103
{
117-
(Get(DockerComposeYamlKeys.Profiles) as YamlObject)?.Add(profileName, profile);
104+
var profiles = GetOrCreate<YamlObject>(DockerComposeYamlKeys.Profiles);
105+
profiles.Add(profileName, profile);
118106
return this;
119107
}
120108
}

src/Aspire.Hosting.Docker/Resources/ComposeService.cs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,22 @@ public sealed class ComposeService : YamlObject
1818
/// <summary>
1919
/// Represents a service definition in a Docker Compose YAML file.
2020
/// </summary>
21-
public ComposeService(string image)
21+
public ComposeService(string name)
2222
{
23-
Add(DockerComposeYamlKeys.Image, new YamlValue(image));
24-
Add(DockerComposeYamlKeys.Ports, new YamlArray());
25-
Add(DockerComposeYamlKeys.Environment, new YamlObject());
23+
Add(DockerComposeYamlKeys.ContainerName, new YamlValue(name));
2624
}
2725

26+
/// <summary>
27+
/// Gets the name of the Docker Compose service.
28+
/// </summary>
29+
/// <remarks>
30+
/// This property retrieves the container name specified in the Docker Compose configuration.
31+
/// If the name is not explicitly set, it returns an empty string.
32+
/// </remarks>
33+
public string? Name => Get(DockerComposeYamlKeys.ContainerName) is YamlValue name ?
34+
name.Value.ToString() :
35+
throw new DistributedApplicationException("Container name not set.");
36+
2837
/// <summary>
2938
/// Adds a port mapping to the service configuration in the Docker Compose YAML.
3039
/// </summary>
@@ -37,7 +46,8 @@ public ComposeService(string image)
3746
/// </returns>
3847
public ComposeService AddPort(string portMapping)
3948
{
40-
(Get(DockerComposeYamlKeys.Ports) as YamlArray)?.Add(new YamlValue(portMapping));
49+
var ports = GetOrCreate<YamlArray>(DockerComposeYamlKeys.Ports);
50+
ports.Add(new YamlValue(portMapping));
4151
return this;
4252
}
4353

@@ -49,7 +59,31 @@ public ComposeService AddPort(string portMapping)
4959
/// <returns>The current instance of <see cref="ComposeService"/> for method chaining.</returns>
5060
public ComposeService AddEnvironmentVariable(string key, string value)
5161
{
52-
(Get(DockerComposeYamlKeys.Environment) as YamlObject)?.Add(key, new YamlValue(value));
62+
var env = GetOrCreate<YamlObject>(DockerComposeYamlKeys.Environment);
63+
env.Add(key, new YamlValue(value));
64+
return this;
65+
}
66+
67+
/// <summary>
68+
/// Adds a command to the service configuration in a Docker Compose file.
69+
/// </summary>
70+
/// <param name="value">The command to add to the service configuration.</param>
71+
/// <returns>The updated <see cref="ComposeService"/> instance.</returns>
72+
public ComposeService AddCommand(string value)
73+
{
74+
var commands = GetOrCreate<YamlArray>(DockerComposeYamlKeys.Command);
75+
commands.Add(new YamlValue(value));
76+
return this;
77+
}
78+
79+
/// <summary>
80+
/// Sets the image property of the Docker Compose service.
81+
/// </summary>
82+
/// <param name="value">The name of the Docker image to set for the service.</param>
83+
/// <returns>The current instance of <see cref="ComposeService"/> to allow chaining of methods.</returns>
84+
public ComposeService WithImage(string value)
85+
{
86+
Replace(DockerComposeYamlKeys.Image, new YamlValue(value));
5387
return this;
5488
}
5589
}

src/Aspire.Hosting.Kubernetes.Helm/Aspire.Hosting.Kubernetes.Helm.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
</ItemGroup>
1616

1717
<ItemGroup>
18-
<Compile Include="$(SharedDir)ContainerReferenceParser.cs" Link="Utils\ContainerReferenceParser.cs" />
18+
<Compile Include="$(SharedDir)ContainerReferenceParser.cs" Link="Shared\ContainerReferenceParser.cs" />
19+
<Compile Include="$(SharedDir)PublishingExtensions.cs" Link="Shared\PublishingExtensions.cs" />
20+
<Compile Include="$(SharedDir)PortAllocator.cs" Link="Shared\PortAllocator.cs" />
1921
</ItemGroup>
2022

2123
</Project>

src/Aspire.Hosting.Kubernetes.Helm/Resources/HelmChartInfo.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ public HelmChartInfo AddDependency(string name, string version, string repo)
7878
depObj.Add(HelmYamlKeys.Name, new YamlValue(name));
7979
depObj.Add(HelmYamlKeys.Version, new YamlValue(version));
8080
depObj.Add("repository", new YamlValue(repo));
81-
(Get(HelmYamlKeys.Dependencies) as YamlArray)?.Add(depObj);
81+
var dependencies = GetOrCreate<YamlArray>(HelmYamlKeys.Dependencies);
82+
dependencies.Add(depObj);
8283
return this;
8384
}
8485
}

src/Aspire.Hosting.Kubernetes.Kustomize/Aspire.Hosting.Kubernetes.Kustomize.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
</ItemGroup>
1616

1717
<ItemGroup>
18-
<Compile Include="$(SharedDir)ContainerReferenceParser.cs" Link="Utils\ContainerReferenceParser.cs" />
18+
<Compile Include="$(SharedDir)ContainerReferenceParser.cs" Link="Shared\ContainerReferenceParser.cs" />
19+
<Compile Include="$(SharedDir)PublishingExtensions.cs" Link="Shared\PublishingExtensions.cs" />
20+
<Compile Include="$(SharedDir)PortAllocator.cs" Link="Shared\PortAllocator.cs" />
1921
</ItemGroup>
2022

2123
</Project>

src/Aspire.Hosting.Kubernetes.Kustomize/Resources/KustomizationFile.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ public KustomizationFile()
6262
/// <returns>The current instance of <see cref="KustomizationFile"/> to allow method chaining.</returns>
6363
public KustomizationFile AddResource(string resourcePath)
6464
{
65-
(Get(KustomizeYamlKeys.Resources) as YamlArray)?.Add(new YamlValue(resourcePath));
65+
var resources = GetOrCreate<YamlArray>(KustomizeYamlKeys.Resources);
66+
resources.Add(new YamlValue(resourcePath));
6667
return this;
6768
}
6869

@@ -74,7 +75,8 @@ public KustomizationFile AddResource(string resourcePath)
7475
/// <returns>The updated <see cref="KustomizationFile"/> instance.</returns>
7576
public KustomizationFile AddPatch(string patchPath)
7677
{
77-
(Get(KustomizeYamlKeys.PatchesStrategicMerge) as YamlArray)?.Add(new YamlValue(patchPath));
78+
var patches = GetOrCreate<YamlArray>(KustomizeYamlKeys.PatchesStrategicMerge);
79+
patches.Add(new YamlValue(patchPath));
7880
return this;
7981
}
8082

@@ -99,7 +101,8 @@ public KustomizationFile AddConfigMap(string name, Dictionary<string, string> da
99101
literalsArr.Add(new YamlValue($"{k}={v}"));
100102
}
101103
mapObj.Add("literals", literalsArr);
102-
(Get(KustomizeYamlKeys.ConfigMapGenerator) as YamlArray)?.Add(mapObj);
104+
var resources = GetOrCreate<YamlArray>(KustomizeYamlKeys.ConfigMapGenerator);
105+
resources.Add(mapObj);
103106
return this;
104107
}
105108
}

0 commit comments

Comments
 (0)