Skip to content

Commit

Permalink
perform explicit Rid-to-Docker-Platform mapping and support solution-…
Browse files Browse the repository at this point in the history
…level pushes
  • Loading branch information
baronfel committed Jul 9, 2023
1 parent f3729d4 commit ef6ae36
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 30 deletions.
94 changes: 94 additions & 0 deletions src/Containers/Microsoft.NET.Build.Containers/PlatformMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.NET.Build.Containers;

/// <summary>
/// Handles mapping between .NET RIDs and Docker platform names/structures
/// </summary>
public static class PlatformMapping {

public static bool TryGetRidForDockerPlatform(string golangPlatform, bool isMuslBased, [NotNullWhen(true)] out string? runtimeIdentifier) {
runtimeIdentifier = null;

runtimeIdentifier = golangPlatform.Split('/') switch {
["linux", "amd64"] when isMuslBased => "linux-musl-x64",
["linux", "amd64"] => "linux-x64",

["linux", "amd64", var _amd64Version] when isMuslBased => "linux-musl-x64",
["linux", "amd64", var _amd64Version] => "linux-x64",

["linux", "arm64"] when isMuslBased => "linux-musl-arm64",
["linux", "arm64"] => "linux-arm64",

["linux", "arm64", var _arm64Version ] when isMuslBased => "linux-musl-arm64",
["linux", "arm64", var _arm64Version ] => "linux-arm64",

["linux", "arm" ] or ["linux", "arm", "v7" ] when isMuslBased => "linux-musl-arm",
["linux", "arm" ] or ["linux", "arm", "v7" ] => "linux-arm",

["linux", "arm", "v6" ] when isMuslBased => "linux-musl-armv6",
["linux", "arm", "v6" ] => "linux-armv6",

["linux", "riscv64" ] when isMuslBased => "linux-musl-riscv64",
["linux", "riscv64" ] => "linux-riscv64",

["linux", "ppc64le" ] when isMuslBased => "linux-musl-ppc64le",
["linux", "ppc64le" ] => "linux-ppc64le",

["linux", "s390x" ] when isMuslBased => "linux-musl-s390x",
["linux", "s390x" ] => "linux-s390x",

["linux", "386" ] when isMuslBased => "linux-musl-x86",
["linux", "386" ] => "linux-x86",

["windows", "amd64"] => "win-x64",
["windows", "arm64"] => "win-arm64",

_ => null // other golang platforms are not supported
};
return runtimeIdentifier != null;
}

public static bool TryGetDockerPlatformForRid(string runtimeIdentifier, [NotNullWhen(true)] out string? dockerPlatform) {
dockerPlatform = null;

runtimeIdentifier = runtimeIdentifier.Replace("-musl-", "-"); // we lose musl information in the docker platform name

dockerPlatform = runtimeIdentifier.Split('-') switch {
["linux", "x64"] => "linux/amd64",
["linux", "arm64"] => "linux/arm64",
["linux", "arm"] => "linux/arm/v7",
["linux", "armv6"] => "linux/arm/v6",
["linux", "riscv64"] => "linux/riscv64",
["linux", "ppc64le"] => "linux/ppc64le",
["linux", "s390x"] => "linux/s390x",
["linux", "x86"] => "linux/386",
["win", "x64"] => "windows/amd64",
["win", "arm64"] => "windows/arm64",
_ => null
};
return dockerPlatform != null;
}

public static bool TryGetDockerImageTagForRid(string runtimeIdentifier, [NotNullWhen(true)] out string? dockerPlatformTag) {
dockerPlatformTag = null;

runtimeIdentifier = runtimeIdentifier.Replace("-musl-", "-"); // we lose musl information in the docker platform name

dockerPlatformTag = runtimeIdentifier.Split('-') switch {
["linux", "x64"] => "amd64",
["linux", "arm64"] => "arm64v8",
["linux", "arm"] => "arm32v7",
["linux", "armv6"] => "arm32v6",
["linux", "riscv64"] => "riscv64",
["linux", "ppc64le"] => "ppc64le",
["linux", "s390x"] => "s390x",
["linux", "x86"] => "386",
_ => null // deliberately not trying to make tag names for windows containers
};
return dockerPlatformTag != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ Microsoft.NET.Build.Containers.PlatformInformation.variant.get -> string?
Microsoft.NET.Build.Containers.PlatformInformation.variant.set -> void
Microsoft.NET.Build.Containers.PlatformInformation.version.get -> string?
Microsoft.NET.Build.Containers.PlatformInformation.version.set -> void
Microsoft.NET.Build.Containers.PlatformMapping
static Microsoft.NET.Build.Containers.PlatformMapping.TryGetRidForDockerPlatform(string! golangPlatform, bool isMuslBased, out string? runtimeIdentifier) -> bool
static Microsoft.NET.Build.Containers.PlatformMapping.TryGetDockerPlatformForRid(string! runtimeIdentifier, out string? dockerPlatform) -> bool
static Microsoft.NET.Build.Containers.PlatformMapping.TryGetDockerImageTagForRid(string! runtimeIdentifier, out string? dockerPlatformTag) -> bool
Microsoft.NET.Build.Containers.PlatformSpecificManifest
Microsoft.NET.Build.Containers.PlatformSpecificManifest.digest.get -> string!
Microsoft.NET.Build.Containers.PlatformSpecificManifest.digest.set -> void
Expand Down Expand Up @@ -166,6 +170,13 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolPath.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolPath.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.WorkingDirectory.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.WorkingDirectory.set -> void
Microsoft.NET.Build.Containers.Tasks.MapRidToDockerPlatform
Microsoft.NET.Build.Containers.Tasks.MapRidToDockerPlatform.MapRidToDockerPlatform() -> void
Microsoft.NET.Build.Containers.Tasks.MapRidToDockerPlatform.RuntimeIdentifiers.get -> Microsoft.Build.Framework.ITaskItem![]!
Microsoft.NET.Build.Containers.Tasks.MapRidToDockerPlatform.RuntimeIdentifiers.set -> void
Microsoft.NET.Build.Containers.Tasks.MapRidToDockerPlatform.ModifiedRuntimeIdentifiers.get -> Microsoft.Build.Framework.ITaskItem![]!
Microsoft.NET.Build.Containers.Tasks.MapRidToDockerPlatform.ModifiedRuntimeIdentifiers.set -> void
override Microsoft.NET.Build.Containers.Tasks.MapRidToDockerPlatform.Execute() -> bool
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerEnvironmentVariables.get -> Microsoft.Build.Framework.ITaskItem![]!
Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ContainerEnvironmentVariables.set -> void
Expand Down
37 changes: 8 additions & 29 deletions src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,35 +192,14 @@ IReadOnlyDictionary<string, PlatformSpecificManifest> GetManifestsByRid(Manifest
return null;
}

private static string? CreateRidForPlatform(PlatformInformation platform)
private static string? CreateRidForPlatform(PlatformInformation platform, bool isMuslBased = false)
{
// we only support linux and windows containers explicitly, so anything else we should skip past.
var osPart = platform.os switch
{
"linux" => "linux",
"windows" => "win",
_ => null
};
// TODO: this part needs a lot of work, the RID graph isn't super precise here and version numbers (especially on windows) are _whack_
// TODO: we _may_ need OS-specific version parsing. Need to do more research on what the field looks like across more manifest lists.
var versionPart = platform.version?.Split('.') switch
{
[var major, .. ] => major,
_ => null
};
var platformPart = platform.architecture switch
{
"amd64" => "x64",
"x386" => "x86",
"arm" => $"arm{(platform.variant != "v7" ? platform.variant : "")}",
"arm64" => "arm64",
"ppc64le" => "ppc64le",
"s390x" => "s390x",
_ => null
};

if (osPart is null || platformPart is null) return null;
return $"{osPart}{versionPart ?? ""}-{platformPart}";
var dockerPlatformString = $"{platform.os}/{platform.architecture}{(platform.variant is null ? "" : $"/{platform.variant}")}";
if (PlatformMapping.TryGetRidForDockerPlatform(dockerPlatformString, isMuslBased: isMuslBased, out string? rid)) {
return rid;
} else {
return null;
}
}

private static RuntimeGraph GetRuntimeGraphForDotNet(string ridGraphPath) => JsonRuntimeFormat.ReadRuntimeGraph(ridGraphPath);
Expand Down Expand Up @@ -298,7 +277,7 @@ internal async Task<FinalizeUploadInformation> UploadBlobChunkedAsync(Stream con
// manual because ACR throws an error with the .NET type {"Range":"bytes 0-84521/*","Reason":"the Content-Range header format is invalid"}
// content.Headers.Add("Content-Range", $"0-{contents.Length - 1}");
Debug.Assert(content.Headers.TryAddWithoutValidation("Content-Range", $"{chunkStart}-{chunkStart + bytesRead - 1}"));

NextChunkUploadInformation nextChunk = await _registryAPI.Blob.Upload.UploadChunkAsync(patchUri, content, cancellationToken).ConfigureAwait(false);
patchUri = nextChunk.UploadUri;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Build.Framework;

namespace Microsoft.NET.Build.Containers.Tasks;

public sealed class MapRidToDockerPlatform : Microsoft.Build.Utilities.Task
{
[Required]
public ITaskItem[] RuntimeIdentifiers { get; set; }

[Output]
public ITaskItem[] ModifiedRuntimeIdentifiers { get; set; }

public MapRidToDockerPlatform()
{
RuntimeIdentifiers = Array.Empty<ITaskItem>();
ModifiedRuntimeIdentifiers = Array.Empty<ITaskItem>();
}
public override bool Execute()
{
bool result = true;
var modifiedRuntimeIdentifiers = new List<ITaskItem>(RuntimeIdentifiers.Length);
foreach (ITaskItem rid in RuntimeIdentifiers) {
if (PlatformMapping.TryGetDockerImageTagForRid(rid.ItemSpec, out string? dockerPlatform))
{
rid.SetMetadata("DockerPlatformTag", dockerPlatform);
modifiedRuntimeIdentifiers.Add(rid);
}
else {
Log.LogError($"No Docker platform mapping found for RuntimeIdentifier '{rid.ItemSpec}'");
result = false;
}
}
ModifiedRuntimeIdentifiers = modifiedRuntimeIdentifiers.ToArray();
return result;
}
}
3 changes: 3 additions & 0 deletions src/Containers/containerize/ContainerizeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

using System.CommandLine;
using System.CommandLine.Parsing;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.ComponentModel;
using System.Text;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.Logging;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
<UsingTask TaskName="$(MSBuildThisFileName).Tasks.CreateNewImage" AssemblyFile="$(ContainerCustomTasksAssembly)"/>
<UsingTask TaskName="$(MSBuildThisFileName).Tasks.ParseContainerProperties" AssemblyFile="$(ContainerCustomTasksAssembly)"/>
<UsingTask TaskName="$(MSBuildThisFileName).Tasks.ComputeDotnetBaseImageTag" AssemblyFile="$(ContainerCustomTasksAssembly)"/>
<UsingTask TaskName="$(MSBuildThisFileName).Tasks.MapRidToDockerPlatform" AssemblyFile="$(ContainerCustomTasksAssembly)"/>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
</PublishContainerDependsOn>
</PropertyGroup>

<!-- Main entrypoint for creating a specific container -->
<Target Name="PublishContainer" DependsOnTargets="$(PublishContainerDependsOn)">

<PropertyGroup Condition="'$(DOTNET_HOST_PATH)' == ''">
Expand Down Expand Up @@ -221,10 +222,69 @@
ContainerEnvironmentVariables="@(ContainerEnvironmentVariables)"
ContainerRuntimeIdentifier="$(ContainerRuntimeIdentifier)"
ContainerUser="$(ContainerUser)"
RuntimeIdentifierGraphPath="$(RuntimeIdentifierGraphPath)"> <!-- The RID graph path is provided as a property by the SDK. -->
RuntimeIdentifierGraphPath="$(RuntimeIdentifierGraphPath)" > <!-- The RID graph path is provided as a property by the SDK. -->

<Output TaskParameter="GeneratedContainerManifest" PropertyName="GeneratedContainerManifest" />
<Output TaskParameter="GeneratedContainerConfiguration" PropertyName="GeneratedContainerConfiguration" />
</CreateNewImage>
</Target>

<!-- Secondary entrypoint, either from solution-level `/t:Containerize` or project-level `/t:Containerize` -->
<Target Name="Containerize" Condition="'$(EnableSdkContainerSupport)' == 'true'">
<PropertyGroup>
<!-- We have to build Publish AND PublishContainer because PublishContainer (and other
PublishProfile-delivered targets) don't have an explicit Publish dependency. -->
<_RequiredContainerPublishTargets>Publish;PublishContainer</_RequiredContainerPublishTargets>
</PropertyGroup>

<!-- Strategy here is that we will figure out what project(s) to build the containerization targets(s) for
based on project state. We use `AdditionalProperties` to customize the outputs of each of the builds. -->

<!-- Properties set here:
* TargetFramework - multitargeting - changes inference for base image based on TFM
* ContainerImageTag - this is a rough cut
* ContainerRuntimeIdentifier - if we're building for a specific RID, we need to set this so that the
containerization targets know what RID to build for
* RuntimeIdentifier - if we're building for a specific RID, we need to set this so that we get optimized
RID-specific assets in the publish output
NOTE: we could get away with setting `RuntimeIdentfier` here to control `ContainerRuntimeIdentifier` inference
but this is also nice and explicit.
-->

<!-- TFMs but no TF -> multitarget, making image for each TFM -->
<ItemGroup Condition="'$(TargetFrameworks)' != ''
and '$(TargetFramework)' == ''" >
<_TFMItems Include="$(TargetFrameworks)" />
<_SingleContainerPublish Include="$(MSBuildProjectFullPath)"
AdditionalProperties="TargetFramework=%(_TFMItems.Identity);
ContainerImageTag=$([MSBuild]::GetTargetFrameworkVersion('%(_TFMItems.Identity)', 2))" />
</ItemGroup>

<!-- TF but no TFMs -> single image (aka the default pathway) up until now -->
<ItemGroup Condition="'$(TargetFramework)' != ''
and '$(RuntimeIdentifiers)' == ''">
<_SingleContainerPublish Include="$(MSBuildProjectFullPath)" />
</ItemGroup>

<!-- TF with RIDs -> multi-arch, single image per arch -->
<ItemGroup Condition="'$(TargetFramework)' != ''
and '$(RuntimeIdentifiers)' != ''">
<_RIDItems Include="$(RuntimeIdentifiers)" />
</ItemGroup>
<MapRidToDockerPlatform Condition="'$(TargetFramework)' != ''
and '$(RuntimeIdentifiers)' != ''"
RuntimeIdentifiers="@(_RIDItems)">
<Output TaskParameter="ModifiedRuntimeIdentifiers" ItemName="_RIDItemsWithDockerPlatformTag" />
</MapRidToDockerPlatform>
<ItemGroup Condition="'$(TargetFramework)' != ''
and '$(RuntimeIdentifiers)' != ''">
<_SingleContainerPublish Include="$(MSBuildProjectFullPath)"
AdditionalProperties="ContainerRuntimeIdentifier=%(_RIDItemsWithDockerPlatformTag.Identity);
RuntimeIdentifier=%(_RIDItemsWithDockerPlatformTag.Identity);
ContainerImageTag=$(Version)-%(_RIDItemsWithDockerPlatformTag.DockerPlatformTag);" />
</ItemGroup>

<MSBuild Projects="@(_SingleContainerPublish)" Targets="$(_RequiredContainerPublishTargets)" BuildInParallel="true" />
</Target>
</Project>
Loading

0 comments on commit ef6ae36

Please sign in to comment.