forked from dotnet/sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathImageIndexGenerator.cs
138 lines (121 loc) · 5.27 KB
/
ImageIndexGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.NET.Build.Containers.Resources;
namespace Microsoft.NET.Build.Containers;
internal static class ImageIndexGenerator
{
/// <summary>
/// Generates an image index from the given images.
/// </summary>
/// <param name="images">Images to generate image index from.</param>
/// <returns>Returns json string of image index and image index mediaType.</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="NotSupportedException"></exception>
internal static (string, string) GenerateImageIndex(BuiltImage[] images)
{
if (images.Length == 0)
{
throw new ArgumentException(Strings.ImagesEmpty);
}
string manifestMediaType = images[0].ManifestMediaType;
if (!images.All(image => string.Equals(image.ManifestMediaType, manifestMediaType, StringComparison.OrdinalIgnoreCase)))
{
throw new ArgumentException(Strings.MixedMediaTypes);
}
if (manifestMediaType == SchemaTypes.DockerManifestV2)
{
return (GenerateImageIndex(images, SchemaTypes.DockerManifestV2, SchemaTypes.DockerManifestListV2), SchemaTypes.DockerManifestListV2);
}
else if (manifestMediaType == SchemaTypes.OciManifestV1)
{
return (GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1), SchemaTypes.OciImageIndexV1);
}
else
{
throw new NotSupportedException(string.Format(Strings.UnsupportedMediaType, manifestMediaType));
}
}
/// <summary>
/// Generates an image index from the given images.
/// </summary>
/// <param name="images">Images to generate image index from.</param>
/// <param name="manifestMediaType">Media type of the manifest.</param>
/// <param name="imageIndexMediaType">Media type of the produced image index.</param>
/// <returns>Returns json string of image index and image index mediaType.</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="NotSupportedException"></exception>
internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType)
{
if (images.Length == 0)
{
throw new ArgumentException(Strings.ImagesEmpty);
}
// Here we are using ManifestListV2 struct, but we could use ImageIndexV1 struct as well.
// We are filling the same fields, so we can use the same struct.
var manifests = new PlatformSpecificManifest[images.Length];
for (int i = 0; i < images.Length; i++)
{
manifests[i] = new PlatformSpecificManifest
{
mediaType = manifestMediaType,
size = images[i].Manifest.Length,
digest = images[i].ManifestDigest,
platform = new PlatformInformation
{
architecture = images[i].Architecture!,
os = images[i].OS!
}
};
}
var imageIndex = new ManifestListV2
{
schemaVersion = 2,
mediaType = imageIndexMediaType,
manifests = manifests
};
return GetJsonStringFromImageIndex(imageIndex);
}
internal static string GenerateImageIndexWithAnnotations(string manifestMediaType, string manifestDigest, long manifestSize, string repository, string[] tags)
{
string containerdImageNamePrefix = repository.Contains('/') ? "docker.io/" : "docker.io/library/";
var manifests = new PlatformSpecificOciManifest[tags.Length];
for (int i = 0; i < tags.Length; i++)
{
var tag = tags[i];
manifests[i] = new PlatformSpecificOciManifest
{
mediaType = manifestMediaType,
size = manifestSize,
digest = manifestDigest,
annotations = new Dictionary<string, string>
{
{ "io.containerd.image.name", $"{containerdImageNamePrefix}{repository}:{tag}" },
{ "org.opencontainers.image.ref.name", tag }
}
};
}
var index = new ImageIndexV1
{
schemaVersion = 2,
mediaType = SchemaTypes.OciImageIndexV1,
manifests = manifests
};
return GetJsonStringFromImageIndex(index);
}
private static string GetJsonStringFromImageIndex<T>(T imageIndex)
{
var nullIgnoreOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
// To avoid things like \u002B for '+' especially in media types ("application/vnd.oci.image.manifest.v1\u002Bjson"), we use UnsafeRelaxedJsonEscaping.
var escapeOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
return JsonSerializer.SerializeToNode(imageIndex, nullIgnoreOptions)?.ToJsonString(escapeOptions) ?? "";
}
}