Skip to content

Commit

Permalink
Re-implement Blazor WASM Dockerfile
Browse files Browse the repository at this point in the history
  • Loading branch information
lbussell committed Aug 26, 2024
1 parent 400da92 commit 29771ea
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 113 deletions.
15 changes: 3 additions & 12 deletions tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,6 @@ public static IEnumerable<object[]> 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.
Expand All @@ -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]
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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);

Expand Down Expand Up @@ -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
Expand Down
134 changes: 112 additions & 22 deletions tests/Microsoft.DotNet.Docker.Tests/TestScenarios/TestDockerfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Microsoft.DotNet.Docker.Tests;

Expand Down Expand Up @@ -36,27 +37,32 @@ private static string GetContent(IEnumerable<string> args, IEnumerable<string> 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",
];

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
""",
$"""
Expand All @@ -70,7 +76,7 @@ ENTRYPOINT ["./app"]
PublishConfig.FxDependent =>
[
$"""
{publishLayerFromLine}
FROM {TestDockerfile.BuildStageName} AS {TestDockerfile.PublishStageName}
RUN dotnet publish --no-restore -c Release -o out
""",
$"""
Expand All @@ -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
""",
Expand All @@ -102,7 +108,7 @@ ENTRYPOINT ["./app"]
};

return new TestDockerfile(
args: s_args,
args: s_commonArgs,
layers: [ GetDefaultBuildLayer(), ..publishAndAppLayers ]);
}

Expand All @@ -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}";
}
Expand Down

0 comments on commit 29771ea

Please sign in to comment.