diff --git a/src/Aspire.Hosting/Dcp/ContainerCreator.cs b/src/Aspire.Hosting/Dcp/ContainerCreator.cs index fe377d8bef6..2219babcb54 100644 --- a/src/Aspire.Hosting/Dcp/ContainerCreator.cs +++ b/src/Aspire.Hosting/Dcp/ContainerCreator.cs @@ -10,6 +10,7 @@ using System.Net.Sockets; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Dcp.Model; +using Aspire.Hosting.Publishing; using Aspire.Hosting.Utils; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -260,7 +261,7 @@ private async Task BuildAndCreateContainerAsync(RenderedModelResource var dcpContainer = cr.DcpResource; var modelContainer = cr.ModelResource; - await ApplyBuildArgumentsAsync(dcpContainer, cr.ModelResource, _executionContext.ServiceProvider, cToken).ConfigureAwait(false); + await ApplyBuildArgumentsAsync(dcpContainer, cr.ModelResource, _executionContext.ServiceProvider, logger, cToken).ConfigureAwait(false); var spec = dcpContainer.Spec; @@ -732,7 +733,7 @@ await modelResource.ProcessContainerRuntimeArgValues( return (runArgs, failedToApplyArgs); } - private static async Task ApplyBuildArgumentsAsync(Container dcpContainerResource, IResource modelContainerResource, IServiceProvider serviceProvider, CancellationToken cancellationToken) + private static async Task ApplyBuildArgumentsAsync(Container dcpContainerResource, IResource modelContainerResource, IServiceProvider serviceProvider, ILogger logger, CancellationToken cancellationToken) { if (modelContainerResource.Annotations.OfType().SingleOrDefault() is { } dockerfileBuildAnnotation) { @@ -783,6 +784,18 @@ private static async Task ApplyBuildArgumentsAsync(Container dcpContainerResourc Args = dcpBuildArgs, Secrets = dcpBuildSecrets }; + +#pragma warning disable ASPIREPIPELINES003 // ContainerBuildOptions APIs are experimental. + var buildOptionsContext = await modelContainerResource.ProcessContainerBuildOptionsCallbackAsync( + serviceProvider, + logger, + cancellationToken: cancellationToken).ConfigureAwait(false); + + if (buildOptionsContext.TargetPlatform is { } targetPlatform) + { + dcpContainerResource.Spec.Build.Platform = targetPlatform.ToRuntimePlatformString(); + } +#pragma warning restore ASPIREPIPELINES003 } } diff --git a/src/Aspire.Hosting/Dcp/Model/Container.cs b/src/Aspire.Hosting/Dcp/Model/Container.cs index 7a9279632f7..80be74cb549 100644 --- a/src/Aspire.Hosting/Dcp/Model/Container.cs +++ b/src/Aspire.Hosting/Dcp/Model/Container.cs @@ -126,6 +126,10 @@ internal sealed class BuildContext // Optional labels to apply to the built image [JsonPropertyName("labels")] public List? Labels { get; set; } + + // Optional target platform for the build (e.g. "linux/amd64") + [JsonPropertyName("platform")] + public string? Platform { get; set; } } internal sealed class BuildContextSecret diff --git a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs index 0dee41d64fe..230ecc826e7 100644 --- a/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs +++ b/tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs @@ -12,6 +12,7 @@ using Aspire.Dashboard.Model; using Aspire.Hosting.Dcp; using Aspire.Hosting.Dcp.Model; +using Aspire.Hosting.Publishing; using Aspire.Hosting.Tests.Utils; using k8s.Models; using Microsoft.AspNetCore.InternalTesting; @@ -51,6 +52,31 @@ public async Task ContainersArePassedOtelServiceName() Assert.Equal("CustomName", container.Metadata.Annotations["otel-service-name"]); } + [Fact] + public async Task DockerfileContainerBuildSpecIncludesPlatform() + { + using var tempDockerfileContext = await DockerfileUtils.CreateTemporaryDockerfileAsync(); + + var builder = DistributedApplication.CreateBuilder(); +#pragma warning disable ASPIREPIPELINES003 // ContainerBuildOptions APIs are experimental. + builder.AddDockerfile("mycontainer", tempDockerfileContext.ContextPath, tempDockerfileContext.DockerfilePath) + .WithContainerBuildOptions(ctx => ctx.TargetPlatform = ContainerTargetPlatform.LinuxArm64); +#pragma warning restore ASPIREPIPELINES003 + + var kubernetesService = new TestKubernetesService(); + + using var app = builder.Build(); + var distributedAppModel = app.Services.GetRequiredService(); + + var appExecutor = CreateAppExecutor(distributedAppModel, kubernetesService: kubernetesService); + + await appExecutor.RunApplicationAsync(); + + var container = Assert.Single(kubernetesService.CreatedResources.OfType()); + Assert.NotNull(container.Spec.Build); + Assert.Equal("linux/arm64", container.Spec.Build!.Platform); + } + [Fact] public async Task ResourceStarted_ProjectHasReplicas_EventRaisedOnce() {