Skip to content

Commit fdb756a

Browse files
committed
support manifest lists and use the RID to pick the most relevant manifest
1 parent bb1114f commit fdb756a

File tree

11 files changed

+179
-118
lines changed

11 files changed

+179
-118
lines changed

Microsoft.NET.Build.Containers/ContainerBuilder.cs

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,81 +7,82 @@ namespace Microsoft.NET.Build.Containers;
77

88
public static class ContainerBuilder
99
{
10-
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)
10+
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)
1111
{
1212
var isDockerPull = String.IsNullOrEmpty(registryName);
1313
if (isDockerPull) {
1414
throw new ArgumentException("Don't know how to pull images from local daemons at the moment");
1515
}
1616
Registry baseRegistry = new Registry(ContainerHelpers.TryExpandRegistryToUri(registryName));
1717

18-
Image img = await baseRegistry.GetImageManifest(baseName, baseTag);
19-
img.WorkingDirectory = workingDir;
18+
var img = await baseRegistry.GetImageManifest(baseName, baseTag, containerRuntimeIdentifier);
19+
if (img is not null) {
20+
img.WorkingDirectory = workingDir;
2021

21-
JsonSerializerOptions options = new()
22-
{
23-
WriteIndented = true,
24-
};
25-
26-
Layer l = Layer.FromDirectory(folder.FullName, workingDir);
22+
JsonSerializerOptions options = new()
23+
{
24+
WriteIndented = true,
25+
};
2726

28-
img.AddLayer(l);
27+
Layer l = Layer.FromDirectory(folder.FullName, workingDir);
2928

30-
img.SetEntrypoint(entrypoint, entrypointArgs);
29+
img.AddLayer(l);
3130

32-
var isDockerPush = String.IsNullOrEmpty(outputRegistry);
33-
Registry? outputReg = isDockerPush ? null : new Registry(ContainerHelpers.TryExpandRegistryToUri(outputRegistry));
31+
img.SetEntrypoint(entrypoint, entrypointArgs);
3432

35-
foreach (var label in labels)
36-
{
37-
string[] labelPieces = label.Split('=');
33+
var isDockerPush = String.IsNullOrEmpty(outputRegistry);
34+
Registry? outputReg = isDockerPush ? null : new Registry(ContainerHelpers.TryExpandRegistryToUri(outputRegistry));
3835

39-
// labels are validated by System.CommandLine API
40-
img.Label(labelPieces[0], labelPieces[1]);
41-
}
36+
foreach (var label in labels)
37+
{
38+
string[] labelPieces = label.Split('=');
4239

43-
foreach (string envVar in envVars)
44-
{
45-
string[] envPieces = envVar.Split('=', 2);
40+
// labels are validated by System.CommandLine API
41+
img.Label(labelPieces[0], labelPieces[1]);
42+
}
4643

47-
img.AddEnvironmentVariable(envPieces[0], envPieces[1]);
48-
}
44+
foreach (string envVar in envVars)
45+
{
46+
string[] envPieces = envVar.Split('=', 2);
4947

50-
foreach (var (number, type) in exposedPorts)
51-
{
52-
// ports are validated by System.CommandLine API
53-
img.ExposePort(number, type);
54-
}
48+
img.AddEnvironmentVariable(envPieces[0], envPieces[1]);
49+
}
5550

56-
foreach (var tag in imageTags)
57-
{
58-
if (isDockerPush)
51+
foreach (var (number, type) in exposedPorts)
5952
{
60-
try
61-
{
62-
LocalDocker.Load(img, imageName, tag, baseName).Wait();
63-
Console.WriteLine("Containerize: Pushed container '{0}:{1}' to Docker daemon", imageName, tag);
64-
}
65-
catch (Exception e)
66-
{
67-
Console.WriteLine($"Containerize: error CONTAINER001: Failed to push to local docker registry: {e}");
68-
Environment.ExitCode = -1;
69-
}
53+
// ports are validated by System.CommandLine API
54+
img.ExposePort(number, type);
7055
}
71-
else
56+
57+
foreach (var tag in imageTags)
7258
{
73-
try
59+
if (isDockerPush)
7460
{
75-
outputReg?.Push(img, imageName, tag, imageName, (message) => Console.WriteLine($"Containerize: {message}")).Wait();
76-
Console.WriteLine($"Containerize: Pushed container '{imageName}:{tag}' to registry '{outputRegistry}'");
61+
try
62+
{
63+
LocalDocker.Load(img, imageName, tag, baseName).Wait();
64+
Console.WriteLine("Containerize: Pushed container '{0}:{1}' to Docker daemon", imageName, tag);
65+
}
66+
catch (Exception e)
67+
{
68+
Console.WriteLine($"Containerize: error CONTAINER001: Failed to push to local docker registry: {e}");
69+
Environment.ExitCode = -1;
70+
}
7771
}
78-
catch (Exception e)
72+
else
7973
{
80-
Console.WriteLine($"Containerize: error CONTAINER001: Failed to push to output registry: {e}");
81-
Environment.ExitCode = -1;
74+
try
75+
{
76+
outputReg?.Push(img, imageName, tag, imageName, (message) => Console.WriteLine($"Containerize: {message}")).Wait();
77+
Console.WriteLine($"Containerize: Pushed container '{imageName}:{tag}' to registry '{outputRegistry}'");
78+
}
79+
catch (Exception e)
80+
{
81+
Console.WriteLine($"Containerize: error CONTAINER001: Failed to push to output registry: {e}");
82+
Environment.ExitCode = -1;
83+
}
8284
}
8385
}
8486
}
85-
8687
}
8788
}

Microsoft.NET.Build.Containers/CreateNewImage.Interface.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ partial class CreateNewImage
9393
/// </summary>
9494
public ITaskItem[] ContainerEnvironmentVariables { get; set; }
9595

96+
/// <summary>
97+
/// The RID to use to determine the host manifest if the parent container is a manifest list
98+
/// </summary>
99+
[Required]
100+
public string ContainerRuntimeIdentifier { get; set; }
101+
102+
96103
[Output]
97104
public string GeneratedContainerManifest { get; set; }
98105

@@ -117,6 +124,8 @@ public CreateNewImage()
117124
Labels = Array.Empty<ITaskItem>();
118125
ExposedPorts = Array.Empty<ITaskItem>();
119126
ContainerEnvironmentVariables = Array.Empty<ITaskItem>();
127+
ContainerRuntimeIdentifier = "";
128+
120129
GeneratedContainerConfiguration = "";
121130
GeneratedContainerManifest = "";
122131
}

Microsoft.NET.Build.Containers/CreateNewImage.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.Build.Framework;
1+
using System.Text.Json;
2+
using Microsoft.Build.Framework;
23

34
namespace Microsoft.NET.Build.Containers.Tasks;
45

@@ -72,12 +73,12 @@ private void SetEnvironmentVariables(Image img, ITaskItem[] envVars)
7273
}
7374
}
7475

75-
private Image GetBaseImage() {
76+
private Image? GetBaseImage() {
7677
if (IsDockerPull) {
7778
throw new ArgumentException("Don't know how to pull images from local daemons at the moment");
7879
} else {
7980
var reg = new Registry(ContainerHelpers.TryExpandRegistryToUri(BaseRegistry));
80-
return reg.GetImageManifest(BaseImageName, BaseImageTag).Result;
81+
return reg.GetImageManifest(BaseImageName, BaseImageTag, ContainerRuntimeIdentifier).Result;
8182
}
8283
}
8384

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

9697
var image = GetBaseImage();
9798

99+
if (image is null) {
100+
Log.LogError($"Couldn't find matching base image for {0}:{1} that matches RuntimeIdentifier {2}", BaseImageName, BaseImageTag, ContainerRuntimeIdentifier);
101+
return !Log.HasLoggedErrors;
102+
}
103+
98104
SafeLog("Building image '{0}' with tags {1} on top of base image {2}/{3}:{4}", ImageName, String.Join(",", ImageTags), BaseRegistry, BaseImageName, BaseImageTag);
99105

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

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

124130
Registry? outputReg = IsDockerPush ? null : new Registry(ContainerHelpers.TryExpandRegistryToUri(OutputRegistry));

Microsoft.NET.Build.Containers/Image.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Microsoft.NET.Build.Containers;
88

99
public class Image
1010
{
11-
public JsonNode manifest;
11+
public ManifestV2 manifest;
1212
public JsonNode config;
1313

1414
public readonly string OriginatingName;
@@ -22,7 +22,7 @@ public class Image
2222

2323
internal Dictionary<string, string> environmentVariables;
2424

25-
public Image(JsonNode manifest, JsonNode config, string name, Registry? registry)
25+
public Image(ManifestV2 manifest, JsonNode config, string name, Registry? registry)
2626
{
2727
this.manifest = manifest;
2828
this.config = config;
@@ -38,39 +38,33 @@ public IEnumerable<Descriptor> LayerDescriptors
3838
{
3939
get
4040
{
41-
JsonNode? layersNode = manifest["layers"];
41+
var layersNode = manifest.layers;
4242

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

48-
foreach (JsonNode? descriptorJson in layersNode.AsArray())
48+
foreach (var layer in layersNode)
4949
{
50-
if (descriptorJson is null)
51-
{
52-
throw new NotImplementedException("Null layer descriptor in the list?");
53-
}
54-
55-
yield return descriptorJson.Deserialize<Descriptor>();
50+
yield return new(layer.mediaType, layer.digest, layer.size);
5651
}
5752
}
5853
}
5954

6055
public void AddLayer(Layer l)
6156
{
6257
newLayers.Add(l);
63-
manifest["layers"]!.AsArray().Add(l.Descriptor);
58+
59+
manifest.layers.Add(new (l.Descriptor.MediaType, l.Descriptor.Size, l.Descriptor.Digest, l.Descriptor.Urls));
6460
config["rootfs"]!["diff_ids"]!.AsArray().Add(l.Descriptor.UncompressedDigest);
6561
RecalculateDigest();
6662
}
6763

6864
private void RecalculateDigest()
6965
{
7066
config["created"] = DateTime.UtcNow;
71-
72-
manifest["config"]!["digest"] = GetDigest(config);
73-
manifest["config"]!["size"] = Encoding.UTF8.GetBytes(config.ToJsonString()).Length;
67+
manifest = manifest with { config = manifest.config with { digest = GetDigest(config), size = Encoding.UTF8.GetBytes(config.ToJsonString()).Length } };
7468
}
7569

7670
private JsonObject CreatePortMap()
@@ -249,6 +243,13 @@ public string GetDigest(JsonNode json)
249243
return $"sha256:{hashString}";
250244
}
251245

246+
public string GetDigest<T>(T item)
247+
{
248+
var node = JsonSerializer.SerializeToNode(item);
249+
if (node is not null) return GetDigest(node);
250+
else return String.Empty;
251+
}
252+
252253
public static string GetSha(JsonNode json)
253254
{
254255
Span<byte> hash = stackalloc byte[SHA256.HashSizeInBytes];

Microsoft.NET.Build.Containers/LocalDocker.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ public static async Task WriteImageToStream(Image x, string name, string tag, St
4747

4848
foreach (var d in x.LayerDescriptors)
4949
{
50-
if (!x.originatingRegistry.HasValue)
50+
if (x.originatingRegistry is null)
5151
{
5252
throw new NotImplementedException("Need a good error for 'couldn't download a thing because no link to registry'");
5353
}
5454

55-
string localPath = await x.originatingRegistry.Value.DownloadBlob(x.OriginatingName, d);
55+
string localPath = await x.originatingRegistry.DownloadBlob(x.OriginatingName, d);
5656

5757
// Stuff that (uncompressed) tarball into the image tar stream
5858
// TODO uncompress!!

0 commit comments

Comments
 (0)