Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support manifest lists and use the publish RID to pick the most relevant manifest from the list #247

Merged
merged 25 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fdb756a
support manifest lists and use the RID to pick the most relevant mani…
baronfel Nov 11, 2022
5209f49
don't dispose the client
baronfel Nov 11, 2022
0468080
write tests for packaging an app in all supported configurations
baronfel Nov 21, 2022
c75bf13
more thought put into error messages
baronfel Nov 21, 2022
ca47a5c
WIP support real RID graphs
baronfel Dec 1, 2022
021c629
remove full dependency tracking and just use exact-matching
baronfel Dec 20, 2022
3510ea2
supply platform tags to allow non-native-architecture-images to run d…
baronfel Dec 20, 2022
999612f
use correct docker platform
baronfel Dec 20, 2022
fc072a6
debug actions build's docker context
baronfel Dec 20, 2022
4743318
try to add more platforms to the buildx container
baronfel Dec 20, 2022
a00a519
document the sadness that is cross-platform container execution
baronfel Dec 20, 2022
08c09df
remove artificial block for linux-x64
baronfel Dec 21, 2022
da69e11
re-add runtime inheritance for manifest list runtimes
baronfel Dec 21, 2022
aeb099c
fix the things
baronfel Dec 21, 2022
2ad8afd
documentation for the new ContainerRuntimeIdentifier property
baronfel Dec 21, 2022
ee9f48b
merge from main
baronfel Jan 16, 2023
bbf039a
Add NuGet assemblies to package
rainersigwald Jan 19, 2023
18ccb52
Pass RID through ToolTask implementation
rainersigwald Jan 19, 2023
af582c5
Merge branch 'main' into support-other-arch-and-os
baronfel Jan 19, 2023
37684cc
Pick a rid graph at semi-random for testing
rainersigwald Jan 19, 2023
53a8b6c
remove invalid mediaType check
baronfel Jan 19, 2023
8bbd201
Address some review questions
baronfel Jan 19, 2023
bfba2ac
only use the LEAF rids to compute compatibility with user-provided RIDs
baronfel Jan 19, 2023
abd8c18
bring clarity to the RIDs
baronfel Jan 19, 2023
81c9768
remove linux only messaging
baronfel Jan 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageVersion Include="MSTest.TestAdapter" Version="2.3.0-preview-20220810-02" />
<PackageVersion Include="MSTest.TestFramework" Version="2.2.10" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.5.109" />
<PackageVersion Include="NuGet.Packaging" Version="6.3.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="Valleysoft.DockerCredsProvider" Version="2.1.0" />
</ItemGroup>
Expand Down
9 changes: 6 additions & 3 deletions Microsoft.NET.Build.Containers/ContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ namespace Microsoft.NET.Build.Containers;

public static class ContainerBuilder
{
public static async Task Containerize(DirectoryInfo folder, string workingDir, string registryName, string baseName, string baseTag, string[] entrypoint, string[] entrypointArgs, string imageName, string[] imageTags, string? outputRegistry, string[] labels, Port[] exposedPorts, string[] envVars)
public static async Task Containerize(DirectoryInfo folder, string workingDir, string registryName, string baseName, string baseTag, string[] entrypoint, string[] entrypointArgs, string imageName, string[] imageTags, string? outputRegistry, string[] labels, Port[] exposedPorts, string[] envVars, string containerRuntimeIdentifier, string ridGraphPath)
{
var isDockerPull = String.IsNullOrEmpty(registryName);
if (isDockerPull) {
throw new ArgumentException("Don't know how to pull images from local daemons at the moment");
}
Registry baseRegistry = new Registry(ContainerHelpers.TryExpandRegistryToUri(registryName));

Image img = await baseRegistry.GetImageManifest(baseName, baseTag);
var img = await baseRegistry.GetImageManifest(baseName, baseTag, containerRuntimeIdentifier, ridGraphPath);
if (img is null) {
throw new ArgumentException($"Could not find image {baseName}:{baseTag} in registry {registryName} matching RuntimeIdentifier {containerRuntimeIdentifier}");
}

img.WorkingDirectory = workingDir;

JsonSerializerOptions options = new()
Expand Down Expand Up @@ -82,6 +86,5 @@ public static async Task Containerize(DirectoryInfo folder, string workingDir, s
}
}
}

}
}
1 change: 1 addition & 0 deletions Microsoft.NET.Build.Containers/ContentStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public static string PathForDescriptor(Descriptor descriptor)
{
"application/vnd.docker.image.rootfs.diff.tar.gzip"
or "application/vnd.oci.image.layer.v1.tar+gzip"
or "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
baronfel marked this conversation as resolved.
Show resolved Hide resolved
=> ".tar.gz",
"application/vnd.docker.image.rootfs.diff.tar"
or "application/vnd.oci.image.layer.v1.tar"
Expand Down
16 changes: 16 additions & 0 deletions Microsoft.NET.Build.Containers/CreateNewImage.Interface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,25 @@ partial class CreateNewImage
/// </summary>
public ITaskItem[] ContainerEnvironmentVariables { get; set; }

/// <summary>
/// The RID to use to determine the host manifest if the parent container is a manifest list
/// </summary>
[Required]
public string ContainerRuntimeIdentifier { get; set; }

/// <summary>
/// The path to the runtime identifier graph file. This is used to compute RID compatibility for Image Manifest List entries.
/// </summary>
[Required]
public string RuntimeIdentifierGraphPath { get; set; }

[Output]
public string GeneratedContainerManifest { get; set; }

[Output]
public string GeneratedContainerConfiguration { get; set; }


public CreateNewImage()
{
ContainerizeDirectory = "";
Expand All @@ -117,6 +130,9 @@ public CreateNewImage()
Labels = Array.Empty<ITaskItem>();
ExposedPorts = Array.Empty<ITaskItem>();
ContainerEnvironmentVariables = Array.Empty<ITaskItem>();
ContainerRuntimeIdentifier = "";
RuntimeIdentifierGraphPath = "";

GeneratedContainerConfiguration = "";
GeneratedContainerManifest = "";
}
Expand Down
14 changes: 10 additions & 4 deletions Microsoft.NET.Build.Containers/CreateNewImage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Build.Framework;
using System.Text.Json;
using Microsoft.Build.Framework;

namespace Microsoft.NET.Build.Containers.Tasks;

Expand Down Expand Up @@ -72,12 +73,12 @@ private void SetEnvironmentVariables(Image img, ITaskItem[] envVars)
}
}

private Image GetBaseImage() {
private Image? GetBaseImage() {
if (IsDockerPull) {
throw new ArgumentException("Don't know how to pull images from local daemons at the moment");
} else {
var reg = new Registry(ContainerHelpers.TryExpandRegistryToUri(BaseRegistry));
return reg.GetImageManifest(BaseImageName, BaseImageTag).Result;
return reg.GetImageManifest(BaseImageName, BaseImageTag, ContainerRuntimeIdentifier, RuntimeIdentifierGraphPath).Result;
}
}

Expand All @@ -95,6 +96,11 @@ public override bool Execute()

var image = GetBaseImage();

if (image is null) {
Log.LogError($"Couldn't find matching base image for {0}:{1} that matches RuntimeIdentifier {2}", BaseImageName, BaseImageTag, ContainerRuntimeIdentifier);
return !Log.HasLoggedErrors;
}

SafeLog("Building image '{0}' with tags {1} on top of base image {2}/{3}:{4}", ImageName, String.Join(",", ImageTags), BaseRegistry, BaseImageName, BaseImageTag);

Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory);
Expand All @@ -118,7 +124,7 @@ public override bool Execute()
}

// at this point we're done with modifications and are just pushing the data other places
GeneratedContainerManifest = image.manifest.ToJsonString();
GeneratedContainerManifest = JsonSerializer.Serialize(image.manifest);
GeneratedContainerConfiguration = image.config.ToJsonString();

Registry? outputReg = IsDockerPush ? null : new Registry(ContainerHelpers.TryExpandRegistryToUri(OutputRegistry));
Expand Down
4 changes: 3 additions & 1 deletion Microsoft.NET.Build.Containers/CreateNewImageToolTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ protected override string GenerateCommandLineCommands()
(ImageTags.Length > 0 ? " --imagetags " + String.Join(" ", ImageTags.Select((i) => Quote(i))) : "") +
(EntrypointArgs.Length > 0 ? " --entrypointargs " + String.Join(" ", EntrypointArgs.Select((i) => i.ItemSpec)) : "") +
(ExposedPorts.Length > 0 ? " --ports " + String.Join(" ", ExposedPorts.Select((i) => i.ItemSpec + "/" + i.GetMetadata("Type"))) : "") +
(ContainerEnvironmentVariables.Length > 0 ? " --environmentvariables " + String.Join(" ", ContainerEnvironmentVariables.Select((i) => i.ItemSpec + "=" + Quote(i.GetMetadata("Value")))) : "");
(ContainerEnvironmentVariables.Length > 0 ? " --environmentvariables " + String.Join(" ", ContainerEnvironmentVariables.Select((i) => i.ItemSpec + "=" + Quote(i.GetMetadata("Value")))) : "") +
$" --rid {Quote(ContainerRuntimeIdentifier)}" +
$" --ridgraphpath {Quote(RuntimeIdentifierGraphPath)}";
}

private string Quote(string path)
Expand Down
34 changes: 20 additions & 14 deletions Microsoft.NET.Build.Containers/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.NET.Build.Containers;

public class Image
{
public JsonNode manifest;
public ManifestV2 manifest;
public JsonNode config;

public readonly string OriginatingName;
Expand All @@ -22,7 +22,7 @@ public class Image

internal Dictionary<string, string> environmentVariables;

public Image(JsonNode manifest, JsonNode config, string name, Registry? registry)
public Image(ManifestV2 manifest, JsonNode config, string name, Registry? registry)
{
this.manifest = manifest;
this.config = config;
Expand All @@ -38,39 +38,38 @@ public IEnumerable<Descriptor> LayerDescriptors
{
get
{
JsonNode? layersNode = manifest["layers"];
var layersNode = manifest.layers;

if (layersNode is null)
{
throw new NotImplementedException("Tried to get layer information but there is no layer node?");
}

foreach (JsonNode? descriptorJson in layersNode.AsArray())
foreach (var layer in layersNode)
{
if (descriptorJson is null)
{
throw new NotImplementedException("Null layer descriptor in the list?");
}

yield return descriptorJson.Deserialize<Descriptor>();
yield return new(layer.mediaType, layer.digest, layer.size);
}
}
}

public void AddLayer(Layer l)
{
newLayers.Add(l);
manifest["layers"]!.AsArray().Add(l.Descriptor);

manifest.layers.Add(new(l.Descriptor.MediaType, l.Descriptor.Size, l.Descriptor.Digest, l.Descriptor.Urls));
config["rootfs"]!["diff_ids"]!.AsArray().Add(l.Descriptor.UncompressedDigest);
RecalculateDigest();
}

private void RecalculateDigest()
{
config["created"] = DateTime.UtcNow;

manifest["config"]!["digest"] = GetDigest(config);
manifest["config"]!["size"] = Encoding.UTF8.GetBytes(config.ToJsonString()).Length;
var newManifestConfig = manifest.config with
{
digest = GetDigest(config),
size = Encoding.UTF8.GetBytes(config.ToJsonString()).Length
};
manifest.config = newManifestConfig;
baronfel marked this conversation as resolved.
Show resolved Hide resolved
}

private JsonObject CreatePortMap()
Expand Down Expand Up @@ -249,6 +248,13 @@ public string GetDigest(JsonNode json)
return $"sha256:{hashString}";
}

public string GetDigest<T>(T item)
{
var node = JsonSerializer.SerializeToNode(item);
if (node is not null) return GetDigest(node);
else return String.Empty;
baronfel marked this conversation as resolved.
Show resolved Hide resolved
}

public static string GetSha(JsonNode json)
{
Span<byte> hash = stackalloc byte[SHA256.HashSizeInBytes];
Expand Down
23 changes: 12 additions & 11 deletions Microsoft.NET.Build.Containers/LocalDocker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,20 @@ public static async Task WriteImageToStream(Image x, string name, string tag, St

foreach (var d in x.LayerDescriptors)
{
if (!x.originatingRegistry.HasValue)
if (x.originatingRegistry is {} registry)
{
throw new NotImplementedException("Need a good error for 'couldn't download a thing because no link to registry'");
}
string localPath = await registry.DownloadBlob(x.OriginatingName, d);

string localPath = await x.originatingRegistry.Value.DownloadBlob(x.OriginatingName, d);

// Stuff that (uncompressed) tarball into the image tar stream
// TODO uncompress!!
string layerTarballPath = $"{d.Digest.Substring("sha256:".Length)}/layer.tar";
await writer.WriteEntryAsync(localPath, layerTarballPath);
layerTarballPaths.Add(layerTarballPath);
}
// Stuff that (uncompressed) tarball into the image tar stream
// TODO uncompress!!
string layerTarballPath = $"{d.Digest.Substring("sha256:".Length)}/layer.tar";
await writer.WriteEntryAsync(localPath, layerTarballPath);
layerTarballPaths.Add(layerTarballPath);
}
else
{
throw new NotImplementedException("Need a good error for 'couldn't download a thing because no link to registry'");
} }

// add config
string configTarballPath = $"{Image.GetSha(x.config)}.json";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" PrivateAssets="all" ExcludeAssets="runtime" />
<PackageReference Include="Nuget.Packaging" />
<PackageReference Include="Valleysoft.DockerCredsProvider" />
</ItemGroup>

Expand Down
Loading