Skip to content

Commit c82cd2e

Browse files
authored
[automated] Merge branch 'release/9.0.1xx' => 'release/9.0.2xx' (#46781)
2 parents ffe4458 + 530c54c commit c82cd2e

Some content is hidden

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

52 files changed

+2132
-932
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/ContainerBuilder.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
namespace Microsoft.NET.Build.Containers;
88

9+
internal enum KnownImageFormats
10+
{
11+
OCI,
12+
Docker
13+
}
14+
915
internal static class ContainerBuilder
1016
{
1117
internal static async Task<int> ContainerizeAsync(
@@ -34,6 +40,7 @@ internal static async Task<int> ContainerizeAsync(
3440
string? archiveOutputPath,
3541
bool generateLabels,
3642
bool generateDigestLabel,
43+
KnownImageFormats? imageFormat,
3744
ILoggerFactory loggerFactory,
3845
CancellationToken cancellationToken)
3946
{
@@ -99,6 +106,15 @@ internal static async Task<int> ContainerizeAsync(
99106
logger.LogInformation(Strings.ContainerBuilder_StartBuildingImage, imageName, string.Join(",", imageName), sourceImageReference);
100107
cancellationToken.ThrowIfCancellationRequested();
101108

109+
// forcibly change the media type if required
110+
imageBuilder.ManifestMediaType = imageFormat switch
111+
{
112+
null => imageBuilder.ManifestMediaType,
113+
KnownImageFormats.Docker => SchemaTypes.DockerManifestV2,
114+
KnownImageFormats.OCI => SchemaTypes.OciManifestV1,
115+
_ => imageBuilder.ManifestMediaType // should be impossible unless we add to the enum
116+
};
117+
102118
Layer newLayer = Layer.FromDirectory(publishDirectory.FullName, workingDir, imageBuilder.IsWindows, imageBuilder.ManifestMediaType);
103119
imageBuilder.AddLayer(newLayer);
104120
imageBuilder.SetWorkingDirectory(workingDir);

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: 16 additions & 10 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>
@@ -19,7 +19,6 @@ internal sealed class ImageBuilder
1919

2020
// the mutable internal manifest that we're building by modifying the base and applying customizations
2121
private readonly ManifestV2 _manifest;
22-
private readonly string _manifestMediaType;
2322
private readonly ImageConfig _baseImageConfig;
2423
private readonly ILogger _logger;
2524

@@ -32,15 +31,15 @@ internal sealed class ImageBuilder
3231
public ImageConfig BaseImageConfig => _baseImageConfig;
3332

3433
/// <summary>
35-
/// MediaType of the output manifest.
34+
/// MediaType of the output manifest. By default, this will be the same as the base image manifest.
3635
/// </summary>
37-
public string ManifestMediaType => _manifestMediaType; // output the same media type as the base image manifest.
36+
public string ManifestMediaType { get; set; }
3837

3938
internal ImageBuilder(ManifestV2 manifest, string manifestMediaType, ImageConfig baseImageConfig, ILogger logger)
4039
{
4140
_baseImageManifest = manifest;
4241
_manifest = new ManifestV2() { SchemaVersion = manifest.SchemaVersion, Config = manifest.Config, Layers = new(manifest.Layers), MediaType = manifest.MediaType };
43-
_manifestMediaType = manifestMediaType;
42+
ManifestMediaType = manifestMediaType;
4443
_baseImageConfig = baseImageConfig;
4544
_logger = logger;
4645
}
@@ -70,14 +69,20 @@ internal BuiltImage Build()
7069
ManifestConfig newManifestConfig = _manifest.Config with
7170
{
7271
digest = imageDigest,
73-
size = imageSize
72+
size = imageSize,
73+
mediaType = ManifestMediaType switch
74+
{
75+
SchemaTypes.OciManifestV1 => SchemaTypes.OciImageConfigV1,
76+
SchemaTypes.DockerManifestV2 => SchemaTypes.DockerContainerV1,
77+
_ => SchemaTypes.OciImageConfigV1 // opinion - defaulting to modern here, but really this should never happen
78+
}
7479
};
7580

7681
ManifestV2 newManifest = new ManifestV2()
7782
{
7883
Config = newManifestConfig,
7984
SchemaVersion = _manifest.SchemaVersion,
80-
MediaType = _manifest.MediaType,
85+
MediaType = ManifestMediaType,
8186
Layers = _manifest.Layers
8287
};
8388

@@ -86,9 +91,10 @@ internal BuiltImage Build()
8691
Config = imageJsonStr,
8792
ImageDigest = imageDigest,
8893
ImageSha = imageSha,
89-
ImageSize = imageSize,
90-
Manifest = newManifest,
91-
ManifestMediaType = ManifestMediaType
94+
Manifest = JsonSerializer.SerializeToNode(newManifest)?.ToJsonString() ?? "",
95+
ManifestDigest = newManifest.GetDigest(),
96+
ManifestMediaType = ManifestMediaType,
97+
Layers = _manifest.Layers
9298
};
9399
}
94100

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)