diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 323600781efe..e03865ac7229 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -376,16 +376,16 @@
https://github.com/dotnet/roslyn-analyzers
- 3d61c57c73c3dd5f1f407ef9cd3414d94bf0eaf2
+ 5bfaf6aea5cf9d1c924d9adc69916eac3be07880https://github.com/dotnet/roslyn-analyzers
- 3d61c57c73c3dd5f1f407ef9cd3414d94bf0eaf2
+ 5bfaf6aea5cf9d1c924d9adc69916eac3be07880https://github.com/dotnet/roslyn-analyzers
- 3d61c57c73c3dd5f1f407ef9cd3414d94bf0eaf2
+ 5bfaf6aea5cf9d1c924d9adc69916eac3be07880
diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs
index 096f70610377..0df6d3fb633a 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs
@@ -50,6 +50,9 @@ internal ImageBuilder(ManifestV2 manifest, string manifestMediaType, ImageConfig
///
public bool IsWindows => _baseImageConfig.IsWindows;
+ // For tests
+ internal string ManifestConfigDigest => _manifest.Config.digest;
+
///
/// Builds the image configuration ready for further processing.
///
diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs
new file mode 100644
index 000000000000..bc23073080ea
--- /dev/null
+++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs
@@ -0,0 +1,100 @@
+// 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.Json;
+using System.Text.Json.Nodes;
+using Microsoft.NET.Build.Containers.Resources;
+using Microsoft.NET.Build.Containers.Tasks;
+
+namespace Microsoft.NET.Build.Containers;
+
+internal readonly struct ImageInfo
+{
+ internal string Config { get; init; }
+ internal string ManifestDigest { get; init; }
+ internal string Manifest { get; init; }
+ internal string ManifestMediaType { get; init; }
+
+ public override string ToString() => ManifestDigest;
+}
+
+internal static class ImageIndexGenerator
+{
+ ///
+ /// Generates an image index from the given images.
+ ///
+ ///
+ /// Returns json string of image index and image index mediaType.
+ ///
+ ///
+ internal static (string, string) GenerateImageIndex(ImageInfo[] imageInfos)
+ {
+ if (imageInfos.Length == 0)
+ {
+ throw new ArgumentException(string.Format(Strings.ImagesEmpty));
+ }
+
+ string manifestMediaType = imageInfos[0].ManifestMediaType;
+
+ if (!imageInfos.All(image => string.Equals(image.ManifestMediaType, manifestMediaType, StringComparison.OrdinalIgnoreCase)))
+ {
+ throw new ArgumentException(Strings.MixedMediaTypes);
+ }
+
+ if (manifestMediaType == SchemaTypes.DockerManifestV2)
+ {
+ return GenerateImageIndex(imageInfos, SchemaTypes.DockerManifestV2, SchemaTypes.DockerManifestListV2);
+ }
+ else if (manifestMediaType == SchemaTypes.OciManifestV1)
+ {
+ return GenerateImageIndex(imageInfos, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1);
+ }
+ else
+ {
+ throw new NotSupportedException(string.Format(Strings.UnsupportedMediaType, manifestMediaType));
+ }
+ }
+
+ private static (string, string) GenerateImageIndex(ImageInfo[] images, string manifestMediaType, string imageIndexMediaType)
+ {
+ // Here we are using ManifestListV2 struct, but we could use ImageIndexV1 struct as well.
+ // We are filling the same fiels, so we can use the same struct.
+ var manifests = new PlatformSpecificManifest[images.Length];
+ for (int i = 0; i < images.Length; i++)
+ {
+ var image = images[i];
+
+ var manifest = new PlatformSpecificManifest
+ {
+ mediaType = manifestMediaType,
+ size = image.Manifest.Length,
+ digest = image.ManifestDigest,
+ platform = GetArchitectureAndOsFromConfig(image)
+ };
+ manifests[i] = manifest;
+ }
+
+ var dockerManifestList = new ManifestListV2
+ {
+ schemaVersion = 2,
+ mediaType = imageIndexMediaType,
+ manifests = manifests
+ };
+
+ return (JsonSerializer.SerializeToNode(dockerManifestList)?.ToJsonString() ?? "", dockerManifestList.mediaType);
+ }
+
+ private static PlatformInformation GetArchitectureAndOsFromConfig(ImageInfo image)
+ {
+ var configJson = JsonNode.Parse(image.Config) as JsonObject ??
+ throw new ArgumentException($"{nameof(image.Config)} should be a JSON object.", nameof(image.Config));
+
+ var architecture = configJson["architecture"]?.ToString() ??
+ throw new ArgumentException($"{nameof(image.Config)} should contain 'architecture'.", nameof(image.Config));
+
+ var os = configJson["os"]?.ToString() ??
+ throw new ArgumentException($"{nameof(image.Config)} should contain 'os'.", nameof(image.Config));
+
+ return new PlatformInformation { architecture = architecture, os = os };
+ }
+}
diff --git a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt
index 6dd6551ddb37..43526e9c3ab4 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt
+++ b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt
@@ -62,6 +62,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerDigest.get
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerDigest.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedArchiveOutputPath.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedArchiveOutputPath.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerMediaType.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerMediaType.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Repository.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Repository.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageTags.get -> string![]!
@@ -123,8 +125,8 @@ Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.get
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.set -> void
-Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.get -> string!
-Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.set -> void
+Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifiers.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifiers.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UsesInvariantGlobalization.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UsesInvariantGlobalization.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag
diff --git a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net9.0/PublicAPI.Unshipped.txt
index 1522357b9a11..b19e4fa81718 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net9.0/PublicAPI.Unshipped.txt
+++ b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net9.0/PublicAPI.Unshipped.txt
@@ -12,12 +12,27 @@ Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.get
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.set -> void
-Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.get -> string!
-Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.set -> void
+Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifiers.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifiers.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UserBaseImage.get -> string?
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UserBaseImage.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UsesInvariantGlobalization.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UsesInvariantGlobalization.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Cancel() -> void
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.CreateImageIndex() -> void
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Dispose() -> void
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedImageIndex.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedImageIndex.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.ImageTags.get -> string![]!
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.ImageTags.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedContainers.get -> Microsoft.Build.Framework.ITaskItem![]!
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedContainers.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.OutputRegistry.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.OutputRegistry.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Repository.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Repository.set -> void
+override Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Execute() -> bool
static readonly Microsoft.NET.Build.Containers.Constants.Version -> string!
Microsoft.NET.Build.Containers.ContainerHelpers
Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError
@@ -196,6 +211,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerDigest.get
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerDigest.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedArchiveOutputPath.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedArchiveOutputPath.set -> void
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerMediaType.get -> string!
+Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerMediaType.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Repository.get -> string!
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Repository.set -> void
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageTags.get -> string![]!
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultManifestOperations.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultManifestOperations.cs
index 14889a669f34..a8b2248d8996 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultManifestOperations.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultManifestOperations.cs
@@ -38,10 +38,9 @@ public async Task GetAsync(string repositoryName, string re
};
}
- public async Task PutAsync(string repositoryName, string reference, ManifestV2 manifest, string mediaType, CancellationToken cancellationToken)
+ public async Task PutAsync(string repositoryName, string reference, string manifestJson, string mediaType, CancellationToken cancellationToken)
{
- string jsonString = JsonSerializer.SerializeToNode(manifest)?.ToJsonString() ?? "";
- HttpContent manifestUploadContent = new StringContent(jsonString);
+ HttpContent manifestUploadContent = new StringContent(manifestJson);
manifestUploadContent.Headers.ContentType = new MediaTypeHeaderValue(mediaType);
HttpResponseMessage putResponse = await _client.PutAsync(new Uri(_baseUri, $"/v2/{repositoryName}/manifests/{reference}"), manifestUploadContent, cancellationToken).ConfigureAwait(false);
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/IManifestOperations.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/IManifestOperations.cs
index 16751a5c6685..23381179403d 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Registry/IManifestOperations.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/IManifestOperations.cs
@@ -14,5 +14,5 @@ internal interface IManifestOperations
{
public Task GetAsync(string repositoryName, string reference, CancellationToken cancellationToken);
- public Task PutAsync(string repositoryName, string reference, ManifestV2 manifest, string mediaType, CancellationToken cancellationToken);
+ public Task PutAsync(string repositoryName, string reference, string manifestListJson, string mediaType, CancellationToken cancellationToken);
}
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs
index 6ac75508f459..fe43d88cc822 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using NuGet.Packaging;
using System.Diagnostics;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
-using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Microsoft.NET.Build.Containers.Resources;
using NuGet.RuntimeModel;
@@ -529,6 +529,17 @@ private async Task UploadBlobAsync(string repository, string digest, Stream cont
}
+ public async Task PushManifestListAsync(string repositoryName, string[] tags, string manifestListJson, string mediaType, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ foreach (var tag in tags)
+ {
+ _logger.LogInformation(Strings.Registry_TagUploadStarted, tag, RegistryName);
+ await _registryAPI.Manifest.PutAsync(repositoryName, tag, manifestListJson, mediaType, cancellationToken).ConfigureAwait(false);
+ _logger.LogInformation(Strings.Registry_TagUploaded, tag, RegistryName);
+ }
+ }
+
public Task PushAsync(BuiltImage builtImage, SourceImageReference source, DestinationImageReference destination, CancellationToken cancellationToken)
=> PushAsync(builtImage, source, destination, pushTags: true, cancellationToken);
@@ -593,13 +604,14 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source,
// Tags can refer to an image manifest or an image manifest list.
// In the first case, we push tags to the registry.
// In the second case, we push the manifest digest so the manifest list can refer to it.
+ string manifestJson = JsonSerializer.SerializeToNode(builtImage.Manifest)?.ToJsonString() ?? "";
if (pushTags)
{
Debug.Assert(destination.Tags.Length > 0);
foreach (string tag in destination.Tags)
{
_logger.LogInformation(Strings.Registry_TagUploadStarted, tag, RegistryName);
- await _registryAPI.Manifest.PutAsync(destination.Repository, tag, builtImage.Manifest, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false);
+ await _registryAPI.Manifest.PutAsync(destination.Repository, tag, manifestJson, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false);
_logger.LogInformation(Strings.Registry_TagUploaded, tag, RegistryName);
}
}
@@ -607,7 +619,7 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source,
{
string manifestDigest = builtImage.Manifest.GetDigest();
_logger.LogInformation(Strings.Registry_ManifestUploadStarted, RegistryName, manifestDigest);
- await _registryAPI.Manifest.PutAsync(destination.Repository, manifestDigest, builtImage.Manifest, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false);
+ await _registryAPI.Manifest.PutAsync(destination.Repository, manifestDigest, manifestJson, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false);
_logger.LogInformation(Strings.Registry_ManifestUploaded, RegistryName);
}
}
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs
index 0edd6dbaa53e..aff92942f408 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs
@@ -168,6 +168,15 @@ internal static string BlobUploadFailed {
}
}
+ ///
+ /// Looks up a localized string similar to Building image index '{0}' on top of manifests {1}..
+ ///
+ internal static string BuildingImageIndex {
+ get {
+ return ResourceManager.GetString("BuildingImageIndex", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Pushed image '{0}' to {1}..
///
@@ -339,6 +348,15 @@ internal static string HostObjectNotDetected {
}
}
+ ///
+ /// Looks up a localized string similar to Pushed image index '{0}' to registry '{1}'..
+ ///
+ internal static string ImageIndexUploadedToRegistry {
+ get {
+ return ResourceManager.GetString("ImageIndexUploadedToRegistry", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to CONTAINER1009: Failed to load image from local registry. stdout: {0}.
///
@@ -357,6 +375,15 @@ internal static string ImagePullNotSupported {
}
}
+ ///
+ /// Looks up a localized string similar to Cannot create manifest list (image index) because no images were provided..
+ ///
+ internal static string ImagesEmpty {
+ get {
+ return ResourceManager.GetString("ImagesEmpty", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to CONTAINER2015: {0}: '{1}' was not a valid Environment Variable. Ignoring..
///
@@ -366,6 +393,15 @@ internal static string InvalidEnvVar {
}
}
+ ///
+ /// Looks up a localized string similar to Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata..
+ ///
+ internal static string InvalidImageMetadata {
+ get {
+ return ResourceManager.GetString("InvalidImageMetadata", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to CONTAINER2005: The inferred image name '{0}' contains entirely invalid characters. The valid characters for an image name are alphanumeric characters, -, /, or _, and the image name must start with an alphanumeric character..
///
@@ -447,6 +483,15 @@ internal static string InvalidTags {
}
}
+ ///
+ /// Looks up a localized string similar to Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none..
+ ///
+ internal static string InvalidTargetRuntimeIdentifiers {
+ get {
+ return ResourceManager.GetString("InvalidTargetRuntimeIdentifiers", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to CONTAINER1003: Token response had neither token nor access_token..
///
@@ -510,6 +555,15 @@ internal static string MissingPortNumber {
}
}
+ ///
+ /// Looks up a localized string similar to 'mediaType' of manifests should be the same in manifest list (image index)..
+ ///
+ internal static string MixedMediaTypes {
+ get {
+ return ResourceManager.GetString("MixedMediaTypes", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to CONTAINER1004: No RequestUri specified..
///
@@ -753,6 +807,15 @@ internal static string UnrecognizedMediaType {
}
}
+ ///
+ /// Looks up a localized string similar to Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'..
+ ///
+ internal static string UnsupportedMediaType {
+ get {
+ return ResourceManager.GetString("UnsupportedMediaType", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Unable to create tarball for mediaType '{0}'..
///
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx
index f25119f51fd4..6337f4861b8e 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx
@@ -201,6 +201,9 @@
CONTAINER2019: Invalid SDK semantic version '{0}'.{StrBegin="CONTAINER2019: "}
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
CONTAINER2018: Invalid SDK prerelease version '{0}' - only 'rc' and 'preview' are supported.{StrBegin="CONTAINER2018: "}
@@ -347,10 +350,36 @@
Pushed image '{0}' to registry '{1}'.
+
+ Pushed image index '{0}' to registry '{1}'.
+
+
Building image '{0}' with tags '{1}' on top of base image '{2}'.
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+
Error while reading daemon config: {0}{0} is the exception message that ends with period
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.cs.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.cs.xlf
index 625eab883915..f0d7173a9cea 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.cs.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.cs.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: Nepovedlo se nahrát objekt blob pomocí {0}; přijatý stavový kód {1}{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ Image {0} byla vložena do {1}.
@@ -152,6 +159,11 @@
Nebyl zjištěn žádný objekt hostitele.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: Nepodařilo se načíst bitovou kopii z místního registru. stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: Načítání imagí z místního registru se nepodporuje.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: '{1}' není platná proměnná prostředí. Ignorování.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: Odvozený název image „{0}“ obsahuje zcela neplatné znaky. Platné znaky pro název obrázku jsou alfanumerické znaky, -, /, nebo _, a název obrázku musí začínat alfanumerickým znakem.
@@ -212,6 +234,11 @@
CONTAINER2010: Byla zadána neplatná {0} : {1}. {0} musí být seznam platných značek obrázků oddělených středníky. Značky obrázků musí být alfanumerické, podtržítka, spojovníky nebo tečky.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: Odpověď tokenu neměla token ani access_token.
@@ -247,6 +274,11 @@
CONTAINER2016: Položka ContainerPort '{0}' neurčuje číslo portu. Ujistěte se prosím, že položka Include je číslo portu, například <ContainerPort Include="80" />.{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: Nebyl zadán žádný identifikátor RequestUri.
@@ -382,6 +414,11 @@
CONTAINER2001: Nerozpoznaný typ mediaType '{0}'.{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Unable to create tarball for mediaType '{0}'.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.de.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.de.xlf
index 6249a6147f07..ea8fc611e0a0 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.de.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.de.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: Fehler beim Hochladen des Blobs mit {0}; der Statuscode „{1}“ wurde empfangen.{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ Bild "{0}" wurde in {1} gepusht.
@@ -152,6 +159,11 @@
Es wurde kein Hostobjekt erkannt.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: Fehler beim Laden des Images aus der lokalen Registrierung. stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: Das Pullen von Images aus der lokalen Registrierung wird nicht unterstützt.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: „{1}“ war keine gültige Umgebungsvariable. Sie wird ignoriert.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: Der abgeleitete Imagename '{0}' enthält vollständig ungültige Zeichen. Die gültigen Zeichen für einen Bildnamen sind alphanumerische Zeichen, -, /, oder _, und der Bildname muss mit einem alphanumerischen Zeichen beginnen.
@@ -212,6 +234,11 @@
CONTAINER2010: Ungültige {0} angegeben: {1}. {0} muss eine durch Semikolons getrennte Liste gültiger Imagetags sein. Imagetags müssen alphanumerisch, Unterstrich, Bindestrich oder Punkt sein.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: Die Tokenantwort enthielt weder ein Token noch access_token.
@@ -247,6 +274,11 @@
CONTAINER2016: Das ContainerPort-Element „{0}“ gibt keine Portnummer an. Stellen Sie sicher, dass der Include des Elements eine Portnummer ist, z. B. „<ContainerPort Include="80" />“{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: Es wurde kein RequestUri angegeben.
@@ -382,6 +414,11 @@
CONTAINER2001: Unbekannter mediaType „{0}“.{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Unable to create tarball for mediaType '{0}'.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.es.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.es.xlf
index dcd6b7f40a0e..89f6aa5025c7 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.es.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.es.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: no se pudo cargar el blob mediante {0}; se ha recibido el código de estado "{1}".{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ Insertada la imagen “{0}” en {1}.
@@ -152,6 +159,11 @@
No se detectó ningún objeto host.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: no se pudo cargar la imagen desde el registro local. Stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: No se admite la extracción de imágenes del registro local.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: "{1}" no era una variable de entorno válida. Ignorando.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: el nombre de imagen inferido '{0}' contiene caracteres totalmente no válidos. Los caracteres válidos para un nombre de imagen son los caracteres alfanuméricos, -, /, o _; el nombre de imagen tiene que comenzar con uno.
@@ -212,6 +234,11 @@
CONTAINER2010: se proporcionó un {0} no válido: {1}. {0} debe ser una lista delimitada por punto y coma de etiquetas de imagen válidas. Las etiquetas de imagen deben ser alfanuméricas, con guion bajo, guiones o puntos.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: La respuesta del token no tenía ningún token ni access_token.
@@ -247,6 +274,11 @@
CONTAINER2016: El elemento ContainerPort "{0}" no especifica el número de puerto. Asegúrate de que la inclusión del elemento es un número de puerto, por ejemplo, "<ContainerPort Include="80" />"{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: No se especificó RequestUri.
@@ -382,6 +414,11 @@
CONTAINER2001: mediaType "{0}" no reconocido.{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ No se puede crear un tarball para el mediaType "{0}".
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.fr.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.fr.xlf
index 180aad48339d..9dcc070cb802 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.fr.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.fr.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: échec du chargement de l’objet blob à l’aide de {0}; le code d’état «{1}» a été reçu.{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ L’image '{0}' a été envoyée à {1}.
@@ -152,6 +159,11 @@
Aucun objet hôte détecté.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: Échec du chargement de l'image à partir du registre local. sortie standard : {0}
@@ -162,11 +174,21 @@
CONTAINER1010: L'extraction d'images à partir du registre local n'est pas prise en charge.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0} : '{1}' n’était pas une variable d’environnement valide. Ignorant.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: le nom d'image déduit '{0}' contient des caractères entièrement non valides. Les caractères valides pour un nom d'image sont les caractères alphanumériques, -, / ou _, et le nom de l'image doit commencer par un caractère alphanumérique.
@@ -212,6 +234,11 @@
CONTAINER2010: {0} non valide fournie : {1}. {0} doit être une liste de balises d’image valides délimitées par des points-virgules. Les balises d’image doivent être alphanumériques, traits de soulignement, traits d’union ou point.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: la réponse de jeton n’avait ni jeton ni access_token.
@@ -247,6 +274,11 @@
CONTAINER2016: l’élément ContainerPort '{0}' ne spécifie pas le numéro de port. Vérifiez que l’élément Include est un numéro de port, par exemple '<ContainerPort Include="80" />'{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: aucun RequestUri spécifié.
@@ -382,6 +414,11 @@
CONTAINER2001: '{0}' mediaType non reconnu.{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Impossible de créer tarball pour mediaType '{0}'.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.it.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.it.xlf
index 3c108c9d90d3..7ab0ca10be53 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.it.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.it.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: non è stato possibile caricare il BLOB usando {0}; codice di stato ricevuto '{1}'.{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ È stato eseguito il push dell'immagine '{0}' in {1}.
@@ -152,6 +159,11 @@
Nessun oggetto host rilevato.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: non è stato possibile caricare l'immagine dal registro locale. stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: il pull di immagini dal registro locale non è supportato.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: '{1}' non è una variabile di ambiente valida. Il valore verrà ignorato.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: il nome dell'immagine dedotto '{0}' contiene caratteri completamente non validi. I caratteri validi per un nome di immagine sono caratteri alfanumerici, -, / o _, e il nome dell'immagine deve iniziare con un carattere alfanumerico.
@@ -212,6 +234,11 @@
CONTAINER2010: il valore {0} specificato non è valido: {1}. {0} deve essere un elenco delimitato da punto e virgola di tag di immagine validi. I tag immagine devono essere alfanumerici, di sottolineatura, trattino o punto.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: la risposta del token non contiene né token né access_token.
@@ -247,6 +274,11 @@
CONTAINER2016: l'elemento ContainerPort '{0}' non specifica il numero di porta. Assicurarsi che il valore Include dell'elemento sia un numero di porta, ad esempio '<ContainerPort Include="80" />'{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: nessun RequestUri specificato.
@@ -382,6 +414,11 @@
CONTAINER2001: mediaType '{0}' non riconosciuto.{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Impossibile creare il tarball per un mediaType '{0}'.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ja.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ja.xlf
index 88cb18140bcd..9c7ebe912fd4 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ja.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ja.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: {0} を使用して BLOB をアップロードできませんでした; 状態コード '{1}' を受信しました。{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ イメージ '{0}' を {1} にプッシュしました。
@@ -152,6 +159,11 @@
ホスト オブジェクトが検出されませんでした。
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: ローカル レジストリからイメージを読み込めませんでした。stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: ローカル レジストリからのイメージのプルはサポートされていません。{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: '{1}' は有効な環境変数ではありませんでした。無視しています。{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: 推定されたイメージ名 '{0}' に、完全に無効な文字が含まれています。イメージ名に有効な文字は英数字、-、/、または _で、イメージ名の先頭には英数字を使用する必要があります。
@@ -212,6 +234,11 @@
CONTAINER2010: 無効な {0} が指定されました: {1}。{0} は、セミコロンで区切られた有効なイメージ タグのリストである必要があります。イメージ タグは、英数字、アンダースコア、ハイフン、またはピリオドである必要があります。{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: トークン応答にトークンも access_token もありませんでした。
@@ -247,6 +274,11 @@
CONTAINER2016: ContainerPort 項目 '{0}' でポート番号が指定されていません。項目の Include がポート番号 (e '<ContainerPort Include="80" />' など) であることを確認してください{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: RequestUri が指定されていません。
@@ -382,6 +414,11 @@
CONTAINER2001: 認識されない mediaType '{0}' です。{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ mediaType '{0}' の tarball を作成できません。
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ko.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ko.xlf
index e0738ef570c3..2ff624cec676 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ko.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ko.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: {0}을(를) 사용하여 Blob을 업로드하지 못했습니다. '{1}' 상태 코드를 수신했습니다.{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ '{0}' 이미지를 {1}에 푸시했습니다.
@@ -152,6 +159,11 @@
호스트 개체가 검색되지 않았습니다.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: 로컬 레지스트리에서 이미지를 로드하지 못했습니다. stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: 로컬 레지스트리에서 이미지 끌어오기가 지원되지 않습니다.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: '{1}'은(는) 유효한 환경 변수가 아닙니다. 무시 중.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: 유추된 이미지 이름 '{0}'에 완전히 잘못된 문자가 포함되어 있습니다. 이미지 이름의 유효한 문자는 영숫자, -, /또는 _이며 이미지 이름은 영숫자 문자로 시작해야 합니다.
@@ -212,6 +234,11 @@
CONTAINER2010: 잘못된 {0}이(가) 제공됨: {1}. {0}은(는) 세미콜론으로 구분된 유효한 이미지 태그 목록이어야 합니다. 이미지 태그는 영숫자, 밑줄, 하이픈 또는 마침표여야 합니다.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: 토큰 응답에 토큰이나 access_token이 없습니다.
@@ -247,6 +274,11 @@
CONTAINER2016: ContainerPort 항목 '{0}'이(가) 포트 번호를 지정하지 않습니다. 항목의 포함이 포트 번호인지 확인하세요(예: '<ContainerPort Include="80" />').{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: RequestUri가 지정되지 않았습니다.
@@ -382,6 +414,11 @@
CONTAINER2001: 미디어 유형 '{0}'을(를) 인식할 수 없습니다.{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Unable to create tarball for mediaType '{0}'.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.pl.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.pl.xlf
index 988eec6e8358..ee74c48962b2 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.pl.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.pl.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: nie można przekazać obiektu blob przy użyciu {0}; odebrano kod stanu „{1}”.{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ Wypchnięty obraz „{0}” do „{1}”.
@@ -152,6 +159,11 @@
Nie wykryto obiektu hosta.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: Nie można załadować obrazu z rejestru lokalnego. stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: Ściąganie obrazów z rejestru lokalnego nie jest obsługiwane.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: „{1}” nie jest prawidłową zmienną środowiskową. Ignorowanie.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: Wywnioskowana nazwa obrazu „{0}” zawiera całkowicie nieprawidłowe znaki. Prawidłowe znaki nazwy obrazu to znaki alfanumeryczne oraz —, /, lub _, a nazwa obrazu musi zaczynać się znakiem alfanumerycznym.
@@ -212,6 +234,11 @@
CONTAINER2010: podano nieprawidłowy {0}: {1}. {0} musi być rozdzielaną średnikami listą prawidłowych tagów obrazów. Tagi obrazów muszą być alfanumeryczne, zawierać podkreślenia, łączniki lub kropki.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: odpowiedź tokenu nie miała tokenu ani access_token.
@@ -247,6 +274,11 @@
CONTAINER2016: element ContainerPort „{0}” nie określa numeru portu. Upewnij się, że element Include jest numerem portu, na przykład „<ContainerPort Include="80" />”{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: nie określono identyfikatora RequestUri.
@@ -382,6 +414,11 @@
CONTAINER2001: nierozpoznany typ nośnika „{0}”.{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Nie można utworzyć elementu tarball dla elementu mediaType „{0}”.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.pt-BR.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.pt-BR.xlf
index 194d82ddefb8..cefb88d7f7e6 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.pt-BR.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: Falha ao carregar o blob usando {0}; código de status recebido '{1}'.{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ Imagem enviada '{0}' para {1}.
@@ -152,6 +159,11 @@
Nenhum objeto de host detectado.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: falha ao carregar a imagem do registro local. stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: A extração de imagens do registro local não é suportada.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: '{1}' não era uma variável de ambiente válida. Ignorando.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: o nome da imagem inferida '{0}' contém caracteres totalmente inválidos. Os caracteres válidos para um nome de imagem são caracteres alfanuméricos, -, / ou _, e o nome da imagem deve começar com um caractere alfanumérico.
@@ -212,6 +234,11 @@
CONTAINER2010: inválido {0} fornecido: {1}. {0} deve ser uma lista delimitada por ponto-e-vírgula de marcas de imagem válidas. As tags de imagem devem ser alfanuméricas, sublinhado, hífen ou ponto.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: A resposta do token não tinha token nem access_token.
@@ -247,6 +274,11 @@
CONTAINER2016: O item ContainerPort '{0}' não especifica o número da porta. Certifique-se de que o Include do item seja um número de porta, por exemplo '<ContainerPort Include="80" />'{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: Nenhum RequestUri especificado.
@@ -382,6 +414,11 @@
CONTAINER2001: MediaType não reconhecido '{0}'.{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Não é possível criar tarball para mediaType "{0}".
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ru.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ru.xlf
index 2c54887d3d89..43563d4a7d84 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ru.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.ru.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: не удалось отправить BLOB-объект с помощью {0}; получен код состояния "{1}".{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ Изображение "{0}" отправлено в {1}.
@@ -152,6 +159,11 @@
Объект узла не обнаружен.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: не удалось загрузить образ из локального реестра. stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: извлечение образов из локального реестра не поддерживается.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: "{1}" не является допустимой переменной среды. Пропуск.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: Предполагаемое имя изображения "{0}" содержит совершенно недопустимые символы. Допустимыми символами для имени изображения являются буквенно-цифровые символы, -, / или _, а имя изображения должно начинаться с буквенно-цифрового символа.
@@ -212,6 +234,11 @@
CONTAINER2010: предоставлен недопустимый {0}: {1}. {0} должен быть списком допустимых тегов изображений, разделенных точкой с запятой. В качестве тегов изображений допускаются буквы, цифры, символы подчеркивания, дефисы и точки.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: ответ токена не содержит маркера и access_token.
@@ -247,6 +274,11 @@
CONTAINER2016: элемент ContainerPort "{0}" не указывает номер порта. Убедитесь, что include элемента является номером порта, например "<ContainerPort Include="80" />"{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: не указан RequestUri.
@@ -382,6 +414,11 @@
CONTAINER2001: нераспознанный тип мультимедиа "{0}".{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Не удалось создать tarball для mediaType "{0}".
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.tr.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.tr.xlf
index 646791f2d72a..e08a51a49a71 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.tr.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.tr.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: Blob, {0} kullanarak karşıya yüklenemedi; '{1}' durum kodu alındı.{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ '{0}' görüntüsü {1} konumuna gönderildi.
@@ -152,6 +159,11 @@
Ana bilgisayar nesnesi algılanmadı.
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: Görüntü yerel kayıt defterinden yüklenemedi. stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: Yerel kayıt defterinden görüntü çekme desteklenmiyor.{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: '{1}' geçerli bir Ortam Değişkeni değildi. Görmezden geliniyor.{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: Çıkarsanan '{0}' görüntü adı tamamen geçersiz karakterler içeriyor. Bir görüntü adında geçerli karakterler şunlardan oluşur: alfasayısal karakterler, -, /, veya _. Görüntü adı alfasayısal karakterle başlamalıdır.
@@ -212,6 +234,11 @@
CONTAINER2010: Geçersiz {0} sağlandı: {1}. {0}, geçerli resim etiketlerinin noktalı virgülle ayrılmış listesi olmalıdır. Resim etiketleri alfasayısal, alt çizgi, kısa çizgi veya nokta olmalıdır.{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: Belirteç yanıtında belirteç veya access_token yok.
@@ -247,6 +274,11 @@
CONTAINER2016: ContainerPort öğesi ('{0}'), bağlantı noktası numarasını belirtmiyor. Lütfen öğenin Include değerinin bir bağlantı noktası numarası olduğundan emin olun, örneğin '<ContainerPort Include="80" />'{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: RequestUri belirtilmedi.
@@ -382,6 +414,11 @@
CONTAINER2001: Tanınmayan mediaType ('{0}').{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ mediaType '{0}' için tarball oluşturulamıyor.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.zh-Hans.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.zh-Hans.xlf
index 175a9084fc20..8eb6429f7db6 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.zh-Hans.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: 无法使用 {0} 上传 blob;已收到状态代码“{1}”。{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ 已将图像“{0}”推送到 {1}。
@@ -152,6 +159,11 @@
未检测到主机对象。
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: 未能从本地注册表加载映像。stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: 不支持从本地注册表拉取映像。{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: "{1}" 不是有效的环境变量。忽略。{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: 推断的图像名称“{0}”包含完全无效的字符。图像名称的有效字符包括字母数字字符、-、/ 或 _,图像名称必须以字母数字字符开头。
@@ -212,6 +234,11 @@
CONTAINER2010: 提供的 {0} 无效: {1}。{0} 必须是有效图像标记的分号分隔列表。图像标记必须是字母数字、下划线、连字符或句点。{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: 令牌响应既没有令牌,也没有access_token。
@@ -247,6 +274,11 @@
CONTAINER2016: ContainerPort 项“{0}”未指定端口号。请确保项的 Include 是端口号,例如 "<ContainerPort Include="80" />"{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: 未指定 RequestUri。
@@ -382,6 +414,11 @@
CONTAINER2001: 无法识别 mediaType“{0}”。{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Unable to create tarball for mediaType '{0}'.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.zh-Hant.xlf b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.zh-Hant.xlf
index 0333461575af..892b174bbe97 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.zh-Hant.xlf
@@ -57,6 +57,13 @@
CONTAINER1001: 無法使用 {0} 上傳 blob; 收到狀態碼 '{1}'。{StrBegin="CONTAINER1001: "}
+
+
+ Building image index '{0}' on top of manifests {1}.
+
+ {0} is the name of the image index and its tag, {1} is the list of manifests digests
+
+ 已將影像 '{0}' 推送至 {1}。
@@ -152,6 +159,11 @@
未偵測到主機物件。
+
+
+ Pushed image index '{0}' to registry '{1}'.
+
+ CONTAINER1009: 無法從本機登錄載入映像。stdout: {0}
@@ -162,11 +174,21 @@
CONTAINER1010: 不支援從本機登錄提取映像。{StrBegin="CONTAINER1010: "}
+
+
+ Cannot create manifest list (image index) because no images were provided.
+
+ CONTAINER2015: {0}: '{1}' 不是有效的環境變數。正在忽略。{StrBegin="CONTAINER2015: "}
+
+
+ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.
+
+ CONTAINER2005: 推斷的映像名稱 '{0}' 包含完全無效字元。映像名稱的有效字元是英數字元、-、/ 或 _,並且映像名稱必須以英數字元開頭。
@@ -212,6 +234,11 @@
CONTAINER2010: 提供的 {0} 無效: {1}。{0} 必須是有效映像標記的分號分隔清單。映像標記必須是英數字元、底線、連字號或句號。{StrBegin="CONTAINER2010: "}
+
+
+ Invalid string[] TargetRuntimeIdentifiers. Either all should be 'linux-musl' or none.
+
+ CONTAINER1003: 權杖回應沒有權杖,也沒有access_token。
@@ -247,6 +274,11 @@
CONTAINER2016: ContainerPort 項目 '{0}' 未指定連接埠號碼。請確保項目的 Include 是連接埠號碼,例如 '<ContainerPort Include="80" />'{StrBegin="CONTAINER2016: "}
+
+
+ 'mediaType' of manifests should be the same in manifest list (image index).
+
+ CONTAINER1004: 未指定 RequestUri。
@@ -382,6 +414,11 @@
CONTAINER2001: 無法辨識的 mediaType '{0}'。{StrBegin="CONTAINER2001: "}
+
+
+ Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.
+
+ Unable to create tarball for mediaType '{0}'.
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs
index 836b44e90751..b5573582900b 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs
@@ -44,7 +44,7 @@ public sealed class ComputeDotnetBaseImageAndTag : Microsoft.Build.Utilities.Tas
/// If this is set to linux-musl-ARCH then we need to use `alpine` for all containers, and tag on `aot` or `extra` as necessary.
///
[Required]
- public string TargetRuntimeIdentifier { get; set; }
+ public string[] TargetRuntimeIdentifiers { get; set; }
///
/// If a project is self-contained then it includes a runtime, and so the runtime-deps image should be used.
@@ -84,7 +84,7 @@ public sealed class ComputeDotnetBaseImageAndTag : Microsoft.Build.Utilities.Tas
FrameworkReferences.Length > 0
&& FrameworkReferences.Any(x => x.ItemSpec.Equals("Microsoft.AspNetCore.App", StringComparison.OrdinalIgnoreCase));
- private bool IsMuslRid => TargetRuntimeIdentifier.StartsWith("linux-musl", StringComparison.Ordinal);
+ private bool IsMuslRid;
private bool IsBundledRuntime => IsSelfContained;
private bool RequiresInference => String.IsNullOrEmpty(UserBaseImage);
@@ -99,7 +99,7 @@ public ComputeDotnetBaseImageAndTag()
TargetFrameworkVersion = "";
ContainerFamily = "";
FrameworkReferences = [];
- TargetRuntimeIdentifier = "";
+ TargetRuntimeIdentifiers = [];
UserBaseImage = "";
}
@@ -113,16 +113,39 @@ public override bool Execute()
}
else
{
- var defaultRegistry = RegistryConstants.MicrosoftContainerRegistryDomain;
- if (ComputeRepositoryAndTag(out var repository, out var tag))
+ if (TargetRuntimeIdentiriersAreValid())
{
- ComputedContainerBaseImage = $"{defaultRegistry}/{repository}:{tag}";
- LogInferencePerformedTelemetry($"{defaultRegistry}/{repository}", tag!);
+ var defaultRegistry = RegistryConstants.MicrosoftContainerRegistryDomain;
+ if (ComputeRepositoryAndTag(out var repository, out var tag))
+ {
+ ComputedContainerBaseImage = $"{defaultRegistry}/{repository}:{tag}";
+ LogInferencePerformedTelemetry($"{defaultRegistry}/{repository}", tag!);
+ }
}
return !Log.HasLoggedErrors;
}
}
+ private bool TargetRuntimeIdentiriersAreValid()
+ {
+ // For "linux-musl" RIDs we choose the alpine base image.
+ // And because we compute the base image only once, we need to ensure that all RIDs are "linux-musl" or none of them.
+ var muslRidsCount = TargetRuntimeIdentifiers.Count(rid => rid.StartsWith("linux-musl", StringComparison.Ordinal));
+ if (muslRidsCount > 0)
+ {
+ if (muslRidsCount == TargetRuntimeIdentifiers.Length)
+ {
+ IsMuslRid = true;
+ }
+ else
+ {
+ Log.LogError(Resources.Strings.InvalidTargetRuntimeIdentifiers);
+ return false;
+ }
+ }
+ return true;
+ }
+
private string UbuntuCodenameForSDKVersion(SemanticVersion version)
{
if (version >= SemanticVersion.Parse("8.0.300"))
@@ -314,14 +337,14 @@ private void LogNoInferencePerformedTelemetry()
containerFamily = ContainerFamily;
}
}
- var telemetryData = new InferenceTelemetryData(InferencePerformed: false, TargetFramework: ParseSemVerToMajorMinor(TargetFrameworkVersion), userBaseImage, userTag, containerFamily, GetTelemetryProjectType(), GetTelemetryPublishMode(), UsesInvariantGlobalization, TargetRuntimeIdentifier);
+ var telemetryData = new InferenceTelemetryData(InferencePerformed: false, TargetFramework: ParseSemVerToMajorMinor(TargetFrameworkVersion), userBaseImage, userTag, containerFamily, GetTelemetryProjectType(), GetTelemetryPublishMode(), UsesInvariantGlobalization, TargetRuntimeIdentifiers);
LogTelemetryData(telemetryData);
}
private void LogInferencePerformedTelemetry(string imageName, string tag)
{
// for all inference use cases we will use .NET's images, so we can safely log name, tag, and family
- var telemetryData = new InferenceTelemetryData(InferencePerformed: true, TargetFramework: ParseSemVerToMajorMinor(TargetFrameworkVersion), imageName, tag, String.IsNullOrEmpty(ContainerFamily) ? null : ContainerFamily, GetTelemetryProjectType(), GetTelemetryPublishMode(), UsesInvariantGlobalization, TargetRuntimeIdentifier);
+ var telemetryData = new InferenceTelemetryData(InferencePerformed: true, TargetFramework: ParseSemVerToMajorMinor(TargetFrameworkVersion), imageName, tag, String.IsNullOrEmpty(ContainerFamily) ? null : ContainerFamily, GetTelemetryProjectType(), GetTelemetryPublishMode(), UsesInvariantGlobalization, TargetRuntimeIdentifiers);
LogTelemetryData(telemetryData);
}
@@ -342,7 +365,7 @@ private void LogTelemetryData(InferenceTelemetryData telemetryData)
{ nameof(telemetryData.ProjectType), telemetryData.ProjectType.ToString() },
{ nameof(telemetryData.PublishMode), telemetryData.PublishMode.ToString() },
{ nameof(telemetryData.IsInvariant), telemetryData.IsInvariant.ToString() },
- { nameof(telemetryData.TargetRuntime), telemetryData.TargetRuntime }
+ { nameof(telemetryData.TargetRuntimes), string.Join(";", telemetryData.TargetRuntimes) }
};
Log.LogTelemetry("sdk/container/inference", telemetryProperties);
}
@@ -359,8 +382,8 @@ private void LogTelemetryData(InferenceTelemetryData telemetryData)
/// Classifies the project into categories - currently only the broad categories of web/console are known.
/// Categorizes the publish mode of the app - FDD, SC, Trimmed, AOT in rough order of complexity/container customization
/// We make inference decisions on the invariant-ness of the project, so it's useful to track how often that is used.
- /// Different RIDs change the inference calculation, so it's useful to know how different RIDs flow into the results of inference.
- private record class InferenceTelemetryData(bool InferencePerformed, string TargetFramework, string? BaseImage, string? BaseImageTag, string? ContainerFamily, ProjectType ProjectType, PublishMode PublishMode, bool IsInvariant, string TargetRuntime);
+ /// Different RIDs change the inference calculation, so it's useful to know how different RIDs flow into the results of inference.
+ private record class InferenceTelemetryData(bool InferencePerformed, string TargetFramework, string? BaseImage, string? BaseImageTag, string? ContainerFamily, ProjectType ProjectType, PublishMode PublishMode, bool IsInvariant, string[] TargetRuntimes);
private enum ProjectType
{
AspNetCore,
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs
new file mode 100644
index 000000000000..0894686603d2
--- /dev/null
+++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs
@@ -0,0 +1,169 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using Microsoft.Build.Framework;
+using Microsoft.Extensions.Logging;
+using Microsoft.NET.Build.Containers.Logging;
+using Microsoft.NET.Build.Containers.Resources;
+using ILogger = Microsoft.Extensions.Logging.ILogger;
+using Task = System.Threading.Tasks.Task;
+
+namespace Microsoft.NET.Build.Containers.Tasks;
+
+public sealed class CreateImageIndex : Microsoft.Build.Utilities.Task, ICancelableTask, IDisposable
+{
+ #region Parameters
+ ///
+ /// Manifests to include in the image index.
+ ///
+ [Required]
+ public ITaskItem[] GeneratedContainers { get; set; }
+
+ ///
+ /// The registry to push the image index to.
+ ///
+ [Required]
+ public string OutputRegistry { get; set; }
+
+ ///
+ /// The name of the output image index (manifest list) that will be pushed to the registry.
+ ///
+ [Required]
+ public string Repository { get; set; }
+
+ ///
+ /// The tag to associate with the new image index (manifest list).
+ ///
+ [Required]
+ public string[] ImageTags { get; set; }
+
+ ///
+ /// The generated image index (manifest list) in JSON format.
+ ///
+ [Output]
+ public string GeneratedImageIndex { get; set; }
+
+ public CreateImageIndex()
+ {
+ GeneratedContainers = Array.Empty();
+ OutputRegistry = string.Empty;
+ Repository = string.Empty;
+ ImageTags = Array.Empty();
+ GeneratedImageIndex = string.Empty;
+ }
+ #endregion
+
+ private readonly CancellationTokenSource _cancellationTokenSource = new();
+
+ public void Cancel() => _cancellationTokenSource.Cancel();
+
+ public void Dispose()
+ {
+ _cancellationTokenSource.Dispose();
+ }
+
+ public override bool Execute()
+ {
+ try
+ {
+ Task.Run(() => ExecuteAsync(_cancellationTokenSource.Token)).GetAwaiter().GetResult();
+ }
+ catch (TaskCanceledException ex)
+ {
+ Log.LogWarningFromException(ex);
+ }
+ catch (OperationCanceledException ex)
+ {
+ Log.LogWarningFromException(ex);
+ }
+ return !Log.HasLoggedErrors;
+ }
+
+ internal async Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var images = ParseImages();
+ if (Log.HasLoggedErrors)
+ {
+ return false;
+ }
+
+ using MSBuildLoggerProvider loggerProvider = new(Log);
+ ILoggerFactory msbuildLoggerFactory = new LoggerFactory(new[] { loggerProvider });
+ ILogger logger = msbuildLoggerFactory.CreateLogger();
+
+ logger.LogInformation(Strings.BuildingImageIndex, GetRepositoryAndTagsString(), string.Join(", ", images.Select(i => i.ManifestDigest)));
+
+ try
+ {
+ (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images);
+
+ GeneratedImageIndex = imageIndex;
+
+ await PushToRemoteRegistry(GeneratedImageIndex, mediaType, logger, cancellationToken);
+ }
+ catch (ContainerHttpException e)
+ {
+ if (BuildEngine != null)
+ {
+ Log.LogErrorFromException(e, true);
+ }
+ }
+ catch (ArgumentException ex)
+ {
+ Log.LogErrorFromException(ex);
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ private ImageInfo[] ParseImages()
+ {
+ var images = new ImageInfo[GeneratedContainers.Length];
+
+ for (int i = 0; i < GeneratedContainers.Length; i++)
+ {
+ var unparsedImage = GeneratedContainers[i];
+
+ string config = unparsedImage.GetMetadata("Configuration");
+ string manifestDigest = unparsedImage.GetMetadata("ManifestDigest");
+ string manifest = unparsedImage.GetMetadata("Manifest");
+ string manifestMediaType = unparsedImage.GetMetadata("ManifestMediaType");
+
+ if (string.IsNullOrEmpty(config) || string.IsNullOrEmpty(manifestDigest) || string.IsNullOrEmpty(manifest))
+ {
+ Log.LogError(Strings.InvalidImageMetadata, unparsedImage.ItemSpec);
+ break;
+ }
+
+ images[i] = new ImageInfo
+ {
+ Config = config,
+ ManifestDigest = manifestDigest,
+ Manifest = manifest,
+ ManifestMediaType = manifestMediaType
+ };
+ }
+
+ return images;
+ }
+
+ private async Task PushToRemoteRegistry(string manifestList, string mediaType, ILogger logger, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ Debug.Assert(ImageTags.Length > 0);
+ var registry = new Registry(OutputRegistry, logger, RegistryMode.Push);
+ await registry.PushManifestListAsync(Repository, ImageTags, manifestList, mediaType, cancellationToken).ConfigureAwait(false);
+ logger.LogInformation(Strings.ImageIndexUploadedToRegistry, GetRepositoryAndTagsString(), OutputRegistry);
+ }
+
+ private string? _repositoryAndTagsString = null;
+
+ private string GetRepositoryAndTagsString()
+ {
+ _repositoryAndTagsString ??= $"{Repository}:{string.Join(", ", ImageTags)}";
+ return _repositoryAndTagsString;
+ }
+}
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs
index 4f69658c66d2..3da5135b9b07 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs
@@ -173,6 +173,9 @@ partial class CreateNewImage
[Output]
public string GeneratedArchiveOutputPath { get; set; }
+ [Output]
+ public string GeneratedContainerMediaType { get; set; }
+
[Output]
public ITaskItem[] GeneratedContainerNames { get; set; }
@@ -208,6 +211,7 @@ public CreateNewImage()
GeneratedContainerManifest = "";
GeneratedContainerDigest = "";
GeneratedArchiveOutputPath = "";
+ GeneratedContainerMediaType = "";
GeneratedContainerNames = Array.Empty();
GenerateLabels = false;
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
index aa81ae801953..34c4984ae0bb 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
@@ -174,6 +174,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken)
GeneratedContainerConfiguration = builtImage.Config;
GeneratedContainerDigest = builtImage.Manifest.GetDigest();
GeneratedArchiveOutputPath = ArchiveOutputPath;
+ GeneratedContainerMediaType = builtImage.ManifestMediaType;
GeneratedContainerNames = destinationImageReference.FullyQualifiedImageNames().Select(name => new Microsoft.Build.Utilities.TaskItem(name)).ToArray();
switch (destinationImageReference.Kind)
diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.props b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.props
index 4abb098d7c82..80bb59569dd0 100644
--- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.props
+++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.props
@@ -15,6 +15,7 @@
+
diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets
index 11589947cc51..e9e52d5dfece 100644
--- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets
+++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets
@@ -34,14 +34,21 @@
+ $(RuntimeIdentifiers)
- $(RuntimeIdentifier)
- linux-$(NETCoreSdkPortableRuntimeIdentifier.Split('-')[1])
+ $(RuntimeIdentifier)
+ linux-$(NETCoreSdkPortableRuntimeIdentifier.Split('-')[1])
+
<_ContainerIsUsingMicrosoftDefaultImages Condition="'$(ContainerBaseImage)' == ''">true
<_ContainerIsUsingMicrosoftDefaultImages Condition="'$(ContainerBaseImage)' != ''">false
+
+ <_TargetRuntimeIdentifiers Include="$(ContainerRuntimeIdentifiers)" Condition="'$(ContainerRuntimeIdentifiers)' != ''" />
+ <_TargetRuntimeIdentifiers Include="$(ContainerRuntimeIdentifier)" Condition="'$(ContainerRuntimeIdentifiers)' == ''" />
+
+
+
+
+ <_TargetRuntimeIdentifiers Remove ="$(_TargetRuntimeIdentifiers)" />
+
@@ -99,9 +110,9 @@
-
+
-
+
@@ -229,10 +240,8 @@
-
-
+ $(NetCoreRoot)dotnet
@@ -271,7 +280,133 @@
+
+
+
+
+ $(GeneratedContainerManifest)
+ $(GeneratedContainerConfiguration)
+ $(GeneratedContainerDigest)
+ $(GeneratedContainerMediaType)
+
+
+
+
+
+
+
+
+ <_rids Include="$(ContainerRuntimeIdentifiers)" Condition="'$(ContainerRuntimeIdentifiers)' != ''" />
+ <_rids Include="$(RuntimeIdentifiers)" Condition="'$(ContainerRuntimeIdentifiers)' == '' and '$(RuntimeIdentifiers)' != ''" />
+ <_InnerBuild
+ Include="$(MSBuildProjectFullPath)"
+ AdditionalProperties="
+ ContainerRuntimeIdentifier=%(_rids.Identity);
+ RuntimeIdentifier=%(_rids.Identity);
+ ContainerBaseRegistry=$(ContainerBaseRegistry);
+ ContainerBaseName=$(ContainerBaseName);
+ ContainerBaseTag=$(ContainerBaseTag);
+ ContainerRegistry=$(ContainerRegistry);
+ _ContainerImageTags=@(ContainerImageTags, ';');
+ ContainerRepository=$(ContainerRepository);
+ ContainerWorkingDirectory=$(ContainerWorkingDirectory);
+ _ContainerEntrypoint=@(ContainerEntrypoint, ';');
+ _ContainerEntrypointArgs=@(ContainerEntrypointArgs, ';');
+ _ContainerAppCommand=@(ContainerAppCommand, ';');
+ _ContainerAppCommandArgs=@(ContainerAppCommandArgs, ';');
+ ContainerAppCommandInstruction=$(ContainerAppCommandInstruction);
+ _ContainerDefaultArgs=@(ContainerDefaultArgs, ';');
+ _ContainerLabel=@(ContainerLabel->'%(Identity):%(Value)');
+ _ContainerPort=@(ContainerPort->'%(Identity):%(Type)');
+ _ContainerEnvironmentVariables=@(ContainerEnvironmentVariable->'%(Identity):%(Value)');
+ ContainerUser=$(ContainerUser);
+ ContainerGenerateLabels=$(ContainerGenerateLabels);
+ ContainerGenerateLabelsImageBaseDigest=$(ContainerGenerateLabelsImageBaseDigest)
+ "/>
+ <_rids Remove ="$(_rids)" />
+
+
+
+
+
+
+
+ <_SkipCreateImageIndex>false
+ <_SkipCreateImageIndex Condition="'$(ContainerRegistry)' == ''">true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ParsedContainerLabel
+ Condition="'$(_ContainerLabel)' != ':'"
+ Include="$(_ContainerLabel)"/>
+
+
+ <_ParsedContainerPort
+ Condition="'$(_ContainerPort)' != ':'"
+ Include="$(_ContainerPort)"/>
+
+
+ <_ParsedContainerEnvironmentVariables
+ Condition="'$(_ContainerEnvironmentVariables)' != ':'"
+ Include="$(_ContainerEnvironmentVariables)"/>
+
+
+
+
+ $([System.IO.Path]::Combine($(ContainerArchiveOutputPath), $(ContainerRepository)-$(ContainerRuntimeIdentifier).tar.gz))
+
+
+
+
+ <_IsMultiTFMBuild Condition="'$(TargetFrameworks)' != '' and '$(TargetFramework)' == ''">true
+ <_IsMultiRIDBuild Condition="'$(BuildingInsideVisualStudio)' != 'true' and (('$(RuntimeIdentifiers)' != '' and '$(RuntimeIdentifier)' == '') or ('$(ContainerRuntimeIdentifiers)' != '' and '$(ContainerRuntimeIdentfier)' == ''))">true
+ <_IsSingleRIDBuild Condition="'$(_IsMultiRIDBuild)' == ''">true
+
+
+
+
+
+
+
+
+
diff --git a/src/SourceBuild/content/eng/Versions.props b/src/SourceBuild/content/eng/Versions.props
index 97eea60b3146..4d98dab1f710 100644
--- a/src/SourceBuild/content/eng/Versions.props
+++ b/src/SourceBuild/content/eng/Versions.props
@@ -23,8 +23,8 @@
of a .NET major or minor release, prebuilts may be needed. When the release is mature, prebuilts
are not necessary, and this property is removed from the file.
-->
- 9.0.100
- 9.0.100-rtm.24529.1
+ 9.0.101
+ 9.0.101-servicing.24575.12.0.0-beta4.24126.1
diff --git a/src/SourceBuild/content/global.json b/src/SourceBuild/content/global.json
index 044f23f6237a..dad59de52dde 100644
--- a/src/SourceBuild/content/global.json
+++ b/src/SourceBuild/content/global.json
@@ -1,6 +1,6 @@
{
"tools": {
- "dotnet": "9.0.100"
+ "dotnet": "9.0.101"
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.7.0",
diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs
index 82ccc3d4a2c9..a83e24239f15 100644
--- a/test/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs
+++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs
@@ -7,6 +7,8 @@ static class ContainerCli
{
public static bool IsPodman => _isPodman.Value;
+ public static bool IsAvailable => _isAvailable.Value;
+
public static RunExeCommand PullCommand(ITestOutputHelper log, params string[] args)
=> CreateCommand(log, "pull", args);
@@ -60,4 +62,7 @@ private static RunExeCommand CreateCommand(ITestOutputHelper log, string command
private static readonly Lazy _isPodman =
new(() => new DockerCli(loggerFactory: new TestLoggerFactory()).GetCommand() == DockerCli.PodmanCommand);
+
+ private static readonly Lazy _isAvailable =
+ new(() => new DockerCli(loggerFactory: new TestLoggerFactory()).IsAvailable());
}
diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs
new file mode 100644
index 000000000000..2615b2e178ee
--- /dev/null
+++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs
@@ -0,0 +1,169 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using FakeItEasy;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.NET.Build.Containers.IntegrationTests;
+using Microsoft.NET.Build.Containers.UnitTests;
+using NuGet.Protocol;
+using Task = System.Threading.Tasks.Task;
+
+namespace Microsoft.NET.Build.Containers.Tasks.IntegrationTests;
+
+[Collection("Docker tests")]
+public class CreateImageIndexTests
+{
+ private ITestOutputHelper _testOutput;
+
+ public CreateImageIndexTests(ITestOutputHelper testOutput)
+ {
+ _testOutput = testOutput;
+ }
+
+ [DockerAvailableFact]
+ public async Task CreateImageIndex_Baseline()
+ {
+ DirectoryInfo newProjectDir = CreateNewProject();
+ (IBuildEngine buildEngine, List errors) = SetupBuildEngine();
+ string outputRegistry = DockerRegistryManager.LocalRegistry;
+ string repository = "dotnet/create-image-index-baseline";
+ string[] tags = new[] { "tag1", "tag2" };
+
+ // Create images for 2 rids
+ TaskItem image1 = PublishAndCreateNewImage("linux-x64", outputRegistry, repository, tags, newProjectDir, buildEngine, errors);
+ TaskItem image2 = PublishAndCreateNewImage("linux-arm64", outputRegistry, repository, tags, newProjectDir, buildEngine, errors);
+
+ // Create image index
+ CreateImageIndex cii = new();
+ cii.BuildEngine = buildEngine;
+ cii.OutputRegistry = outputRegistry;
+ cii.Repository = repository;
+ cii.ImageTags = tags;
+ cii.GeneratedContainers = [image1, image2];
+ Assert.True(cii.Execute(), FormatBuildMessages(errors));
+
+ // Assert that the image index is created correctly
+ cii.GeneratedImageIndex.Should().NotBeNullOrEmpty();
+ var imageIndex = cii.GeneratedImageIndex.FromJson();
+ imageIndex.manifests.Should().HaveCount(2);
+
+ imageIndex.manifests[0].digest.Should().Be(image1.GetMetadata("ManifestDigest"));
+ imageIndex.manifests[0].platform.os.Should().Be("linux");
+ imageIndex.manifests[0].platform.architecture.Should().Be("amd64");
+
+ imageIndex.manifests[1].digest.Should().Be(image2.GetMetadata("ManifestDigest"));
+ imageIndex.manifests[1].platform.os.Should().Be("linux");
+ imageIndex.manifests[1].platform.architecture.Should().Be("arm64");
+
+ // Assert that the image index is pushed to the registry
+ var loggerFactory = new TestLoggerFactory(_testOutput);
+ var logger = loggerFactory.CreateLogger(nameof(CreateImageIndex_Baseline));
+ Registry registry = new(outputRegistry, logger, RegistryMode.Pull);
+
+ await AssertThatImageIsReferencedInImageIndex("linux-x64", repository, tags, registry);
+ await AssertThatImageIsReferencedInImageIndex("linux-arm64", repository, tags, registry);
+
+ newProjectDir.Delete(true);
+ }
+
+ private DirectoryInfo CreateNewProject()
+ {
+ DirectoryInfo newProjectDir = new(GetTestDirectoryName());
+ if (newProjectDir.Exists)
+ {
+ newProjectDir.Delete(recursive: true);
+ }
+ newProjectDir.Create();
+ new DotnetNewCommand(_testOutput, "console", "-f", ToolsetInfo.CurrentTargetFramework)
+ .WithVirtualHive()
+ .WithWorkingDirectory(newProjectDir.FullName)
+ .Execute()
+ .Should().Pass();
+ return newProjectDir;
+ }
+
+ private TaskItem PublishAndCreateNewImage(
+ string rid,
+ string outputRegistry,
+ string repository,
+ string[] tags,
+ DirectoryInfo newProjectDir,
+ IBuildEngine buildEngine,
+ List errors)
+ {
+ new DotnetCommand(_testOutput, "publish", "-c", "Release", "-r", rid, "--no-self-contained")
+ .WithWorkingDirectory(newProjectDir.FullName)
+ .Execute()
+ .Should().Pass();
+
+ CreateNewImage cni = new();
+
+ cni.BuildEngine = buildEngine;
+
+ cni.BaseRegistry = "mcr.microsoft.com";
+ cni.BaseImageName = "dotnet/runtime";
+ cni.BaseImageTag = "7.0";
+
+ cni.OutputRegistry = outputRegistry;
+ cni.LocalRegistry = DockerAvailableFactAttribute.LocalRegistry;
+ cni.PublishDirectory = Path.Combine(newProjectDir.FullName, "bin", "Release", ToolsetInfo.CurrentTargetFramework, rid, "publish");
+ cni.Repository = repository;
+ cni.ImageTags = tags.Select(t => $"{t}-{rid}").ToArray();
+ cni.WorkingDirectory = "app/";
+ cni.ContainerRuntimeIdentifier = rid;
+ cni.Entrypoint = new TaskItem[] { new("dotnet"), new("build") };
+ cni.RuntimeIdentifierGraphPath = ToolsetUtils.GetRuntimeGraphFilePath();
+
+ Assert.True(cni.Execute(), FormatBuildMessages(errors));
+
+ TaskItem generatedContainer = new("GeneratedContainer" + rid);
+ generatedContainer.SetMetadata("Manifest", cni.GeneratedContainerManifest);
+ generatedContainer.SetMetadata("Configuration", cni.GeneratedContainerConfiguration);
+ generatedContainer.SetMetadata("ManifestDigest", cni.GeneratedContainerDigest);
+ generatedContainer.SetMetadata("ManifestMediaType", cni.GeneratedContainerMediaType);
+
+ return generatedContainer;
+ }
+
+ private async Task AssertThatImageIsReferencedInImageIndex(string rid, string repository, string[] tags, Registry registry)
+ {
+ foreach (var tag in tags)
+ {
+ var individualImage = await registry.GetImageManifestAsync(
+ repository,
+ $"{tag}-{rid}",
+ rid,
+ ToolsetUtils.RidGraphManifestPicker,
+ cancellationToken: default).ConfigureAwait(false);
+ individualImage.Should().NotBeNull();
+
+ var imageFromImageIndex = await registry.GetImageManifestAsync(
+ repository,
+ tag,
+ rid,
+ ToolsetUtils.RidGraphManifestPicker,
+ cancellationToken: default).ConfigureAwait(false);
+ imageFromImageIndex.Should().NotBeNull();
+
+ imageFromImageIndex.ManifestConfigDigest.Should().Be(individualImage.ManifestConfigDigest);
+ }
+ }
+
+ private static (IBuildEngine buildEngine, List errors) SetupBuildEngine()
+ {
+ List errors = new();
+ IBuildEngine buildEngine = A.Fake();
+ A.CallTo(() => buildEngine.LogWarningEvent(A.Ignored)).Invokes((BuildWarningEventArgs e) => errors.Add(e.Message));
+ A.CallTo(() => buildEngine.LogErrorEvent(A.Ignored)).Invokes((BuildErrorEventArgs e) => errors.Add(e.Message));
+ A.CallTo(() => buildEngine.LogMessageEvent(A.Ignored)).Invokes((BuildMessageEventArgs e) => errors.Add(e.Message));
+
+ return (buildEngine, errors);
+ }
+
+ private static string GetTestDirectoryName([CallerMemberName] string testName = "DefaultTest") => Path.Combine(TestSettings.TestArtifactsDirectory, testName + "_" + DateTime.Now.ToString("yyyyMMddHHmmss"));
+
+ private static string FormatBuildMessages(List messages) => string.Join("\r\n", messages);
+}
+
diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs
index 66733c52896c..0161ee485886 100644
--- a/test/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs
+++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs
@@ -153,7 +153,7 @@ public void Tasks_EndToEnd_With_EnvironmentVariable_Validation()
File.WriteAllText(Path.Combine(newProjectDir.FullName, "Program.cs"), $"Console.Write(Environment.GetEnvironmentVariable(\"GoodEnvVar\"));");
- new DotnetCommand(_testOutput, "build", "--configuration", "release", "/p:runtimeidentifier=linux-x64", $"/p:RuntimeFrameworkVersion={DockerRegistryManager.RuntimeFrameworkVersion}")
+ new DotnetCommand(_testOutput, "build", "--configuration", "release", "/p:runtimeidentifier=linux-x64")
.WithWorkingDirectory(newProjectDir.FullName)
.Execute()
.Should().Pass();
@@ -162,7 +162,7 @@ public void Tasks_EndToEnd_With_EnvironmentVariable_Validation()
(IBuildEngine buildEngine, List errors) = SetupBuildEngine();
pcp.BuildEngine = buildEngine;
- pcp.FullyQualifiedBaseImageName = $"mcr.microsoft.com/{DockerRegistryManager.RuntimeBaseImage}:{DockerRegistryManager.Net9PreviewImageTag}";
+ pcp.FullyQualifiedBaseImageName = $"mcr.microsoft.com/{DockerRegistryManager.RuntimeBaseImage}:{DockerRegistryManager.Net9ImageTag}";
pcp.ContainerRegistry = "";
pcp.ContainerRepository = "dotnet/envvarvalidation";
pcp.ContainerImageTag = "latest";
@@ -175,7 +175,7 @@ public void Tasks_EndToEnd_With_EnvironmentVariable_Validation()
Assert.True(pcp.Execute(), FormatBuildMessages(errors));
Assert.Equal("mcr.microsoft.com", pcp.ParsedContainerRegistry);
Assert.Equal("dotnet/runtime", pcp.ParsedContainerImage);
- Assert.Equal(DockerRegistryManager.Net9PreviewImageTag, pcp.ParsedContainerTag);
+ Assert.Equal(DockerRegistryManager.Net9ImageTag, pcp.ParsedContainerTag);
Assert.Single(pcp.NewContainerEnvironmentVariables);
Assert.Equal("Foo", pcp.NewContainerEnvironmentVariables[0].GetMetadata("Value"));
diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs
index 5734483b579e..7c460b5aad66 100644
--- a/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs
+++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs
@@ -15,12 +15,11 @@ public class DockerRegistryManager
public const string Net6ImageTag = "6.0";
public const string Net7ImageTag = "7.0";
public const string Net8ImageTag = "8.0";
- public const string Net9PreviewImageTag = "9.0-preview";
- public const string RuntimeFrameworkVersion = "9.0.0-preview.3.24172.9";
+ public const string Net9ImageTag = "9.0";
public const string Net8PreviewWindowsSpecificImageTag = $"{Net8ImageTag}-nanoserver-ltsc2022";
public const string LocalRegistry = "localhost:5010";
- public const string FullyQualifiedBaseImageDefault = $"{BaseImageSource}/{RuntimeBaseImage}:{Net9PreviewImageTag}";
- public const string FullyQualifiedBaseImageAspNet = $"{BaseImageSource}/{AspNetBaseImage}:{Net9PreviewImageTag}";
+ public const string FullyQualifiedBaseImageDefault = $"{BaseImageSource}/{RuntimeBaseImage}:{Net9ImageTag}";
+ public const string FullyQualifiedBaseImageAspNet = $"{BaseImageSource}/{AspNetBaseImage}:{Net9ImageTag}";
private static string? s_registryContainerId;
internal class SameArchManifestPicker : IManifestPicker
@@ -72,7 +71,7 @@ public static async Task StartAndPopulateDockerRegistry(ITestOutputHelper testOu
EnsureRegistryLoaded(new Uri($"http://{LocalRegistry}"), s_registryContainerId, logger, testOutput);
- foreach (string? tag in new[] { Net6ImageTag, Net7ImageTag, Net8ImageTag, Net9PreviewImageTag })
+ foreach (string? tag in new[] { Net6ImageTag, Net7ImageTag, Net8ImageTag, Net9ImageTag })
{
logger.LogInformation("Pulling image '{repo}/{image}:{tag}'.", BaseImageSource, RuntimeBaseImage, tag);
string dotnetdll = System.Reflection.Assembly.GetExecutingAssembly().Location;
diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs
new file mode 100644
index 000000000000..36ad9fbc0c9a
--- /dev/null
+++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.Build.Containers.IntegrationTests;
+
+public class DockerIsAvailableAndSupportsArchFactAttribute : FactAttribute
+{
+ public DockerIsAvailableAndSupportsArchFactAttribute(string arch)
+ {
+ if (!DockerSupportsArchHelper.DaemonIsAvailable)
+ {
+ base.Skip = "Skipping test because Docker is not available on this host.";
+ }
+ else if (!DockerSupportsArchHelper.DaemonSupportsArch(arch))
+ {
+ base.Skip = $"Skipping test because Docker daemon does not support {arch}.";
+ }
+ }
+}
diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs
index bf5b8d4bd65d..caafbaea4b45 100644
--- a/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs
+++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs
@@ -9,12 +9,6 @@ namespace Microsoft.NET.Build.Containers.IntegrationTests;
public class DockerSupportsArchInlineData : DataAttribute
{
- // an optimization - this doesn't change over time so we can compute it once
- private static string[] LinuxPlatforms = GetSupportedLinuxPlatforms();
-
- // another optimization - daemons don't switch types easily or quickly, so this is as good as static
- private static bool IsWindowsDockerDaemon = GetIsWindowsDockerDaemon();
-
private readonly string _arch;
private readonly object[] _data;
@@ -26,26 +20,40 @@ public DockerSupportsArchInlineData(string arch, params object[] data)
public override IEnumerable