Skip to content

Commit 3be5038

Browse files
authored
[automated] Merge branch 'release/9.0.3xx' => 'main' (#46908)
2 parents df4091f + 8f39695 commit 3be5038

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1962
-918
lines changed

src/Cli/dotnet/commands/InstallingWorkloadCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ private IEnumerable<ManifestVersionUpdate> InstallWorkloadSet(ITransactionContex
352352
Reporter.WriteLine(string.Format(Strings.NewWorkloadSet, workloadSetVersion));
353353
var workloadSet = _workloadInstaller.InstallWorkloadSet(context, workloadSetVersion);
354354

355-
return _workloadManifestUpdater.CalculateManifestUpdatesForWorkloadSet(workloadSet);
355+
return workloadSet is null ? Enumerable.Empty<ManifestVersionUpdate>() : _workloadManifestUpdater.CalculateManifestUpdatesForWorkloadSet(workloadSet);
356356
}
357357

358358
protected async Task<List<WorkloadDownload>> GetDownloads(IEnumerable<WorkloadId> workloadIds, bool skipManifestUpdate, bool includePreview, string downloadFolder = null,

src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,51 @@ internal readonly struct BuiltImage
1616
/// <summary>
1717
/// Gets image digest.
1818
/// </summary>
19-
internal required string ImageDigest { get; init; }
19+
internal string? ImageDigest { get; init; }
2020

2121
/// <summary>
2222
/// Gets image SHA.
2323
/// </summary>
24-
internal required string ImageSha { get; init; }
24+
internal string? ImageSha { get; init; }
2525

2626
/// <summary>
27-
/// Gets image size.
27+
/// Gets image manifest.
2828
/// </summary>
29-
internal required long ImageSize { get; init; }
29+
internal required string Manifest { get; init; }
3030

3131
/// <summary>
32-
/// Gets image manifest.
32+
/// Gets manifest digest.
3333
/// </summary>
34-
internal required ManifestV2 Manifest { get; init; }
34+
internal required string ManifestDigest { get; init; }
3535

3636
/// <summary>
3737
/// Gets manifest mediaType.
3838
/// </summary>
3939
internal required string ManifestMediaType { get; init; }
4040

41+
/// <summary>
42+
/// Gets image layers.
43+
/// </summary>
44+
internal List<ManifestLayer>? Layers { get; init; }
45+
46+
/// <summary>
47+
/// Gets image OS.
48+
/// </summary>
49+
internal string? OS { get; init; }
50+
51+
/// <summary>
52+
/// Gets image architecture.
53+
/// </summary>
54+
internal string? Architecture { get; init; }
55+
4156
/// <summary>
4257
/// Gets layers descriptors.
4358
/// </summary>
4459
internal IEnumerable<Descriptor> LayerDescriptors
4560
{
4661
get
4762
{
48-
List<ManifestLayer> layersNode = Manifest.Layers ?? throw new NotImplementedException("Tried to get layer information but there is no layer node?");
63+
List<ManifestLayer> layersNode = Layers ?? throw new NotImplementedException("Tried to get layer information but there is no layer node?");
4964
foreach (ManifestLayer layer in layersNode)
5065
{
5166
yield return new(layer.mediaType, layer.digest, layer.size);

src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ internal sealed class DigestUtils
1717
/// </summary>
1818
internal static string GetDigestFromSha(string sha) => $"sha256:{sha}";
1919

20+
internal static string GetShaFromDigest(string digest)
21+
{
22+
if (!digest.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase))
23+
{
24+
throw new ArgumentException($"Invalid digest '{digest}'. Digest must start with 'sha256:'.");
25+
}
26+
27+
return digest.Substring("sha256:".Length);
28+
}
29+
2030
/// <summary>
2131
/// Gets the SHA of <paramref name="str"/>.
2232
/// </summary>

src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
using System.Text;
55
using System.Text.RegularExpressions;
6+
using System.Text.Json;
67
using Microsoft.Extensions.Logging;
78
using Microsoft.NET.Build.Containers.Resources;
89

910
namespace Microsoft.NET.Build.Containers;
1011

11-
1212
/// <summary>
1313
/// The class builds new image based on the base image.
1414
/// </summary>
@@ -91,9 +91,10 @@ internal BuiltImage Build()
9191
Config = imageJsonStr,
9292
ImageDigest = imageDigest,
9393
ImageSha = imageSha,
94-
ImageSize = imageSize,
95-
Manifest = newManifest,
96-
ManifestMediaType = ManifestMediaType
94+
Manifest = JsonSerializer.SerializeToNode(newManifest)?.ToJsonString() ?? "",
95+
ManifestDigest = newManifest.GetDigest(),
96+
ManifestMediaType = ManifestMediaType,
97+
Layers = _manifest.Layers
9798
};
9899
}
99100

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,138 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text.Encodings.Web;
45
using System.Text.Json;
5-
using System.Text.Json.Nodes;
6+
using System.Text.Json.Serialization;
67
using Microsoft.NET.Build.Containers.Resources;
7-
using Microsoft.NET.Build.Containers.Tasks;
88

99
namespace Microsoft.NET.Build.Containers;
1010

11-
internal readonly struct ImageInfo
12-
{
13-
internal string Config { get; init; }
14-
internal string ManifestDigest { get; init; }
15-
internal string Manifest { get; init; }
16-
internal string ManifestMediaType { get; init; }
17-
18-
public override string ToString() => ManifestDigest;
19-
}
20-
2111
internal static class ImageIndexGenerator
2212
{
2313
/// <summary>
2414
/// Generates an image index from the given images.
2515
/// </summary>
26-
/// <param name="imageInfos"></param>
16+
/// <param name="images">Images to generate image index from.</param>
2717
/// <returns>Returns json string of image index and image index mediaType.</returns>
2818
/// <exception cref="ArgumentException"></exception>
2919
/// <exception cref="NotSupportedException"></exception>
30-
internal static (string, string) GenerateImageIndex(ImageInfo[] imageInfos)
20+
internal static (string, string) GenerateImageIndex(BuiltImage[] images)
3121
{
32-
if (imageInfos.Length == 0)
22+
if (images.Length == 0)
3323
{
34-
throw new ArgumentException(string.Format(Strings.ImagesEmpty));
24+
throw new ArgumentException(Strings.ImagesEmpty);
3525
}
3626

37-
string manifestMediaType = imageInfos[0].ManifestMediaType;
27+
string manifestMediaType = images[0].ManifestMediaType;
3828

39-
if (!imageInfos.All(image => string.Equals(image.ManifestMediaType, manifestMediaType, StringComparison.OrdinalIgnoreCase)))
29+
if (!images.All(image => string.Equals(image.ManifestMediaType, manifestMediaType, StringComparison.OrdinalIgnoreCase)))
4030
{
4131
throw new ArgumentException(Strings.MixedMediaTypes);
4232
}
4333

4434
if (manifestMediaType == SchemaTypes.DockerManifestV2)
4535
{
46-
return GenerateImageIndex(imageInfos, SchemaTypes.DockerManifestV2, SchemaTypes.DockerManifestListV2);
36+
return (GenerateImageIndex(images, SchemaTypes.DockerManifestV2, SchemaTypes.DockerManifestListV2), SchemaTypes.DockerManifestListV2);
4737
}
4838
else if (manifestMediaType == SchemaTypes.OciManifestV1)
4939
{
50-
return GenerateImageIndex(imageInfos, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1);
40+
return (GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1), SchemaTypes.OciImageIndexV1);
5141
}
5242
else
5343
{
5444
throw new NotSupportedException(string.Format(Strings.UnsupportedMediaType, manifestMediaType));
5545
}
5646
}
5747

58-
private static (string, string) GenerateImageIndex(ImageInfo[] images, string manifestMediaType, string imageIndexMediaType)
48+
/// <summary>
49+
/// Generates an image index from the given images.
50+
/// </summary>
51+
/// <param name="images">Images to generate image index from.</param>
52+
/// <param name="manifestMediaType">Media type of the manifest.</param>
53+
/// <param name="imageIndexMediaType">Media type of the produced image index.</param>
54+
/// <returns>Returns json string of image index and image index mediaType.</returns>
55+
/// <exception cref="ArgumentException"></exception>
56+
/// <exception cref="NotSupportedException"></exception>
57+
internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType)
5958
{
59+
if (images.Length == 0)
60+
{
61+
throw new ArgumentException(Strings.ImagesEmpty);
62+
}
63+
6064
// Here we are using ManifestListV2 struct, but we could use ImageIndexV1 struct as well.
61-
// We are filling the same fiels, so we can use the same struct.
65+
// We are filling the same fields, so we can use the same struct.
6266
var manifests = new PlatformSpecificManifest[images.Length];
67+
6368
for (int i = 0; i < images.Length; i++)
6469
{
65-
var image = images[i];
66-
67-
var manifest = new PlatformSpecificManifest
70+
manifests[i] = new PlatformSpecificManifest
6871
{
6972
mediaType = manifestMediaType,
70-
size = image.Manifest.Length,
71-
digest = image.ManifestDigest,
72-
platform = GetArchitectureAndOsFromConfig(image)
73+
size = images[i].Manifest.Length,
74+
digest = images[i].ManifestDigest,
75+
platform = new PlatformInformation
76+
{
77+
architecture = images[i].Architecture!,
78+
os = images[i].OS!
79+
}
7380
};
74-
manifests[i] = manifest;
7581
}
7682

77-
var dockerManifestList = new ManifestListV2
83+
var imageIndex = new ManifestListV2
7884
{
7985
schemaVersion = 2,
8086
mediaType = imageIndexMediaType,
8187
manifests = manifests
8288
};
8389

84-
return (JsonSerializer.SerializeToNode(dockerManifestList)?.ToJsonString() ?? "", dockerManifestList.mediaType);
90+
return GetJsonStringFromImageIndex(imageIndex);
8591
}
8692

87-
private static PlatformInformation GetArchitectureAndOsFromConfig(ImageInfo image)
93+
internal static string GenerateImageIndexWithAnnotations(string manifestMediaType, string manifestDigest, long manifestSize, string repository, string[] tags)
8894
{
89-
var configJson = JsonNode.Parse(image.Config) as JsonObject ??
90-
throw new ArgumentException($"{nameof(image.Config)} should be a JSON object.", nameof(image.Config));
95+
string containerdImageNamePrefix = repository.Contains('/') ? "docker.io/" : "docker.io/library/";
96+
97+
var manifests = new PlatformSpecificOciManifest[tags.Length];
98+
for (int i = 0; i < tags.Length; i++)
99+
{
100+
var tag = tags[i];
101+
manifests[i] = new PlatformSpecificOciManifest
102+
{
103+
mediaType = manifestMediaType,
104+
size = manifestSize,
105+
digest = manifestDigest,
106+
annotations = new Dictionary<string, string>
107+
{
108+
{ "io.containerd.image.name", $"{containerdImageNamePrefix}{repository}:{tag}" },
109+
{ "org.opencontainers.image.ref.name", tag }
110+
}
111+
};
112+
}
91113

92-
var architecture = configJson["architecture"]?.ToString() ??
93-
throw new ArgumentException($"{nameof(image.Config)} should contain 'architecture'.", nameof(image.Config));
114+
var index = new ImageIndexV1
115+
{
116+
schemaVersion = 2,
117+
mediaType = SchemaTypes.OciImageIndexV1,
118+
manifests = manifests
119+
};
94120

95-
var os = configJson["os"]?.ToString() ??
96-
throw new ArgumentException($"{nameof(image.Config)} should contain 'os'.", nameof(image.Config));
121+
return GetJsonStringFromImageIndex(index);
122+
}
123+
124+
private static string GetJsonStringFromImageIndex<T>(T imageIndex)
125+
{
126+
var nullIgnoreOptions = new JsonSerializerOptions
127+
{
128+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
129+
};
130+
// To avoid things like \u002B for '+' especially in media types ("application/vnd.oci.image.manifest.v1\u002Bjson"), we use UnsafeRelaxedJsonEscaping.
131+
var escapeOptions = new JsonSerializerOptions
132+
{
133+
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
134+
};
97135

98-
return new PlatformInformation { architecture = architecture, os = os };
136+
return JsonSerializer.SerializeToNode(imageIndex, nullIgnoreOptions)?.ToJsonString(escapeOptions) ?? "";
99137
}
100138
}

0 commit comments

Comments
 (0)