From 6166e6ce68bf8254cbde41a049aefb596ba9e3eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:38:19 +0000 Subject: [PATCH 1/2] Initial plan From c77edffd9a7d624ce6e436f2814df0d9a13fbba7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:46:08 +0000 Subject: [PATCH 2/2] Add buildContext property to Platform manifest model Co-authored-by: lbussell <36081148+lbussell@users.noreply.github.com> --- src/ImageBuilder.Models/Manifest/Platform.cs | 5 +++ .../Models/PlatformSerializationTests.cs | 4 ++ src/ImageBuilder.Tests/PlatformInfoTests.cs | 39 +++++++++++++++++++ src/ImageBuilder/ViewModel/ModelExtensions.cs | 5 +++ src/ImageBuilder/ViewModel/PlatformInfo.cs | 4 +- 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/ImageBuilder.Models/Manifest/Platform.cs b/src/ImageBuilder.Models/Manifest/Platform.cs index 9e08067ce..2de981dab 100644 --- a/src/ImageBuilder.Models/Manifest/Platform.cs +++ b/src/ImageBuilder.Models/Manifest/Platform.cs @@ -28,6 +28,11 @@ public class Platform "to override variables defined in the Dockerfile.")] public IDictionary BuildArgs { get; set; } = new Dictionary(); + [Description( + "Relative path to the build context directory for the `docker build` command. " + + "If not specified, the directory containing the Dockerfile is used.")] + public string? BuildContext { get; set; } + [Description( "Relative path to the associated Dockerfile. This can be a file or a " + "directory. If it is a directory, the file name defaults to Dockerfile." diff --git a/src/ImageBuilder.Tests/Models/PlatformSerializationTests.cs b/src/ImageBuilder.Tests/Models/PlatformSerializationTests.cs index 138472f22..2f943293c 100644 --- a/src/ImageBuilder.Tests/Models/PlatformSerializationTests.cs +++ b/src/ImageBuilder.Tests/Models/PlatformSerializationTests.cs @@ -50,6 +50,7 @@ public void FullyPopulatedPlatform_Bidirectional() ["DOTNET_VERSION"] = "8.0.0", ["ASPNET_VERSION"] = "8.0.0" }, + BuildContext = "src/runtime/8.0/jammy", Dockerfile = "src/runtime/8.0/jammy/arm64v8/Dockerfile", DockerfileTemplate = "src/runtime/Dockerfile.linux.template", OS = OS.Linux, @@ -71,6 +72,7 @@ public void FullyPopulatedPlatform_Bidirectional() "DOTNET_VERSION": "8.0.0", "ASPNET_VERSION": "8.0.0" }, + "buildContext": "src/runtime/8.0/jammy", "dockerfile": "src/runtime/8.0/jammy/arm64v8/Dockerfile", "dockerfileTemplate": "src/runtime/Dockerfile.linux.template", "os": "Linux", @@ -95,6 +97,7 @@ public void FullyPopulatedPlatform_RoundTrip() { Architecture = Architecture.ARM, BuildArgs = new Dictionary { ["VERSION"] = "8.0" }, + BuildContext = "src", Dockerfile = "src/Dockerfile", DockerfileTemplate = "src/Dockerfile.template", OS = OS.Linux, @@ -266,6 +269,7 @@ private static void AssertPlatformsEqual(Platform expected, Platform actual) { Assert.Equal(expected.Architecture, actual.Architecture); Assert.Equal(expected.BuildArgs, actual.BuildArgs); + Assert.Equal(expected.BuildContext, actual.BuildContext); Assert.Equal(expected.Dockerfile, actual.Dockerfile); Assert.Equal(expected.DockerfileTemplate, actual.DockerfileTemplate); Assert.Equal(expected.OS, actual.OS); diff --git a/src/ImageBuilder.Tests/PlatformInfoTests.cs b/src/ImageBuilder.Tests/PlatformInfoTests.cs index 91cb9d9e9..a46816937 100644 --- a/src/ImageBuilder.Tests/PlatformInfoTests.cs +++ b/src/ImageBuilder.Tests/PlatformInfoTests.cs @@ -83,5 +83,44 @@ private void ValidateGetOSDisplayName(OS os, string osVersion, string expectedDi Assert.Equal(expectedDisplayName, platformInfo.GetOSDisplayName()); } + + [Fact] + public void BuildContextPath_DefaultsToDockerfileDirectory() + { + using TempFolderContext tempFolderContext = TestHelper.UseTempFolder(); + + string dockerfilePath = DockerfileHelper.CreateDockerfile("src/runtime/os", tempFolderContext); + + Platform platform = CreatePlatform(dockerfilePath, [ "test" ]); + + VariableHelper variableHelper = new(new Manifest(), Mock.Of(), null); + PlatformInfo platformInfo = PlatformInfo.Create( + platform, "", "test", variableHelper, tempFolderContext.Path); + + string expectedContextPath = PathHelper.NormalizePath( + Path.Combine(tempFolderContext.Path, "src/runtime/os")); + + Assert.Equal(expectedContextPath, platformInfo.BuildContextPath); + } + + [Fact] + public void BuildContextPath_UsesExplicitBuildContext() + { + using TempFolderContext tempFolderContext = TestHelper.UseTempFolder(); + + string dockerfilePath = DockerfileHelper.CreateDockerfile("src/runtime/os", tempFolderContext); + + Platform platform = CreatePlatform(dockerfilePath, [ "test" ]); + platform.BuildContext = "src"; + + VariableHelper variableHelper = new(new Manifest(), Mock.Of(), null); + PlatformInfo platformInfo = PlatformInfo.Create( + platform, "", "test", variableHelper, tempFolderContext.Path); + + string expectedContextPath = PathHelper.NormalizePath( + Path.Combine(tempFolderContext.Path, "src")); + + Assert.Equal(expectedContextPath, platformInfo.BuildContextPath); + } } } diff --git a/src/ImageBuilder/ViewModel/ModelExtensions.cs b/src/ImageBuilder/ViewModel/ModelExtensions.cs index f58e3585c..bb9d8a243 100644 --- a/src/ImageBuilder/ViewModel/ModelExtensions.cs +++ b/src/ImageBuilder/ViewModel/ModelExtensions.cs @@ -138,6 +138,11 @@ private static void ValidatePlatform(Platform platform, string manifestDirectory { ValidateFileReference(platform.ResolveDockerfilePath(manifestDirectory), manifestDirectory); ValidateFileReference(platform.DockerfileTemplate, manifestDirectory); + + if (platform.BuildContext is not null) + { + ValidatePathIsRelative(platform.BuildContext); + } } private static void ValidateUniqueTags(Repo repo) diff --git a/src/ImageBuilder/ViewModel/PlatformInfo.cs b/src/ImageBuilder/ViewModel/PlatformInfo.cs index 9afb1bd28..9d4cee4e4 100644 --- a/src/ImageBuilder/ViewModel/PlatformInfo.cs +++ b/src/ImageBuilder/ViewModel/PlatformInfo.cs @@ -59,7 +59,9 @@ private PlatformInfo(Platform model, string baseOsVersion, string fullRepoModelN string dockerfileWithBaseDir = Path.Combine(baseDirectory, model.ResolveDockerfilePath(baseDirectory)); DockerfilePath = PathHelper.NormalizePath(dockerfileWithBaseDir); - BuildContextPath = PathHelper.NormalizePath(Path.GetDirectoryName(dockerfileWithBaseDir)); + BuildContextPath = model.BuildContext is not null + ? PathHelper.NormalizePath(Path.Combine(baseDirectory, model.BuildContext)) + : PathHelper.NormalizePath(Path.GetDirectoryName(dockerfileWithBaseDir)); DockerfilePathRelativeToManifest = PathHelper.TrimPath(baseDirectory, DockerfilePath); if (model.DockerfileTemplate != null)