Skip to content

Commit 782cfc2

Browse files
[release/9.0.1xx] Allow reading of OCI Image Indexes to determine RID-specific base image for multi-architecture images (#43913)
Co-authored-by: Chet Husk <[email protected]>
1 parent a524686 commit 782cfc2

File tree

5 files changed

+84
-14
lines changed

5 files changed

+84
-14
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ public record struct ManifestListV2(int schemaVersion, string mediaType, Platfor
1010
public record struct PlatformInformation(string architecture, string os, string? variant, string[] features, [property: JsonPropertyName("os.version")][field: JsonPropertyName("os.version")] string? version);
1111

1212
public record struct PlatformSpecificManifest(string mediaType, long size, string digest, PlatformInformation platform);
13+
14+
public record struct ImageIndexV1(int schemaVersion, string mediaType, PlatformSpecificManifest[] manifests);

src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net9.0/PublicAPI.Unshipped.txt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ Microsoft.NET.Build.Containers.ManifestListV2.mediaType.get -> string!
7272
Microsoft.NET.Build.Containers.ManifestListV2.mediaType.set -> void
7373
Microsoft.NET.Build.Containers.ManifestListV2.schemaVersion.get -> int
7474
Microsoft.NET.Build.Containers.ManifestListV2.schemaVersion.set -> void
75+
Microsoft.NET.Build.Containers.ImageIndexV1
76+
Microsoft.NET.Build.Containers.ImageIndexV1.ImageIndexV1() -> void
77+
Microsoft.NET.Build.Containers.ImageIndexV1.ImageIndexV1(int schemaVersion, string! mediaType, Microsoft.NET.Build.Containers.PlatformSpecificManifest[]! manifests) -> void
78+
Microsoft.NET.Build.Containers.ImageIndexV1.manifests.get -> Microsoft.NET.Build.Containers.PlatformSpecificManifest[]!
79+
Microsoft.NET.Build.Containers.ImageIndexV1.manifests.set -> void
80+
Microsoft.NET.Build.Containers.ImageIndexV1.mediaType.get -> string!
81+
Microsoft.NET.Build.Containers.ImageIndexV1.mediaType.set -> void
82+
Microsoft.NET.Build.Containers.ImageIndexV1.schemaVersion.get -> int
83+
Microsoft.NET.Build.Containers.ImageIndexV1.schemaVersion.set -> void
7584
Microsoft.NET.Build.Containers.ManifestV2
7685
Microsoft.NET.Build.Containers.ManifestV2.Config.get -> Microsoft.NET.Build.Containers.ManifestConfig
7786
Microsoft.NET.Build.Containers.ManifestV2.Config.init -> void
@@ -268,4 +277,11 @@ static Microsoft.NET.Build.Containers.ManifestListV2.operator ==(Microsoft.NET.B
268277
override Microsoft.NET.Build.Containers.ManifestListV2.GetHashCode() -> int
269278
~override Microsoft.NET.Build.Containers.ManifestListV2.Equals(object obj) -> bool
270279
Microsoft.NET.Build.Containers.ManifestListV2.Equals(Microsoft.NET.Build.Containers.ManifestListV2 other) -> bool
271-
Microsoft.NET.Build.Containers.ManifestListV2.Deconstruct(out int schemaVersion, out string! mediaType, out Microsoft.NET.Build.Containers.PlatformSpecificManifest[]! manifests) -> void
280+
Microsoft.NET.Build.Containers.ManifestListV2.Deconstruct(out int schemaVersion, out string! mediaType, out Microsoft.NET.Build.Containers.PlatformSpecificManifest[]! manifests) -> void
281+
~override Microsoft.NET.Build.Containers.ImageIndexV1.ToString() -> string
282+
static Microsoft.NET.Build.Containers.ImageIndexV1.operator !=(Microsoft.NET.Build.Containers.ImageIndexV1 left, Microsoft.NET.Build.Containers.ImageIndexV1 right) -> bool
283+
static Microsoft.NET.Build.Containers.ImageIndexV1.operator ==(Microsoft.NET.Build.Containers.ImageIndexV1 left, Microsoft.NET.Build.Containers.ImageIndexV1 right) -> bool
284+
override Microsoft.NET.Build.Containers.ImageIndexV1.GetHashCode() -> int
285+
~override Microsoft.NET.Build.Containers.ImageIndexV1.Equals(object obj) -> bool
286+
Microsoft.NET.Build.Containers.ImageIndexV1.Equals(Microsoft.NET.Build.Containers.ImageIndexV1 other) -> bool
287+
Microsoft.NET.Build.Containers.ImageIndexV1.Deconstruct(out int schemaVersion, out string! mediaType, out Microsoft.NET.Build.Containers.PlatformSpecificManifest[]! manifests) -> void

src/Containers/Microsoft.NET.Build.Containers/Registry/HttpExtensions.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3-
//
43

4+
using System.Net.Http.Headers;
55
using Microsoft.Extensions.Logging;
6+
using NuGet.Packaging;
67

78
namespace Microsoft.NET.Build.Containers;
89

910
internal static class HttpExtensions
1011
{
12+
private static readonly MediaTypeWithQualityHeaderValue[] _knownManifestFormats = [
13+
new("application/json"),
14+
new(SchemaTypes.DockerManifestListV2),
15+
new(SchemaTypes.OciImageIndexV1),
16+
new(SchemaTypes.DockerManifestV2),
17+
new(SchemaTypes.OciManifestV1),
18+
new(SchemaTypes.DockerContainerV1),
19+
];
20+
1121
internal static HttpRequestMessage AcceptManifestFormats(this HttpRequestMessage request)
1222
{
1323
request.Headers.Accept.Clear();
14-
request.Headers.Accept.Add(new("application/json"));
15-
request.Headers.Accept.Add(new(SchemaTypes.DockerManifestListV2));
16-
request.Headers.Accept.Add(new(SchemaTypes.DockerManifestV2));
17-
request.Headers.Accept.Add(new(SchemaTypes.OciManifestV1));
18-
request.Headers.Accept.Add(new(SchemaTypes.DockerContainerV1));
24+
request.Headers.Accept.AddRange(_knownManifestFormats);
1925
return request;
2026
}
2127

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

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ internal Registry(string registryName, ILogger logger, IRegistryAPI registryAPI,
8181
this(new Uri($"https://{registryName}"), logger, registryAPI, settings)
8282
{ }
8383

84-
internal Registry(string registryName, ILogger logger, RegistryMode mode, RegistrySettings? settings = null) :
84+
internal Registry(string registryName, ILogger logger, RegistryMode mode, RegistrySettings? settings = null) :
8585
this(new Uri($"https://{registryName}"), logger, new RegistryApiFactory(mode), settings)
8686
{ }
8787

@@ -191,6 +191,14 @@ await initialManifestResponse.Content.ReadFromJsonAsync<ManifestListV2>(cancella
191191
runtimeIdentifier,
192192
manifestPicker,
193193
cancellationToken).ConfigureAwait(false),
194+
SchemaTypes.OciImageIndexV1 =>
195+
await PickBestImageFromImageIndexAsync(
196+
repositoryName,
197+
reference,
198+
await initialManifestResponse.Content.ReadFromJsonAsync<ImageIndexV1>(cancellationToken: cancellationToken).ConfigureAwait(false),
199+
runtimeIdentifier,
200+
manifestPicker,
201+
cancellationToken).ConfigureAwait(false),
194202
var unknownMediaType => throw new NotImplementedException(Resource.FormatString(
195203
nameof(Strings.UnknownMediaType),
196204
repositoryName,
@@ -236,10 +244,10 @@ private async Task<ImageBuilder> ReadSingleImageAsync(string repositoryName, Man
236244
}
237245

238246

239-
private static IReadOnlyDictionary<string, PlatformSpecificManifest> GetManifestsByRid(ManifestListV2 manifestList)
247+
private static IReadOnlyDictionary<string, PlatformSpecificManifest> GetManifestsByRid(PlatformSpecificManifest[] manifestList)
240248
{
241249
var ridDict = new Dictionary<string, PlatformSpecificManifest>();
242-
foreach (var manifest in manifestList.manifests)
250+
foreach (var manifest in manifestList)
243251
{
244252
if (CreateRidForPlatform(manifest.platform) is { } rid)
245253
{
@@ -293,14 +301,51 @@ private async Task<ImageBuilder> PickBestImageFromManifestListAsync(
293301
CancellationToken cancellationToken)
294302
{
295303
cancellationToken.ThrowIfCancellationRequested();
296-
var ridManifestDict = GetManifestsByRid(manifestList);
297-
if (manifestPicker.PickBestManifestForRid(ridManifestDict, runtimeIdentifier) is PlatformSpecificManifest matchingManifest)
304+
var ridManifestDict = GetManifestsByRid(manifestList.manifests);
305+
return await PickBestImageFromManifestsAsync(
306+
repositoryName,
307+
reference,
308+
ridManifestDict,
309+
runtimeIdentifier,
310+
manifestPicker,
311+
cancellationToken).ConfigureAwait(false);
312+
}
313+
314+
private async Task<ImageBuilder> PickBestImageFromImageIndexAsync(
315+
string repositoryName,
316+
string reference,
317+
ImageIndexV1 index,
318+
string runtimeIdentifier,
319+
IManifestPicker manifestPicker,
320+
CancellationToken cancellationToken)
321+
{
322+
cancellationToken.ThrowIfCancellationRequested();
323+
var ridManifestDict = GetManifestsByRid(index.manifests);
324+
return await PickBestImageFromManifestsAsync(
325+
repositoryName,
326+
reference,
327+
ridManifestDict,
328+
runtimeIdentifier,
329+
manifestPicker,
330+
cancellationToken).ConfigureAwait(false);
331+
}
332+
333+
private async Task<ImageBuilder> PickBestImageFromManifestsAsync(
334+
string repositoryName,
335+
string reference,
336+
IReadOnlyDictionary<string, PlatformSpecificManifest> knownManifests,
337+
string runtimeIdentifier,
338+
IManifestPicker manifestPicker,
339+
CancellationToken cancellationToken)
340+
{
341+
cancellationToken.ThrowIfCancellationRequested();
342+
if (manifestPicker.PickBestManifestForRid(knownManifests, runtimeIdentifier) is PlatformSpecificManifest matchingManifest)
298343
{
299344
using HttpResponseMessage manifestResponse = await _registryAPI.Manifest.GetAsync(repositoryName, matchingManifest.digest, cancellationToken).ConfigureAwait(false);
300345

301346
cancellationToken.ThrowIfCancellationRequested();
302347
var manifest = await manifestResponse.Content.ReadFromJsonAsync<ManifestV2>(cancellationToken: cancellationToken).ConfigureAwait(false);
303-
if (manifest is null) throw new BaseImageNotFoundException(runtimeIdentifier, repositoryName, reference, ridManifestDict.Keys);
348+
if (manifest is null) throw new BaseImageNotFoundException(runtimeIdentifier, repositoryName, reference, knownManifests.Keys);
304349
manifest.KnownDigest = matchingManifest.digest;
305350
return await ReadSingleImageAsync(
306351
repositoryName,
@@ -309,7 +354,7 @@ private async Task<ImageBuilder> PickBestImageFromManifestListAsync(
309354
}
310355
else
311356
{
312-
throw new BaseImageNotFoundException(runtimeIdentifier, repositoryName, reference, ridManifestDict.Keys);
357+
throw new BaseImageNotFoundException(runtimeIdentifier, repositoryName, reference, knownManifests.Keys);
313358
}
314359
}
315360

src/Containers/Microsoft.NET.Build.Containers/Registry/SchemaTypes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ internal class SchemaTypes
1010
internal const string DockerManifestListV2 = "application/vnd.docker.distribution.manifest.list.v2+json";
1111
internal const string DockerManifestV2 = "application/vnd.docker.distribution.manifest.v2+json";
1212
internal const string OciManifestV1 = "application/vnd.oci.image.manifest.v1+json"; // https://containers.gitbook.io/build-containers-the-hard-way/#registry-format-oci-image-manifest
13+
internal const string OciImageIndexV1 = "application/vnd.oci.image.index.v1+json";
1314
internal const string DockerLayerGzip = "application/vnd.docker.image.rootfs.diff.tar.gzip";
1415
internal const string OciLayerGzipV1 = "application/vnd.oci.image.layer.v1.tar+gzip";
1516
}

0 commit comments

Comments
 (0)