diff --git a/tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs b/tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs index 4544de739c..8b92011e47 100644 --- a/tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs +++ b/tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs @@ -55,15 +55,6 @@ public static IEnumerable GetImageData() [MemberData(nameof(GetImageData))] public async void VerifyBlazorWasmScenario(ProductImageData imageData) { - // Test will fail on main branch since `dotnet workload install` does not work with an empty NuGet config. - // Skip test for now, re-enable when https://github.com/dotnet/dotnet-docker/issues/5787 is closed. - if (!Config.IsNightlyRepo) - { - return; - } - - bool isAlpine = imageData.OS.StartsWith(OS.Alpine); - bool useWasmTools = true; // `wasm-tools` workload does not work on .NET 6 with CBL Mariner 2.0. @@ -80,13 +71,13 @@ public async void VerifyBlazorWasmScenario(ProductImageData imageData) } // `wasm-tools` is not supported on Alpine for .NET < 9 due to https://github.com/dotnet/sdk/issues/32327 - if (isAlpine && (imageData.Version.Major == 6 || imageData.Version.Major == 8)) + if (imageData.OS.StartsWith(OS.Alpine) && (imageData.Version.Major == 6 || imageData.Version.Major == 8)) { useWasmTools = false; } - // using BlazorWasmScenario testScenario = new(imageData, DockerHelper, OutputHelper, useWasmTools); - // await testScenario.ExecuteAsync(); + using BlazorWasmScenario testScenario = new(imageData, DockerHelper, OutputHelper, useWasmTools); + await testScenario.ExecuteAsync(); } [LinuxImageTheory] diff --git a/tests/Microsoft.DotNet.Docker.Tests/TestAppArtifacts/Dockerfile.blazorwasm.linux b/tests/Microsoft.DotNet.Docker.Tests/TestAppArtifacts/Dockerfile.blazorwasm.linux deleted file mode 100644 index c9792aedd2..0000000000 --- a/tests/Microsoft.DotNet.Docker.Tests/TestAppArtifacts/Dockerfile.blazorwasm.linux +++ /dev/null @@ -1,43 +0,0 @@ -ARG sdk_image -ARG use_wasm_tools - - -FROM $sdk_image AS pre_build_wasm_tools_false -ARG port -EXPOSE $port -WORKDIR /source -COPY NuGet.config . - - -FROM pre_build_wasm_tools_false AS pre_build_wasm_tools_true -WORKDIR /source -COPY NuGet.config . -RUN dotnet workload install --configfile NuGet.config --skip-manifest-update wasm-tools \ - && . /etc/os-release \ - && case $ID in \ - alpine) apk add --no-cache python3 ;; \ - debian | ubuntu) apt-get update \ - && apt-get install -y --no-install-recommends python3 \ - && rm -rf /var/lib/apt/lists/* ;; \ - mariner | azurelinux) tdnf install -y python3 \ - && tdnf clean all ;; \ - esac - - -FROM pre_build_wasm_tools_${use_wasm_tools} AS build -WORKDIR /source/app -COPY app/*.csproj . -RUN dotnet restore -COPY app/ . -RUN dotnet build --no-restore - - -FROM build AS publish_wasmtools_true -RUN dotnet publish -r browser-wasm -c Release --self-contained true -o out - - -FROM build AS publish_wasmtools_false -RUN dotnet publish --no-restore -c Release -o out - - -FROM publish_wasmtools_${use_wasm_tools} AS final diff --git a/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/BlazorWasmScenario.cs b/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/BlazorWasmScenario.cs index 8332d50d4b..091b270b11 100644 --- a/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/BlazorWasmScenario.cs +++ b/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/BlazorWasmScenario.cs @@ -4,30 +4,19 @@ namespace Microsoft.DotNet.Docker.Tests; -// public class BlazorWasmScenario( -// ProductImageData imageData, -// DockerHelper dockerHelper, -// ITestOutputHelper outputHelper, -// bool useWasmTools) -// : WebScenario(imageData, dockerHelper, outputHelper) -// { -// protected override string Dockerfile { get; } = $"Dockerfile.blazorwasm.{OSDockerfileSuffix}"; -// protected override string SampleName { get; } = "blazorwasm"; +public class BlazorWasmScenario( + ProductImageData imageData, + DockerHelper dockerHelper, + ITestOutputHelper outputHelper, + bool useWasmTools) + : WebScenario(imageData, dockerHelper, outputHelper) +{ + protected override TestDockerfile Dockerfile { get; } = TestDockerfileBuilder.GetBlazorWasmDockerfile(useWasmTools); + protected override string SampleName { get; } = "blazorwasm"; + protected override bool OutputIsStatic { get; } = true; -// // Currently, only some platforms support the wasm-tools workload. -// // In the case that wasm-tools isn't supported, even though blazorwasm's publish output isn't framework dependent, -// // running the standard publish in the fx_dependent target gives us the correct static site output. -// protected override string BuildStageTarget { get; } = "final"; -// protected override string? TestStageTarget { get; } = null; -// protected override string[] CustomDockerBuildArgs { get; } = -// [ $"use_wasm_tools={useWasmTools.ToString().ToLowerInvariant()}" ]; - -// // Known issue: Blazor ignores the ASPNETCORE_HTTP_PORTS environment variable that we set in runtime-deps. -// // We need to manually override it with ASPNETCORE_URLS. -// // https://github.com/dotnet/aspnetcore/issues/52494 -// protected override int? PortOverride { get; } = 8080; - -// // BlazorWASM publish output is a static site, so we don't need to run the app to verify it. -// // Endpoint access will be verified by `dotnet run` in the SDK image. -// protected override string[] AppStageTargets { get; } = []; -// } + // Known issue: Blazor ignores the ASPNETCORE_HTTP_PORTS environment variable that we set in runtime-deps. + // We need to manually override it with ASPNETCORE_URLS. + // https://github.com/dotnet/aspnetcore/issues/52494 + protected override int? PortOverride { get; } = 8080; +} diff --git a/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/ProjectTemplateTestScenario.cs b/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/ProjectTemplateTestScenario.cs index f10c5988b5..511bb0dd5d 100644 --- a/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/ProjectTemplateTestScenario.cs +++ b/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/ProjectTemplateTestScenario.cs @@ -28,6 +28,7 @@ public abstract class ProjectTemplateTestScenario : ITestScenario, IDisposable protected virtual bool NonRootUserSupported => _nonRootUserSupported; protected virtual bool InjectCustomTestCode { get; } = false; + protected virtual bool OutputIsStatic { get; } = false; protected virtual string[] CustomDockerBuildArgs { get; } = []; protected abstract string SampleName { get; } @@ -52,13 +53,7 @@ protected string Build(string stageTarget, string[]? customBuildArgs) { const string DockerfileName = "Dockerfile"; string dockerfilePath = Path.Combine(DockerHelper.TestArtifactsDir, DockerfileName); - string dockerfileContent = Dockerfile.Content; - OutputHelper.WriteLine( - $""" - Generated Dockerfile content: - {dockerfileContent} - """); - File.WriteAllText(dockerfilePath, dockerfileContent); + File.WriteAllText(dockerfilePath, Dockerfile.Content); string tag = ImageData.GetIdentifier(stageTarget); @@ -151,10 +146,14 @@ public async Task ExecuteAsync() string tag = Build(TestDockerfile.AppStageName, customBuildArgs); tags.Add(tag); - await RunAsync(tag, AdminUser); - if (NonRootUserSupported) + // Don't run the app if the build output is not executable + if (!OutputIsStatic) { - await RunAsync(tag, NonRootUser); + await RunAsync(tag, AdminUser); + if (NonRootUserSupported) + { + await RunAsync(tag, NonRootUser); + } } } finally diff --git a/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/TestDockerfile.cs b/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/TestDockerfile.cs index 61d78c053d..24c3908f1e 100644 --- a/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/TestDockerfile.cs +++ b/tests/Microsoft.DotNet.Docker.Tests/TestScenarios/TestDockerfile.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; namespace Microsoft.DotNet.Docker.Tests; @@ -36,13 +37,19 @@ private static string GetContent(IEnumerable args, IEnumerable l public static class TestDockerfileBuilder { + private const string CopyNuGetConfigCommands = + """ + WORKDIR /source + COPY NuGet.config . + """; + private static DockerOS s_os = DockerHelper.IsLinuxContainerModeEnabled ? DockerOS.Linux : DockerOS.Windows; private static bool s_useNuGetConfig = Config.IsNightlyRepo; - private static string[] s_args = [ + private static string[] s_commonArgs = [ "sdk_image", "runtime_image", "runtime_deps_image", @@ -50,13 +57,12 @@ public static class TestDockerfileBuilder public static TestDockerfile GetDefaultDockerfile(PublishConfig publishConfig) { - string publishLayerFromLine = $"FROM {TestDockerfile.BuildStageName} AS {TestDockerfile.PublishStageName}"; string[] publishAndAppLayers = publishConfig switch { PublishConfig.Aot => [ $""" - {publishLayerFromLine} + FROM {TestDockerfile.BuildStageName} AS {TestDockerfile.PublishStageName} RUN dotnet publish -r {FormatArg("rid")} --no-restore -o /app """, $""" @@ -70,7 +76,7 @@ ENTRYPOINT ["./app"] PublishConfig.FxDependent => [ $""" - {publishLayerFromLine} + FROM {TestDockerfile.BuildStageName} AS {TestDockerfile.PublishStageName} RUN dotnet publish --no-restore -c Release -o out """, $""" @@ -85,7 +91,7 @@ ARG port PublishConfig.SelfContained => [ $""" - {publishLayerFromLine} + FROM {TestDockerfile.BuildStageName} AS {TestDockerfile.PublishStageName} ARG rid RUN dotnet publish -r {FormatArg("rid")} -c Release --self-contained true -o out """, @@ -102,7 +108,7 @@ ENTRYPOINT ["./app"] }; return new TestDockerfile( - args: s_args, + args: s_commonArgs, layers: [ GetDefaultBuildLayer(), ..publishAndAppLayers ]); } @@ -121,25 +127,109 @@ COPY tests/ . """; return new TestDockerfile( - args: s_args, + args: s_commonArgs, layers: [ GetDefaultBuildLayer(), testLayer ]); } - private static string GetDefaultBuildLayer() => - $""" - FROM $sdk_image AS {TestDockerfile.BuildStageName} - ARG rid - ARG NuGetFeedPassword - ARG port - EXPOSE $port - WORKDIR /source - COPY NuGet.config . - WORKDIR /source/app - COPY app/*.csproj . - RUN dotnet restore -r {FormatArg("rid")} - COPY app/ . - RUN dotnet build --no-restore - """; + public static TestDockerfile GetBlazorWasmDockerfile(bool useWasmTools) + { + string nugetConfigFileOption = s_useNuGetConfig + ? "--configfile NuGet.config" + : string.Empty; + + StringBuilder buildLayerBuilder = new( + $""" + FROM $sdk_image AS {TestDockerfile.BuildStageName} + ARG port + EXPOSE $port + """); + + if (s_useNuGetConfig) + { + buildLayerBuilder.AppendLine(); + buildLayerBuilder.AppendLine(CopyNuGetConfigCommands); + } + + if (useWasmTools) + { + buildLayerBuilder.AppendLine(); + buildLayerBuilder.AppendLine( + $""" + RUN dotnet workload install {nugetConfigFileOption} --skip-manifest-update wasm-tools \ + && . /etc/os-release \ + && case $ID in \ + alpine) apk add --no-cache python3 ;; \ + debian | ubuntu) apt-get update \ + && apt-get install -y --no-install-recommends python3 \ + && rm -rf /var/lib/apt/lists/* ;; \ + mariner | azurelinux) tdnf install -y python3 \ + && tdnf clean all ;; \ + esac + """); + } + + buildLayerBuilder.AppendLine(); + buildLayerBuilder.AppendLine( + """ + WORKDIR /source/app + COPY app/*.csproj . + RUN dotnet restore + COPY app/ . + RUN dotnet build --no-restore + """); + + string buildLayer = buildLayerBuilder.ToString(); + + StringBuilder publishLayerBuilder = new( + $""" + FROM {TestDockerfile.BuildStageName} AS {TestDockerfile.PublishStageName} + ARG rid + """); + + publishLayerBuilder.AppendLine(); + publishLayerBuilder.AppendLine(useWasmTools + ? "RUN dotnet publish -r browser-wasm -c Release --self-contained true -o out" + : "RUN dotnet publish --no-restore -c Release -o out"); + + string publishLayer = publishLayerBuilder.ToString(); + + // Blazor WASM output is a static site - there are no runtime executables to be ran in the app stage. + // Endpoint access is verified in the build stage in the SDK dockerfile. + // App stage can remain empty in order to test publish functionality. + string appLayer = $"""FROM $runtime_deps_image AS {TestDockerfile.AppStageName}"""; + + return new TestDockerfile(s_commonArgs, layers: [ buildLayer, publishLayer, appLayer ]); + } + + private static string GetDefaultBuildLayer() + { + StringBuilder buildLayerBuilder = new( + $""" + FROM $sdk_image AS {TestDockerfile.BuildStageName} + ARG rid + ARG NuGetFeedPassword + ARG port + EXPOSE $port + """); + + if (s_useNuGetConfig) + { + buildLayerBuilder.AppendLine(); + buildLayerBuilder.AppendLine(CopyNuGetConfigCommands); + } + + buildLayerBuilder.AppendLine(); + buildLayerBuilder.AppendLine( + $""" + WORKDIR /source/app + COPY app/*.csproj . + RUN dotnet restore -r {FormatArg("rid")} + COPY app/ . + RUN dotnet build --no-restore + """); + + return buildLayerBuilder.ToString(); + } private static string FormatArg(string arg) => s_os == DockerOS.Windows ? $"%{arg}%" : $"${arg}"; }