From 81bcbf3a23c7cc576659d900486de23e13b22de2 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Tue, 28 Jan 2025 20:46:40 +0100 Subject: [PATCH 01/50] fix e2e test after checking option 'Use containerd for pulling and storing images' --- .../EndToEndTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 3b0015a10f0a..1cef040a5e34 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -1033,6 +1033,8 @@ public void EndToEndMultiArch_RemoteRegistry() CommandResult processResultX64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/amd64", "--name", $"test-container-{imageName}-x64", imageX64Tagged) @@ -1056,6 +1058,8 @@ public void EndToEndMultiArch_RemoteRegistry() CommandResult processResultArm64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/arm64", "--name", $"test-container-{imageName}-arm64", imageArm64Tagged) From 01a9c07e64901baf22a85ab43ff99c76cdecc992 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 29 Jan 2025 13:24:00 +0100 Subject: [PATCH 02/50] refactor BuiltImage --- .../Microsoft.NET.Build.Containers/BuiltImage.cs | 15 ++++++++++----- .../ImageBuilder.cs | 8 +++++--- .../LocalDaemons/DockerCli.cs | 13 +++++-------- .../Registry/Registry.cs | 8 +++----- .../Tasks/CreateNewImage.cs | 4 ++-- .../EndToEndTests.cs | 3 ++- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs index 64d4e9a528ef..c4572b878340 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs @@ -24,20 +24,25 @@ internal readonly struct BuiltImage internal required string ImageSha { get; init; } /// - /// Gets image size. + /// Gets image manifest. /// - internal required long ImageSize { get; init; } + internal required string Manifest { get; init; } /// - /// Gets image manifest. + /// Gets manifest digest. /// - internal required ManifestV2 Manifest { get; init; } + internal required string ManifestDigest { get; init; } /// /// Gets manifest mediaType. /// internal required string ManifestMediaType { get; init; } + /// + /// Gets image layers. + /// + internal required List Layers { get; init; } + /// /// Gets layers descriptors. /// @@ -45,7 +50,7 @@ internal IEnumerable LayerDescriptors { get { - List layersNode = Manifest.Layers ?? throw new NotImplementedException("Tried to get layer information but there is no layer node?"); + List layersNode = Layers ?? throw new NotImplementedException("Tried to get layer information but there is no layer node?"); foreach (ManifestLayer layer in layersNode) { yield return new(layer.mediaType, layer.digest, layer.size); diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs index bf0876344f28..633ca8e98860 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs @@ -5,6 +5,7 @@ using Microsoft.NET.Build.Containers.Resources; using Microsoft.Extensions.Logging; using System.Text.RegularExpressions; +using System.Text.Json; namespace Microsoft.NET.Build.Containers; @@ -86,9 +87,10 @@ internal BuiltImage Build() Config = imageJsonStr, ImageDigest = imageDigest, ImageSha = imageSha, - ImageSize = imageSize, - Manifest = newManifest, - ManifestMediaType = ManifestMediaType + Manifest = JsonSerializer.SerializeToNode(newManifest)?.ToJsonString() ?? "", + ManifestDigest = newManifest.GetDigest(), + ManifestMediaType = ManifestMediaType, + Layers = _manifest.Layers }; } diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 7e779e9ec3c0..dc151bd7836f 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -279,7 +279,7 @@ public static async Task WriteImageToStreamAsync(BuiltImage image, SourceImageRe } else { - throw new ArgumentException(Resource.FormatString(nameof(Strings.UnsupportedMediaTypeForTarball), image.Manifest.MediaType)); + throw new ArgumentException(Resource.FormatString(nameof(Strings.UnsupportedMediaTypeForTarball), image.ManifestMediaType)); } } @@ -441,12 +441,9 @@ private static async Task WriteManifestForOciImage( { cancellationToken.ThrowIfCancellationRequested(); - string manifestContent = JsonSerializer.SerializeToNode(image.Manifest)!.ToJsonString(); - string manifestDigest = image.Manifest.GetDigest(); - // 1. add manifest to blobs - string manifestPath = $"{_blobsPath}/{manifestDigest.Substring("sha256:".Length)}"; - using (MemoryStream manifestStream = new MemoryStream(Encoding.UTF8.GetBytes(manifestContent))) + string manifestPath = $"{_blobsPath}/{image.ManifestDigest.Substring("sha256:".Length)}"; + using (MemoryStream manifestStream = new MemoryStream(Encoding.UTF8.GetBytes(image.Manifest))) { PaxTarEntry manifestEntry = new(TarEntryType.RegularFile, manifestPath) { @@ -467,8 +464,8 @@ private static async Task WriteManifestForOciImage( new PlatformSpecificOciManifest { mediaType = SchemaTypes.OciManifestV1, - size = manifestContent.Length, - digest = manifestDigest, + size = image.Manifest.Length, + digest = image.ManifestDigest, annotations = new Dictionary { { "org.opencontainers.image.ref.name", $"{destinationReference.Repository}:{destinationReference.Tags[0]}" } } } ] diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs index f09cfd6fd4a3..5ab07dc9713e 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs @@ -602,22 +602,20 @@ 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, manifestJson, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false); + await _registryAPI.Manifest.PutAsync(destination.Repository, tag, builtImage.Manifest, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false); _logger.LogInformation(Strings.Registry_TagUploaded, tag, RegistryName); } } else { - string manifestDigest = builtImage.Manifest.GetDigest(); - _logger.LogInformation(Strings.Registry_ManifestUploadStarted, RegistryName, manifestDigest); - await _registryAPI.Manifest.PutAsync(destination.Repository, manifestDigest, manifestJson, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false); + _logger.LogInformation(Strings.Registry_ManifestUploadStarted, RegistryName, builtImage.ManifestDigest); + await _registryAPI.Manifest.PutAsync(destination.Repository, builtImage.ManifestDigest, builtImage.Manifest, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false); _logger.LogInformation(Strings.Registry_ManifestUploaded, RegistryName); } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index 25ba26080af9..7368fd337596 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -170,9 +170,9 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); // at this point we're done with modifications and are just pushing the data other places - GeneratedContainerManifest = JsonSerializer.Serialize(builtImage.Manifest); + GeneratedContainerManifest = builtImage.Manifest; GeneratedContainerConfiguration = builtImage.Config; - GeneratedContainerDigest = builtImage.Manifest.GetDigest(); + GeneratedContainerDigest = builtImage.ManifestDigest; GeneratedArchiveOutputPath = ArchiveOutputPath; GeneratedContainerMediaType = builtImage.ManifestMediaType; GeneratedContainerNames = destinationImageReference.FullyQualifiedImageNames().Select(name => new Microsoft.Build.Utilities.TaskItem(name)).ToArray(); diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 1cef040a5e34..2d562b4feaa7 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -235,9 +235,10 @@ private BuiltImage ConvertToOciImage(BuiltImage builtImage) Config = builtImage.Config, ImageDigest = builtImage.ImageDigest, ImageSha = builtImage.ImageSha, - ImageSize = builtImage.ImageSize, Manifest = builtImage.Manifest, + ManifestDigest = builtImage.ManifestDigest, ManifestMediaType = SchemaTypes.OciManifestV1, + Layers = builtImage.Layers }; return ociImage; From d973c0e7d210d71d922f6b33e43c92a968519f8a Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 29 Jan 2025 18:43:37 +0100 Subject: [PATCH 03/50] refactor single-arch oci tarball publishing --- .../LocalDaemons/DockerCli.cs | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index dc151bd7836f..68e06d044c3c 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -7,6 +7,7 @@ #endif using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using Microsoft.DotNet.Cli.Utils; using Microsoft.Extensions.Logging; using Microsoft.NET.Build.Containers.Resources; @@ -396,12 +397,13 @@ private static async Task WriteOciImageToStreamAsync( Stream imageStream, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + if (destinationReference.Tags.Length > 1) { throw new ArgumentException(Resource.FormatString(nameof(Strings.OciImageMultipleTagsNotSupported))); } - cancellationToken.ThrowIfCancellationRequested(); using TarWriter writer = new(imageStream, TarEntryFormat.Pax, leaveOpen: true); await WriteOciLayout(writer, cancellationToken) @@ -413,7 +415,10 @@ await WriteImageLayers(writer, image, sourceReference, d => $"{_blobsPath}/{d.Su await WriteImageConfig(writer, image, $"{_blobsPath}/{image.ImageSha}", cancellationToken) .ConfigureAwait(false); - await WriteManifestForOciImage(writer, image, destinationReference, cancellationToken) + await WriteManifestForOciImage(writer, image, cancellationToken) + .ConfigureAwait(false); + + await WriteIndexJsonForOciImage(writer, image, destinationReference, cancellationToken) .ConfigureAwait(false); } @@ -436,12 +441,10 @@ private static async Task WriteOciLayout(TarWriter writer, CancellationToken can private static async Task WriteManifestForOciImage( TarWriter writer, BuiltImage image, - DestinationImageReference destinationReference, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - // 1. add manifest to blobs string manifestPath = $"{_blobsPath}/{image.ManifestDigest.Substring("sha256:".Length)}"; using (MemoryStream manifestStream = new MemoryStream(Encoding.UTF8.GetBytes(image.Manifest))) { @@ -451,10 +454,18 @@ private static async Task WriteManifestForOciImage( }; await writer.WriteEntryAsync(manifestEntry, cancellationToken).ConfigureAwait(false); } + } + private static async Task WriteIndexJsonForOciImage( + TarWriter writer, + BuiltImage image, + DestinationImageReference destinationReference, + CancellationToken cancellationToken) + { cancellationToken.ThrowIfCancellationRequested(); - // 2. add index.json + string tag = destinationReference.Tags[0]; + var index = new ImageIndexV1 { schemaVersion = 2, @@ -466,11 +477,20 @@ private static async Task WriteManifestForOciImage( mediaType = SchemaTypes.OciManifestV1, size = image.Manifest.Length, digest = image.ManifestDigest, - annotations = new Dictionary { { "org.opencontainers.image.ref.name", $"{destinationReference.Repository}:{destinationReference.Tags[0]}" } } + annotations = new Dictionary + { + { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, + { "org.opencontainers.image.ref.name", tag } + } } ] }; - using (MemoryStream indexStream = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.SerializeToNode(index)!.ToJsonString()))) + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(JsonSerializer.SerializeToNode(index, options)!.ToJsonString()))) { PaxTarEntry indexEntry = new(TarEntryType.RegularFile, "index.json") { From 2363f9449ef36156c88ab30a450a80a91b74e08f Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 31 Jan 2025 13:00:23 +0100 Subject: [PATCH 04/50] implement loading multi-arch image as a tarball and to a local registry --- .../BuiltImage.cs | 10 ++ .../DigestUtils.cs | 10 ++ .../LocalDaemons/ArchiveFileRegistry.cs | 29 ++-- .../LocalDaemons/DockerCli.cs | 152 ++++++++++++++++-- .../LocalDaemons/ILocalRegistry.cs | 5 + 5 files changed, 183 insertions(+), 23 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs index c4572b878340..84d3b11c42c9 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs @@ -43,6 +43,16 @@ internal readonly struct BuiltImage /// internal required List Layers { get; init; } + /// + /// Gets image OS. + /// + internal string OS { get; init; } + + /// + /// Gets image architecture. + /// + internal string Architecture { get; init; } + /// /// Gets layers descriptors. /// diff --git a/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs b/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs index e549defd4635..4171589c025d 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs @@ -17,6 +17,16 @@ internal sealed class DigestUtils /// internal static string GetDigestFromSha(string sha) => $"sha256:{sha}"; + internal static string GetShaFromDigest(string digest) + { + if (!digest.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Invalid digest format. Digest must start with 'sha256:'."); + } + + return digest.Substring("sha256:".Length); + } + /// /// Gets the SHA of . /// diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs index a12d15790d38..5af3edd5a546 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Extensions.Logging; using Microsoft.NET.Build.Containers.Resources; namespace Microsoft.NET.Build.Containers.LocalDaemons; @@ -15,9 +14,9 @@ public ArchiveFileRegistry(string archiveOutputPath) ArchiveOutputPath = archiveOutputPath; } - public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReference, - DestinationImageReference destinationReference, - CancellationToken cancellationToken) + private async Task LoadAsync(T image, SourceImageReference sourceReference, + DestinationImageReference destinationReference, CancellationToken cancellationToken, + Func writeStreamFunc) { var fullPath = Path.GetFullPath(ArchiveOutputPath); @@ -36,19 +35,27 @@ public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReferen ArchiveOutputPath = fullPath; await using var fileStream = File.Create(fullPath); - await DockerCli.WriteImageToStreamAsync( - image, - sourceReference, - destinationReference, - fileStream, - cancellationToken).ConfigureAwait(false); + + // Call the delegate to write the image to the stream + await writeStreamFunc(image, sourceReference, destinationReference, fileStream, cancellationToken).ConfigureAwait(false); } + public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReference, + DestinationImageReference destinationReference, + CancellationToken cancellationToken) + => await LoadAsync(image, sourceReference, destinationReference, cancellationToken, + DockerCli.WriteImageToStreamAsync); + + public async Task LoadAsync(BuiltImage[] images, SourceImageReference sourceReference, + DestinationImageReference destinationReference, + CancellationToken cancellationToken) + => await LoadAsync(images, sourceReference, destinationReference, cancellationToken, + DockerCli.WriteMultiArchOciImageToStreamAsync); + public Task IsAvailableAsync(CancellationToken cancellationToken) => Task.FromResult(true); public bool IsAvailable() => true; - public override string ToString() { return string.Format(Strings.ArchiveRegistry_PushInfo, ArchiveOutputPath); diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 68e06d044c3c..112887aa5a6e 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -83,7 +83,12 @@ private async ValueTask FindFullCommandPath(CancellationToken cancellati return _fullCommandPath; } - public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) + private async Task LoadAsync( + T image, + SourceImageReference sourceReference, + DestinationImageReference destinationReference, + Func writeStreamFunc, + CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -95,16 +100,12 @@ public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReferen loadInfo.RedirectStandardOutput = true; loadInfo.RedirectStandardError = true; - using Process? loadProcess = Process.Start(loadInfo); - - if (loadProcess is null) - { + using Process? loadProcess = Process.Start(loadInfo) ?? throw new NotImplementedException(Resource.FormatString(Strings.ContainerRuntimeProcessCreationFailed, commandPath)); - } - // Create new stream tarball - // We want to be able to export to docker, even oci images. - await WriteDockerImageToStreamAsync(image, sourceReference, destinationReference, loadProcess.StandardInput.BaseStream, cancellationToken).ConfigureAwait(false); + // Call the delegate to write the image to the stream + await writeStreamFunc(image, sourceReference, destinationReference, loadProcess.StandardInput.BaseStream, cancellationToken) + .ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -120,6 +121,12 @@ public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReferen } } + public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) + => await LoadAsync(image, sourceReference, destinationReference, WriteDockerImageToStreamAsync, cancellationToken); + + public async Task LoadAsync(BuiltImage[] images, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) + => await LoadAsync(images, sourceReference, destinationReference, WriteMultiArchOciImageToStreamAsync, cancellationToken); + public async Task IsAvailableAsync(CancellationToken cancellationToken) { bool commandPathWasUnknown = this._command is null; // avoid running the version command twice. @@ -390,6 +397,35 @@ private static async Task WriteManifestForDockerImage( } } + public static async Task WriteMultiArchOciImageToStreamAsync( + BuiltImage[] images, + SourceImageReference sourceReference, + DestinationImageReference destinationReference, + Stream imageStream, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (destinationReference.Tags.Length > 1) + { + throw new ArgumentException(Resource.FormatString(nameof(Strings.OciImageMultipleTagsNotSupported))); + } + + using TarWriter writer = new(imageStream, TarEntryFormat.Pax, leaveOpen: true); + + foreach (var image in images) + { + await WriteOciImageToBlobs(writer, image, sourceReference, cancellationToken) + .ConfigureAwait(false); + } + + await WriteIndexJsonForMultiArchOciImage(writer, images, destinationReference, cancellationToken) + .ConfigureAwait(false); + + await WriteOciLayout(writer, cancellationToken) + .ConfigureAwait(false); + } + private static async Task WriteOciImageToStreamAsync( BuiltImage image, SourceImageReference sourceReference, @@ -406,9 +442,22 @@ private static async Task WriteOciImageToStreamAsync( using TarWriter writer = new(imageStream, TarEntryFormat.Pax, leaveOpen: true); + await WriteOciImageToBlobs(writer, image, sourceReference, cancellationToken) + .ConfigureAwait(false); + + await WriteIndexJsonForOciImage(writer, image, destinationReference, cancellationToken) + .ConfigureAwait(false); + await WriteOciLayout(writer, cancellationToken) .ConfigureAwait(false); + } + private static async Task WriteOciImageToBlobs( + TarWriter writer, + BuiltImage image, + SourceImageReference sourceReference, + CancellationToken cancellationToken) + { await WriteImageLayers(writer, image, sourceReference, d => $"{_blobsPath}/{d.Substring("sha256:".Length)}", cancellationToken) .ConfigureAwait(false); @@ -417,9 +466,6 @@ await WriteImageConfig(writer, image, $"{_blobsPath}/{image.ImageSha}", cancella await WriteManifestForOciImage(writer, image, cancellationToken) .ConfigureAwait(false); - - await WriteIndexJsonForOciImage(writer, image, destinationReference, cancellationToken) - .ConfigureAwait(false); } private static async Task WriteOciLayout(TarWriter writer, CancellationToken cancellationToken) @@ -456,6 +502,88 @@ private static async Task WriteManifestForOciImage( } } + private static async Task WriteIndexJsonForMultiArchOciImage( + TarWriter writer, + BuiltImage[] images, + DestinationImageReference destinationReference, + CancellationToken cancellationToken) + { + // 1. create manifest list for the blobs + cancellationToken.ThrowIfCancellationRequested(); + + string tag = destinationReference.Tags[0]; + + var manifests = new PlatformSpecificOciManifest[images.Length]; + for (int i = 0; i < images.Length; i++) + { + var manifest = new PlatformSpecificOciManifest + { + mediaType = SchemaTypes.OciManifestV1, + size = images[i].Manifest.Length, + digest = images[i].ManifestDigest, + platform = new PlatformInformation { architecture = images[i].Architecture, os = images[i].OS } + }; + manifests[i] = manifest; + } + + var manifestList = new ImageIndexV1 + { + schemaVersion = 2, + mediaType = SchemaTypes.OciImageIndexV1, + manifests = manifests + }; + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + var manifestListJson = JsonSerializer.SerializeToNode(manifestList, options)!.ToJsonString(); + var manifestListDigest = DigestUtils.GetDigest(manifestListJson); + var manifestListSha = DigestUtils.GetShaFromDigest(manifestListDigest); + var manifestListPath = $"{_blobsPath}/{manifestListSha}"; + + using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(manifestListJson))) + { + PaxTarEntry indexEntry = new(TarEntryType.RegularFile, manifestListPath) + { + DataStream = indexStream + }; + await writer.WriteEntryAsync(indexEntry, cancellationToken).ConfigureAwait(false); + } + + // 2. create index json that points to manifest list in the blobs + cancellationToken.ThrowIfCancellationRequested(); + + var index = new ImageIndexV1 + { + schemaVersion = 2, + mediaType = SchemaTypes.OciImageIndexV1, + manifests = + [ + new PlatformSpecificOciManifest + { + mediaType = SchemaTypes.OciImageIndexV1, + size = manifestListJson.Length, + digest = manifestListDigest, + annotations = new Dictionary + { + { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, + { "org.opencontainers.image.ref.name", tag } + }, + } + ] + }; + + using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(JsonSerializer.SerializeToNode(index, options)!.ToJsonString()))) + { + PaxTarEntry indexEntry = new(TarEntryType.RegularFile, "index.json") + { + DataStream = indexStream + }; + await writer.WriteEntryAsync(indexEntry, cancellationToken).ConfigureAwait(false); + } + } + private static async Task WriteIndexJsonForOciImage( TarWriter writer, BuiltImage image, diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ILocalRegistry.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ILocalRegistry.cs index c06150fe2425..34ccb13d093d 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ILocalRegistry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ILocalRegistry.cs @@ -13,6 +13,11 @@ internal interface ILocalRegistry { /// public Task LoadAsync(BuiltImage image, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken); + /// + /// Loads a multi-arch image (presumably from a tarball) into the local registry. + /// + public Task LoadAsync(BuiltImage[] images, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken); + /// /// Checks to see if the local registry is available. This is used to give nice errors to the user. /// From 068d0738769c1a62b27014b484df9817f952abfc Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 31 Jan 2025 17:55:09 +0100 Subject: [PATCH 05/50] implement publishing to tarball/local daemon/remote registry in CreateImageIndex --- .../BuiltImage.cs | 2 +- .../ImageIndexGenerator.cs | 42 +-- .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 12 + .../Registry/Registry.cs | 2 - .../Tasks/CreateImageIndex.cs | 322 ++++++++++++++++-- .../Microsoft.NET.Build.Containers.targets | 15 +- .../EndToEndTests.cs | 6 +- .../ImageIndexGeneratorTests.cs | 150 ++++---- 8 files changed, 394 insertions(+), 157 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs index 84d3b11c42c9..0a76191db727 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs @@ -41,7 +41,7 @@ internal readonly struct BuiltImage /// /// Gets image layers. /// - internal required List Layers { get; init; } + internal List? Layers { get; init; } /// /// Gets image OS. diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index bc23073080ea..b0cedc4a60af 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -23,31 +23,31 @@ 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) + internal static (string, string) GenerateImageIndex(BuiltImage[] images) { - if (imageInfos.Length == 0) + if (images.Length == 0) { throw new ArgumentException(string.Format(Strings.ImagesEmpty)); } - string manifestMediaType = imageInfos[0].ManifestMediaType; + string manifestMediaType = images[0].ManifestMediaType; - if (!imageInfos.All(image => string.Equals(image.ManifestMediaType, manifestMediaType, StringComparison.OrdinalIgnoreCase))) + if (!images.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); + return GenerateImageIndex(images, SchemaTypes.DockerManifestV2, SchemaTypes.DockerManifestListV2); } else if (manifestMediaType == SchemaTypes.OciManifestV1) { - return GenerateImageIndex(imageInfos, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1); + return GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1); } else { @@ -55,10 +55,10 @@ internal static (string, string) GenerateImageIndex(ImageInfo[] imageInfos) } } - private static (string, string) GenerateImageIndex(ImageInfo[] images, string manifestMediaType, string imageIndexMediaType) + private static (string, string) GenerateImageIndex(BuiltImage[] 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. + // 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++) { @@ -69,32 +69,22 @@ private static (string, string) GenerateImageIndex(ImageInfo[] images, string ma mediaType = manifestMediaType, size = image.Manifest.Length, digest = image.ManifestDigest, - platform = GetArchitectureAndOsFromConfig(image) + platform = new PlatformInformation + { + architecture = image.Architecture, + os = image.OS + } }; manifests[i] = manifest; } - var dockerManifestList = new ManifestListV2 + var manifestList = 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 }; + return (JsonSerializer.SerializeToNode(manifestList)?.ToJsonString() ?? "", manifestList.mediaType); } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt index b19e4fa81718..f7e2955bd01f 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -19,6 +19,18 @@ Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UserBaseImage. 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.BaseRegistry.get -> string! +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.BaseRegistry.set -> void +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.BaseImageName.get -> string! +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.BaseImageName.set -> void +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.BaseImageTag.get -> string! +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.BaseImageTag.set -> void +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.ArchiveOutputPath.get -> string! +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.ArchiveOutputPath.set -> void +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.LocalRegistry.get -> string! +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.LocalRegistry.set -> void +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedArchiveOutputPath.get -> string! +Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedArchiveOutputPath.set -> void Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Cancel() -> void Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.CreateImageIndex() -> void Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.Dispose() -> void diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs index 5ab07dc9713e..2e754986334a 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs @@ -3,11 +3,9 @@ using Microsoft.Extensions.Logging; using Microsoft.NET.Build.Containers.Resources; -using NuGet.Packaging; using NuGet.RuntimeModel; using System.Diagnostics; using System.Net.Http.Json; -using System.Text.Json; using System.Text.Json.Nodes; namespace Microsoft.NET.Build.Containers; diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 0894686603d2..09128838f212 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.Build.Framework; using Microsoft.Extensions.Logging; +using Microsoft.NET.Build.Containers.LocalDaemons; using Microsoft.NET.Build.Containers.Logging; using Microsoft.NET.Build.Containers.Resources; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -14,6 +17,27 @@ namespace Microsoft.NET.Build.Containers.Tasks; public sealed class CreateImageIndex : Microsoft.Build.Utilities.Task, ICancelableTask, IDisposable { #region Parameters + /// + /// The base registry to pull from. + /// Ex: mcr.microsoft.com + /// + [Required] + public string BaseRegistry { get; set; } + + /// + /// The base image to pull. + /// Ex: dotnet/runtime + /// + [Required] + public string BaseImageName { get; set; } + + /// + /// The base image tag. + /// Ex: 6.0 + /// + [Required] + public string BaseImageTag { get; set; } + /// /// Manifests to include in the image index. /// @@ -23,9 +47,18 @@ public sealed class CreateImageIndex : Microsoft.Build.Utilities.Task, ICancelab /// /// The registry to push the image index to. /// - [Required] public string OutputRegistry { get; set; } + /// + /// The file path to which to write a tar.gz archive of the container image. + /// + public string ArchiveOutputPath { get; set; } + + /// + /// The kind of local registry to use, if any. + /// + public string LocalRegistry { get; set; } + /// /// The name of the output image index (manifest list) that will be pushed to the registry. /// @@ -38,6 +71,9 @@ public sealed class CreateImageIndex : Microsoft.Build.Utilities.Task, ICancelab [Required] public string[] ImageTags { get; set; } + [Output] + public string GeneratedArchiveOutputPath { get; set; } + /// /// The generated image index (manifest list) in JSON format. /// @@ -46,10 +82,16 @@ public sealed class CreateImageIndex : Microsoft.Build.Utilities.Task, ICancelab public CreateImageIndex() { + BaseRegistry = string.Empty; + BaseImageName = string.Empty; + BaseImageTag = string.Empty; GeneratedContainers = Array.Empty(); OutputRegistry = string.Empty; + ArchiveOutputPath = string.Empty; + LocalRegistry = string.Empty; Repository = string.Empty; ImageTags = Array.Empty(); + GeneratedArchiveOutputPath = string.Empty; GeneratedImageIndex = string.Empty; } #endregion @@ -63,6 +105,8 @@ public void Dispose() _cancellationTokenSource.Dispose(); } + private bool IsLocalPull => string.IsNullOrEmpty(BaseRegistry); + public override bool Execute() { try @@ -84,25 +128,120 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var images = ParseImages(); + using MSBuildLoggerProvider loggerProvider = new(Log); + ILoggerFactory msbuildLoggerFactory = new LoggerFactory(new[] { loggerProvider }); + ILogger logger = msbuildLoggerFactory.CreateLogger(); + + // Look up in CreateNewImage how the image is published to registry/local daemon/tarball + + RegistryMode sourceRegistryMode = BaseRegistry.Equals(OutputRegistry, StringComparison.InvariantCultureIgnoreCase) ? RegistryMode.PullFromOutput : RegistryMode.Pull; + Registry? sourceRegistry = IsLocalPull ? null : new Registry(BaseRegistry, logger, sourceRegistryMode); + SourceImageReference sourceImageReference = new(sourceRegistry, BaseImageName, BaseImageTag); + + DestinationImageReference destinationImageReference = DestinationImageReference.CreateFromSettings( + Repository, + ImageTags, + msbuildLoggerFactory, + ArchiveOutputPath, + OutputRegistry, + LocalRegistry); + + var images = ParseImages(destinationImageReference.Kind); + if (Log.HasLoggedErrors) { return false; } - using MSBuildLoggerProvider loggerProvider = new(Log); - ILoggerFactory msbuildLoggerFactory = new LoggerFactory(new[] { loggerProvider }); - ILogger logger = msbuildLoggerFactory.CreateLogger(); + var telemetry = CreateTelemetryContext(sourceImageReference, destinationImageReference); + + switch (destinationImageReference.Kind) + { + case DestinationImageReferenceKind.LocalRegistry: + await PushToLocalRegistryAsync(images, + sourceImageReference, + destinationImageReference, + telemetry, + cancellationToken).ConfigureAwait(false); + break; + case DestinationImageReferenceKind.RemoteRegistry: + await PushToRemoteRegistryAsync(images, + sourceImageReference, + destinationImageReference, + telemetry, + cancellationToken).ConfigureAwait(false); + break; + default: + throw new ArgumentOutOfRangeException(); + } - logger.LogInformation(Strings.BuildingImageIndex, GetRepositoryAndTagsString(), string.Join(", ", images.Select(i => i.ManifestDigest))); + telemetry.LogPublishSuccess(); + return !Log.HasLoggedErrors; + } + + private async Task PushToLocalRegistryAsync(BuiltImage[] images, SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + Telemetry telemetry, + CancellationToken cancellationToken) + { + ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; + if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) + { + telemetry.LogMissingLocalBinary(); + Log.LogErrorWithCodeFromResources(nameof(Strings.LocalRegistryNotAvailable)); + return; + } try { - (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images); + await localRegistry.LoadAsync(images, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); + SafeLog(Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); - GeneratedImageIndex = imageIndex; + if (localRegistry is ArchiveFileRegistry archive) + { + GeneratedArchiveOutputPath = archive.ArchiveOutputPath; + } + } + catch (ContainerHttpException e) + { + if (BuildEngine != null) + { + Log.LogErrorFromException(e, true); + } + } + catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) + { + telemetry.LogLocalLoadError(); + Log.LogErrorFromException(dle, showStackTrace: false); + } + catch (ArgumentException argEx) + { + Log.LogErrorFromException(argEx, showStackTrace: false); + } + } - await PushToRemoteRegistry(GeneratedImageIndex, mediaType, logger, cancellationToken); + private async Task PushToRemoteRegistryAsync(BuiltImage[] images, SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + Telemetry telemetry, + CancellationToken cancellationToken) + { + try + { + (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images); + await destinationImageReference.RemoteRegistry!.PushManifestListAsync( + destinationImageReference.Repository, + destinationImageReference.Tags, + imageIndex, + mediaType, + cancellationToken).ConfigureAwait(false); + SafeLog(Strings.ImageIndexUploadedToRegistry, destinationImageReference, OutputRegistry); + } + catch (UnableToAccessRepositoryException) + { + if (BuildEngine != null) + { + Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); + } } catch (ContainerHttpException e) { @@ -111,17 +250,24 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) Log.LogErrorFromException(e, true); } } - catch (ArgumentException ex) + catch (Exception e) { - Log.LogErrorFromException(ex); + if (BuildEngine != null) + { + Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); + Log.LogMessage(MessageImportance.Low, "Details: {0}", e); + } } + } - return !Log.HasLoggedErrors; + private void SafeLog(string message, params object[] formatParams) + { + if (BuildEngine != null) Log.LogMessage(MessageImportance.High, message, formatParams); } - private ImageInfo[] ParseImages() + private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind) { - var images = new ImageInfo[GeneratedContainers.Length]; + var images = new BuiltImage[GeneratedContainers.Length]; for (int i = 0; i < GeneratedContainers.Length; i++) { @@ -132,38 +278,160 @@ private ImageInfo[] ParseImages() string manifest = unparsedImage.GetMetadata("Manifest"); string manifestMediaType = unparsedImage.GetMetadata("ManifestMediaType"); + //TODO: add manifestmedia type to the error message if (string.IsNullOrEmpty(config) || string.IsNullOrEmpty(manifestDigest) || string.IsNullOrEmpty(manifest)) { Log.LogError(Strings.InvalidImageMetadata, unparsedImage.ItemSpec); break; } - images[i] = new ImageInfo + var manifestV2 = JsonSerializer.Deserialize(manifest); + if (manifestV2 == null) + { + //TODO: log new error about manifest not deserealized + Log.LogError(Strings.InvalidImageMetadata, unparsedImage.ItemSpec); + break; + } + + string imageDigest = manifestV2.Config.digest; + string imageSha = DigestUtils.GetShaFromDigest(imageDigest); + // We don't need layers for remote registry, as the individual images should be pushed already + var layers = destinationKind == DestinationImageReferenceKind.RemoteRegistry ? null : manifestV2.Layers; + (string architecture, string os) = GetArchitectureAndOsFromConfig(config); + + images[i] = new BuiltImage() { Config = config, - ManifestDigest = manifestDigest, + ImageDigest = imageDigest, + ImageSha = imageSha, Manifest = manifest, - ManifestMediaType = manifestMediaType + ManifestDigest = manifestDigest, + ManifestMediaType = manifestMediaType, + Layers = layers, + OS = os, + Architecture = architecture }; } return images; } - private async Task PushToRemoteRegistry(string manifestList, string mediaType, ILogger logger, CancellationToken cancellationToken) + private static (string, string) GetArchitectureAndOsFromConfig(string config) { - 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); + var configJson = JsonNode.Parse(config) as JsonObject ?? + throw new ArgumentException("Image config should be a JSON object."); + + var architecture = configJson["architecture"]?.ToString() ?? + throw new ArgumentException("Image config should contain 'architecture'."); + + var os = configJson["os"]?.ToString() ?? + throw new ArgumentException("Image config should contain 'os'."); + + return (architecture, os); } - private string? _repositoryAndTagsString = null; + private Telemetry CreateTelemetryContext(SourceImageReference source, DestinationImageReference destination) + { + var context = new PublishTelemetryContext( + source.Registry is not null ? GetRegistryType(source.Registry) : null, + null, // we don't support local pull yet, but we may in the future + destination.RemoteRegistry is not null ? GetRegistryType(destination.RemoteRegistry) : null, + destination.LocalRegistry is not null ? GetLocalStorageType(destination.LocalRegistry) : null); + return new Telemetry(Log, context); + } + + private RegistryType GetRegistryType(Registry r) + { + if (r.IsMcr) return RegistryType.MCR; + if (r.IsGithubPackageRegistry) return RegistryType.GitHub; + if (r.IsAmazonECRRegistry) return RegistryType.AWS; + if (r.IsAzureContainerRegistry) return RegistryType.Azure; + if (r.IsGoogleArtifactRegistry) return RegistryType.Google; + if (r.IsDockerHub) return RegistryType.DockerHub; + return RegistryType.Other; + } - private string GetRepositoryAndTagsString() + private LocalStorageType GetLocalStorageType(ILocalRegistry r) { - _repositoryAndTagsString ??= $"{Repository}:{string.Join(", ", ImageTags)}"; - return _repositoryAndTagsString; + if (r is ArchiveFileRegistry) return LocalStorageType.Tarball; + var d = r as DockerCli; + System.Diagnostics.Debug.Assert(d != null, "Unknown local registry type"); + if (d.GetCommand() == DockerCli.DockerCommand) return LocalStorageType.Docker; + else return LocalStorageType.Podman; + } + + /// + /// Interesting data about the container publish - used to track the usage rates of various sources/targets of the process + /// and to help diagnose issues with the container publish overall. + /// + /// If the base image came from a remote registry, what kind of registry was it? + /// If the base image came from a local store of some kind, what kind of store was it? + /// If the new image is being pushed to a remote registry, what kind of registry is it? + /// If the new image is being stored in a local store of some kind, what kind of store is it? + private record class PublishTelemetryContext(RegistryType? RemotePullType, LocalStorageType? LocalPullType, RegistryType? RemotePushType, LocalStorageType? LocalPushType); + private enum RegistryType { Azure, AWS, Google, GitHub, DockerHub, MCR, Other } + private enum LocalStorageType { Docker, Podman, Tarball } + + private class Telemetry(Microsoft.Build.Utilities.TaskLoggingHelper Log, PublishTelemetryContext context) + { + private IDictionary ContextProperties() => new Dictionary + { + { nameof(context.RemotePullType), context.RemotePullType?.ToString() }, + { nameof(context.LocalPullType), context.LocalPullType?.ToString() }, + { nameof(context.RemotePushType), context.RemotePushType?.ToString() }, + { nameof(context.LocalPushType), context.LocalPushType?.ToString() } + }; + + public void LogPublishSuccess() + { + Log.LogTelemetry("sdk/container/publish/success", ContextProperties()); + } + + public void LogUnknownRepository() + { + var props = ContextProperties(); + props.Add("error", "unknown_repository"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogCredentialFailure(SourceImageReference _) + { + var props = ContextProperties(); + props.Add("error", "credential_failure"); + props.Add("direction", "pull"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogCredentialFailure(DestinationImageReference d) + { + var props = ContextProperties(); + props.Add("error", "credential_failure"); + props.Add("direction", "push"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogRidMismatch(string desiredRid, string[] availableRids) + { + var props = ContextProperties(); + props.Add("error", "rid_mismatch"); + props.Add("target_rid", desiredRid); + props.Add("available_rids", string.Join(",", availableRids)); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogMissingLocalBinary() + { + var props = ContextProperties(); + props.Add("error", "missing_binary"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogLocalLoadError() + { + var props = ContextProperties(); + props.Add("error", "local_load"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + } } diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets index 2a006d665a6b..5309bdbeaf8b 100644 --- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets +++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets @@ -340,16 +340,15 @@ - - <_SkipCreateImageIndex>false - <_SkipCreateImageIndex Condition="'$(ContainerRegistry)' == ''">true - - - + ImageTags="@(ContainerImageTags)" + BaseRegistry="$(ContainerBaseRegistry)" + BaseImageName="$(ContainerBaseName)" + BaseImageTag="$(ContainerBaseTag)"> diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 2d562b4feaa7..40083f8fc58e 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -1010,9 +1010,9 @@ public void EndToEndMultiArch_RemoteRegistry() commandResult.Should().Pass() .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) - .And.HaveStdOutContaining($"Pushed image '{imageX64}' to registry") - .And.HaveStdOutContaining($"Pushed image '{imageArm64}' to registry") - .And.HaveStdOutContaining($"Pushed image index '{imageIndex}' to registry '{registry}'"); + .And.HaveStdOutContaining($"Pushed image '{imageX64}' to registry '{registry}'.") + .And.HaveStdOutContaining($"Pushed image '{imageArm64}' to registry '{registry}'.") + .And.HaveStdOutContaining($"Pushed image index '{imageIndex}' to registry '{registry}'."); // Check that the containers can be run diff --git a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs index ef6e5bb84b2a..9bc573b5b0e7 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs @@ -10,7 +10,7 @@ public class ImageIndexGeneratorTests [Fact] public void ImagesCannotBeEmpty() { - ImageInfo[] images = Array.Empty(); + BuiltImage[] images = Array.Empty(); var ex = Assert.Throws(() => ImageIndexGenerator.GenerateImageIndex(images)); Assert.Equal(Strings.ImagesEmpty, ex.Message); } @@ -18,95 +18,49 @@ public void ImagesCannotBeEmpty() [Fact] public void UnsupportedMediaTypeThrows() { - ImageInfo[] images = new ImageInfo[] - { - new ImageInfo + BuiltImage[] images = + [ + new BuiltImage { + Config = "", + ImageDigest = "", + ImageSha = "", + Manifest = "", + ManifestDigest = "", ManifestMediaType = "unsupported" } - }; + ]; var ex = Assert.Throws(() => ImageIndexGenerator.GenerateImageIndex(images)); Assert.Equal(string.Format(Strings.UnsupportedMediaType, "unsupported"), ex.Message); } - [Theory] - [InlineData(SchemaTypes.DockerManifestV2)] - [InlineData(SchemaTypes.OciManifestV1)] - public void ConfigIsNotJsonObjectThrows(string supportedMediaType) - { - ImageInfo[] images = new ImageInfo[] - { - new ImageInfo - { - Config = "[]", - Manifest = "", - ManifestMediaType = supportedMediaType - } - }; - - var ex = Assert.Throws(() => ImageIndexGenerator.GenerateImageIndex(images)); - Assert.Equal($"Config should be a JSON object. (Parameter 'Config')", ex.Message); - } - - [Theory] - [InlineData(SchemaTypes.DockerManifestV2)] - [InlineData(SchemaTypes.OciManifestV1)] - public void ConfigDoesNotContainArchitectureThrows(string supportedMediaType) - { - ImageInfo[] images = new ImageInfo[] - { - new ImageInfo - { - Config = "{}", - Manifest = "", - ManifestMediaType = supportedMediaType - } - }; - - var ex = Assert.Throws(() => ImageIndexGenerator.GenerateImageIndex(images)); - Assert.Equal($"Config should contain 'architecture'. (Parameter 'Config')", ex.Message); - } - - [Theory] - [InlineData(SchemaTypes.DockerManifestV2)] - [InlineData(SchemaTypes.OciManifestV1)] - public void ConfigDoesNotContainOsThrows(string supportedMediaType) - { - ImageInfo[] images = new ImageInfo[] - { - new ImageInfo - { - Config = "{\"architecture\":\"arch1\"}", - Manifest = "", - ManifestMediaType = supportedMediaType - } - }; - - var ex = Assert.Throws(() => ImageIndexGenerator.GenerateImageIndex(images)); - Assert.Equal($"Config should contain 'os'. (Parameter 'Config')", ex.Message); - } - [Theory] [InlineData(SchemaTypes.DockerManifestV2)] [InlineData(SchemaTypes.OciManifestV1)] public void ImagesWithMixedMediaTypes(string supportedMediaType) { - ImageInfo[] images = new ImageInfo[] - { - new ImageInfo + BuiltImage[] images = + [ + new BuiltImage { - Config = "{\"architecture\":\"arch1\",\"os\":\"os1\"}", - Manifest = "", - ManifestMediaType = supportedMediaType + Config = "", + ImageDigest = "", + ImageSha = "", + Manifest = "", + ManifestDigest = "", + ManifestMediaType = supportedMediaType, }, - new ImageInfo + new BuiltImage { Config = "", + ImageDigest = "", + ImageSha = "", Manifest = "", + ManifestDigest = "", ManifestMediaType = "anotherMediaType" } - }; + ]; var ex = Assert.Throws(() => ImageIndexGenerator.GenerateImageIndex(images)); Assert.Equal(Strings.MixedMediaTypes, ex.Message); @@ -115,21 +69,29 @@ public void ImagesWithMixedMediaTypes(string supportedMediaType) [Fact] public void GenerateDockerManifestList() { - ImageInfo[] images = + BuiltImage[] images = [ - new ImageInfo + new BuiltImage { - Config = "{\"architecture\":\"arch1\",\"os\":\"os1\"}", - ManifestDigest = "sha256:digest1", + Config = "", + ImageDigest = "", + ImageSha = "", Manifest = "123", - ManifestMediaType = SchemaTypes.DockerManifestV2 + ManifestDigest = "sha256:digest1", + ManifestMediaType = SchemaTypes.DockerManifestV2, + Architecture = "arch1", + OS = "os1" }, - new ImageInfo + new BuiltImage { - Config = "{\"architecture\":\"arch2\",\"os\":\"os2\"}", - ManifestDigest = "sha256:digest2", + Config = "", + ImageDigest = "", + ImageSha = "", Manifest = "123", - ManifestMediaType = SchemaTypes.DockerManifestV2 + ManifestDigest = "sha256:digest2", + ManifestMediaType = SchemaTypes.DockerManifestV2, + Architecture = "arch2", + OS = "os2" } ]; @@ -141,23 +103,31 @@ public void GenerateDockerManifestList() [Fact] public void GenerateOciImageIndex() { - ImageInfo[] images = new ImageInfo[] - { - new ImageInfo + BuiltImage[] images = + [ + new BuiltImage { - Config = "{\"architecture\":\"arch1\",\"os\":\"os1\"}", - ManifestDigest = "sha256:digest1", + Config = "", + ImageDigest = "", + ImageSha = "", Manifest = "123", - ManifestMediaType = SchemaTypes.OciManifestV1 + ManifestDigest = "sha256:digest1", + ManifestMediaType = SchemaTypes.OciManifestV1, + Architecture = "arch1", + OS = "os1" }, - new ImageInfo + new BuiltImage { - Config = "{\"architecture\":\"arch2\",\"os\":\"os2\"}", - ManifestDigest = "sha256:digest2", + Config = "", + ImageDigest = "", + ImageSha = "", Manifest = "123", - ManifestMediaType = SchemaTypes.OciManifestV1 + ManifestDigest = "sha256:digest2", + ManifestMediaType = SchemaTypes.OciManifestV1, + Architecture = "arch2", + OS = "os2" } - }; + ]; var (imageIndex, mediaType) = ImageIndexGenerator.GenerateImageIndex(images); Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1\\u002Bjson\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\",\"variant\":null,\"features\":null,\"os.version\":null}},{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\",\"variant\":null,\"features\":null,\"os.version\":null}}]}", imageIndex); From 8d5289972963cb275fba6786d3c2555b1002eedb Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 31 Jan 2025 19:01:46 +0100 Subject: [PATCH 06/50] move Telemetry into a seperate file --- .../ImagePublisher.cs | 121 ++++++++++++++++++ .../Tasks/CreateImageIndex.cs | 114 +---------------- .../Tasks/CreateNewImage.cs | 109 +--------------- .../Tasks/Telemetry.cs | 116 +++++++++++++++++ 4 files changed, 240 insertions(+), 220 deletions(-) create mode 100644 src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs create mode 100644 src/Containers/Microsoft.NET.Build.Containers/Tasks/Telemetry.cs diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs new file mode 100644 index 000000000000..c877d2c004ba --- /dev/null +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -0,0 +1,121 @@ +// // Licensed to the .NET Foundation under one or more agreements. +// // The .NET Foundation licenses this file to you under the MIT license. + +// using Microsoft.NET.Build.Containers.LocalDaemons; +// using Microsoft.NET.Build.Containers.Resources; + +// namespace Microsoft.NET.Build.Containers; + + +// internal static class ImagePublisher +// { +// public static async Task PublishImage(BuiltImage builtImage, SourceImageReference sourceImageReference, +// DestinationImageReference destinationImageReference, +// Telemetry telemetry, +// CancellationToken cancellationToken) +// { +// cancellationToken.ThrowIfCancellationRequested(); + +// switch (destinationImageReference.Kind) +// { +// case DestinationImageReferenceKind.LocalRegistry: +// await PushToLocalRegistryAsync(builtImage, +// sourceImageReference, +// destinationImageReference, +// telemetry, +// cancellationToken).ConfigureAwait(false); +// break; +// case DestinationImageReferenceKind.RemoteRegistry: +// await PushToRemoteRegistryAsync(builtImage, +// sourceImageReference, +// destinationImageReference, +// cancellationToken).ConfigureAwait(false); +// break; +// default: +// throw new ArgumentOutOfRangeException(); +// } + +// telemetry.LogPublishSuccess(); +// } + +// private static async Task PushToLocalRegistryAsync( +// BuiltImage builtImage, +// SourceImageReference sourceImageReference, +// DestinationImageReference destinationImageReference, +// Telemetry telemetry, +// CancellationToken cancellationToken) +// { +// ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; +// if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) +// { +// telemetry.LogMissingLocalBinary(); +// Log.LogErrorWithCodeFromResources(nameof(Strings.LocalRegistryNotAvailable)); +// return; +// } +// try +// { +// await localRegistry.LoadAsync(builtImage, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); +// SafeLog(Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); + +// if (localRegistry is ArchiveFileRegistry archive) +// { +// GeneratedArchiveOutputPath = archive.ArchiveOutputPath; +// } +// } +// catch (ContainerHttpException e) +// { +// if (BuildEngine != null) +// { +// Log.LogErrorFromException(e, true); +// } +// } +// catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) +// { +// telemetry.LogLocalLoadError(); +// Log.LogErrorFromException(dle, showStackTrace: false); +// } +// catch (ArgumentException argEx) +// { +// Log.LogErrorFromException(argEx, showStackTrace: false); +// } +// } + +// private static async Task PushToRemoteRegistryAsync( +// BuiltImage builtImage, +// SourceImageReference sourceImageReference, +// DestinationImageReference destinationImageReference, +// CancellationToken cancellationToken) +// { +// try +// { +// await destinationImageReference.RemoteRegistry!.PushAsync( +// builtImage, +// sourceImageReference, +// destinationImageReference, +// cancellationToken).ConfigureAwait(false); +// SafeLog(Strings.ContainerBuilder_ImageUploadedToRegistry, destinationImageReference, OutputRegistry); +// } +// catch (UnableToAccessRepositoryException) +// { +// if (BuildEngine != null) +// { +// Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); +// } +// } +// catch (ContainerHttpException e) +// { +// if (BuildEngine != null) +// { +// Log.LogErrorFromException(e, true); +// } +// } +// catch (Exception e) +// { +// if (BuildEngine != null) +// { +// Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); +// Log.LogMessage(MessageImportance.Low, "Details: {0}", e); +// } +// } +// } +// } \ No newline at end of file diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 09128838f212..e315adef8921 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -132,8 +132,6 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) ILoggerFactory msbuildLoggerFactory = new LoggerFactory(new[] { loggerProvider }); ILogger logger = msbuildLoggerFactory.CreateLogger(); - // Look up in CreateNewImage how the image is published to registry/local daemon/tarball - RegistryMode sourceRegistryMode = BaseRegistry.Equals(OutputRegistry, StringComparison.InvariantCultureIgnoreCase) ? RegistryMode.PullFromOutput : RegistryMode.Pull; Registry? sourceRegistry = IsLocalPull ? null : new Registry(BaseRegistry, logger, sourceRegistryMode); SourceImageReference sourceImageReference = new(sourceRegistry, BaseImageName, BaseImageTag); @@ -153,7 +151,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) return false; } - var telemetry = CreateTelemetryContext(sourceImageReference, destinationImageReference); + var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); switch (destinationImageReference.Kind) { @@ -166,9 +164,7 @@ await PushToLocalRegistryAsync(images, break; case DestinationImageReferenceKind.RemoteRegistry: await PushToRemoteRegistryAsync(images, - sourceImageReference, destinationImageReference, - telemetry, cancellationToken).ConfigureAwait(false); break; default: @@ -220,9 +216,8 @@ private async Task PushToLocalRegistryAsync(BuiltImage[] images, SourceImageRefe } } - private async Task PushToRemoteRegistryAsync(BuiltImage[] images, SourceImageReference sourceImageReference, + private async Task PushToRemoteRegistryAsync(BuiltImage[] images, DestinationImageReference destinationImageReference, - Telemetry telemetry, CancellationToken cancellationToken) { try @@ -329,109 +324,4 @@ private static (string, string) GetArchitectureAndOsFromConfig(string config) return (architecture, os); } - - private Telemetry CreateTelemetryContext(SourceImageReference source, DestinationImageReference destination) - { - var context = new PublishTelemetryContext( - source.Registry is not null ? GetRegistryType(source.Registry) : null, - null, // we don't support local pull yet, but we may in the future - destination.RemoteRegistry is not null ? GetRegistryType(destination.RemoteRegistry) : null, - destination.LocalRegistry is not null ? GetLocalStorageType(destination.LocalRegistry) : null); - return new Telemetry(Log, context); - } - - private RegistryType GetRegistryType(Registry r) - { - if (r.IsMcr) return RegistryType.MCR; - if (r.IsGithubPackageRegistry) return RegistryType.GitHub; - if (r.IsAmazonECRRegistry) return RegistryType.AWS; - if (r.IsAzureContainerRegistry) return RegistryType.Azure; - if (r.IsGoogleArtifactRegistry) return RegistryType.Google; - if (r.IsDockerHub) return RegistryType.DockerHub; - return RegistryType.Other; - } - - private LocalStorageType GetLocalStorageType(ILocalRegistry r) - { - if (r is ArchiveFileRegistry) return LocalStorageType.Tarball; - var d = r as DockerCli; - System.Diagnostics.Debug.Assert(d != null, "Unknown local registry type"); - if (d.GetCommand() == DockerCli.DockerCommand) return LocalStorageType.Docker; - else return LocalStorageType.Podman; - } - - /// - /// Interesting data about the container publish - used to track the usage rates of various sources/targets of the process - /// and to help diagnose issues with the container publish overall. - /// - /// If the base image came from a remote registry, what kind of registry was it? - /// If the base image came from a local store of some kind, what kind of store was it? - /// If the new image is being pushed to a remote registry, what kind of registry is it? - /// If the new image is being stored in a local store of some kind, what kind of store is it? - private record class PublishTelemetryContext(RegistryType? RemotePullType, LocalStorageType? LocalPullType, RegistryType? RemotePushType, LocalStorageType? LocalPushType); - private enum RegistryType { Azure, AWS, Google, GitHub, DockerHub, MCR, Other } - private enum LocalStorageType { Docker, Podman, Tarball } - - private class Telemetry(Microsoft.Build.Utilities.TaskLoggingHelper Log, PublishTelemetryContext context) - { - private IDictionary ContextProperties() => new Dictionary - { - { nameof(context.RemotePullType), context.RemotePullType?.ToString() }, - { nameof(context.LocalPullType), context.LocalPullType?.ToString() }, - { nameof(context.RemotePushType), context.RemotePushType?.ToString() }, - { nameof(context.LocalPushType), context.LocalPushType?.ToString() } - }; - - public void LogPublishSuccess() - { - Log.LogTelemetry("sdk/container/publish/success", ContextProperties()); - } - - public void LogUnknownRepository() - { - var props = ContextProperties(); - props.Add("error", "unknown_repository"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogCredentialFailure(SourceImageReference _) - { - var props = ContextProperties(); - props.Add("error", "credential_failure"); - props.Add("direction", "pull"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogCredentialFailure(DestinationImageReference d) - { - var props = ContextProperties(); - props.Add("error", "credential_failure"); - props.Add("direction", "push"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogRidMismatch(string desiredRid, string[] availableRids) - { - var props = ContextProperties(); - props.Add("error", "rid_mismatch"); - props.Add("target_rid", desiredRid); - props.Add("available_rids", string.Join(",", availableRids)); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogMissingLocalBinary() - { - var props = ContextProperties(); - props.Add("error", "missing_binary"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogLocalLoadError() - { - var props = ContextProperties(); - props.Add("error", "local_load"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index 7368fd337596..89131d696e46 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -72,7 +72,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) OutputRegistry, LocalRegistry); - var telemetry = CreateTelemetryContext(sourceImageReference, destinationImageReference); + var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); ImageBuilder? imageBuilder; if (sourceRegistry is { } registry) @@ -190,7 +190,6 @@ await PushToLocalRegistryAsync(builtImage, await PushToRemoteRegistryAsync(builtImage, sourceImageReference, destinationImageReference, - telemetry, cancellationToken).ConfigureAwait(false); break; default: @@ -244,7 +243,6 @@ private async Task PushToLocalRegistryAsync(BuiltImage builtImage, SourceImageRe private async Task PushToRemoteRegistryAsync(BuiltImage builtImage, SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, - Telemetry telemetry, CancellationToken cancellationToken) { try @@ -349,109 +347,4 @@ public void Dispose() logWarning: s => Log.LogWarningWithCodeFromResources(s), logError: (s, a) => { if (a is null) Log.LogErrorWithCodeFromResources(s); else Log.LogErrorWithCodeFromResources(s, a); }); } - - private Telemetry CreateTelemetryContext(SourceImageReference source, DestinationImageReference destination) - { - var context = new PublishTelemetryContext( - source.Registry is not null ? GetRegistryType(source.Registry) : null, - null, // we don't support local pull yet, but we may in the future - destination.RemoteRegistry is not null ? GetRegistryType(destination.RemoteRegistry) : null, - destination.LocalRegistry is not null ? GetLocalStorageType(destination.LocalRegistry) : null); - return new Telemetry(Log, context); - } - - private RegistryType GetRegistryType(Registry r) - { - if (r.IsMcr) return RegistryType.MCR; - if (r.IsGithubPackageRegistry) return RegistryType.GitHub; - if (r.IsAmazonECRRegistry) return RegistryType.AWS; - if (r.IsAzureContainerRegistry) return RegistryType.Azure; - if (r.IsGoogleArtifactRegistry) return RegistryType.Google; - if (r.IsDockerHub) return RegistryType.DockerHub; - return RegistryType.Other; - } - - private LocalStorageType GetLocalStorageType(ILocalRegistry r) - { - if (r is ArchiveFileRegistry) return LocalStorageType.Tarball; - var d = r as DockerCli; - System.Diagnostics.Debug.Assert(d != null, "Unknown local registry type"); - if (d.GetCommand() == DockerCli.DockerCommand) return LocalStorageType.Docker; - else return LocalStorageType.Podman; - } - - /// - /// Interesting data about the container publish - used to track the usage rates of various sources/targets of the process - /// and to help diagnose issues with the container publish overall. - /// - /// If the base image came from a remote registry, what kind of registry was it? - /// If the base image came from a local store of some kind, what kind of store was it? - /// If the new image is being pushed to a remote registry, what kind of registry is it? - /// If the new image is being stored in a local store of some kind, what kind of store is it? - private record class PublishTelemetryContext(RegistryType? RemotePullType, LocalStorageType? LocalPullType, RegistryType? RemotePushType, LocalStorageType? LocalPushType); - private enum RegistryType { Azure, AWS, Google, GitHub, DockerHub, MCR, Other } - private enum LocalStorageType { Docker, Podman, Tarball } - - private class Telemetry(Microsoft.Build.Utilities.TaskLoggingHelper Log, PublishTelemetryContext context) - { - private IDictionary ContextProperties() => new Dictionary - { - { nameof(context.RemotePullType), context.RemotePullType?.ToString() }, - { nameof(context.LocalPullType), context.LocalPullType?.ToString() }, - { nameof(context.RemotePushType), context.RemotePushType?.ToString() }, - { nameof(context.LocalPushType), context.LocalPushType?.ToString() } - }; - - public void LogPublishSuccess() - { - Log.LogTelemetry("sdk/container/publish/success", ContextProperties()); - } - - public void LogUnknownRepository() - { - var props = ContextProperties(); - props.Add("error", "unknown_repository"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogCredentialFailure(SourceImageReference _) - { - var props = ContextProperties(); - props.Add("error", "credential_failure"); - props.Add("direction", "pull"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogCredentialFailure(DestinationImageReference d) - { - var props = ContextProperties(); - props.Add("error", "credential_failure"); - props.Add("direction", "push"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogRidMismatch(string desiredRid, string[] availableRids) - { - var props = ContextProperties(); - props.Add("error", "rid_mismatch"); - props.Add("target_rid", desiredRid); - props.Add("available_rids", string.Join(",", availableRids)); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogMissingLocalBinary() - { - var props = ContextProperties(); - props.Add("error", "missing_binary"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - public void LogLocalLoadError() - { - var props = ContextProperties(); - props.Add("error", "local_load"); - Log.LogTelemetry("sdk/container/publish/error", props); - } - - } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/Telemetry.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/Telemetry.cs new file mode 100644 index 000000000000..df4ee99ba950 --- /dev/null +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/Telemetry.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.NET.Build.Containers.LocalDaemons; + +namespace Microsoft.NET.Build.Containers.Tasks; + +internal class Telemetry +{ + /// + /// Interesting data about the container publish - used to track the usage rates of various sources/targets of the process + /// and to help diagnose issues with the container publish overall. + /// + /// If the base image came from a remote registry, what kind of registry was it? + /// If the base image came from a local store of some kind, what kind of store was it? + /// If the new image is being pushed to a remote registry, what kind of registry is it? + /// If the new image is being stored in a local store of some kind, what kind of store is it? + private record class PublishTelemetryContext(RegistryType? RemotePullType, LocalStorageType? LocalPullType, RegistryType? RemotePushType, LocalStorageType? LocalPushType); + private enum RegistryType { Azure, AWS, Google, GitHub, DockerHub, MCR, Other } + private enum LocalStorageType { Docker, Podman, Tarball } + + private readonly Microsoft.Build.Utilities.TaskLoggingHelper Log; + private readonly PublishTelemetryContext Context; + + internal Telemetry( + SourceImageReference source, + DestinationImageReference destination, + Microsoft.Build.Utilities.TaskLoggingHelper Log) + { + this.Log = Log; + Context = new PublishTelemetryContext( + source.Registry is not null ? GetRegistryType(source.Registry) : null, + null, // we don't support local pull yet, but we may in the future + destination.RemoteRegistry is not null ? GetRegistryType(destination.RemoteRegistry) : null, + destination.LocalRegistry is not null ? GetLocalStorageType(destination.LocalRegistry) : null); + } + + private RegistryType GetRegistryType(Registry r) + { + if (r.IsMcr) return RegistryType.MCR; + if (r.IsGithubPackageRegistry) return RegistryType.GitHub; + if (r.IsAmazonECRRegistry) return RegistryType.AWS; + if (r.IsAzureContainerRegistry) return RegistryType.Azure; + if (r.IsGoogleArtifactRegistry) return RegistryType.Google; + if (r.IsDockerHub) return RegistryType.DockerHub; + return RegistryType.Other; + } + + private LocalStorageType GetLocalStorageType(ILocalRegistry r) + { + if (r is ArchiveFileRegistry) return LocalStorageType.Tarball; + var d = r as DockerCli; + System.Diagnostics.Debug.Assert(d != null, "Unknown local registry type"); + if (d.GetCommand() == DockerCli.DockerCommand) return LocalStorageType.Docker; + else return LocalStorageType.Podman; + } + + private IDictionary ContextProperties() => new Dictionary + { + { nameof(Context.RemotePullType), Context.RemotePullType?.ToString() }, + { nameof(Context.LocalPullType), Context.LocalPullType?.ToString() }, + { nameof(Context.RemotePushType), Context.RemotePushType?.ToString() }, + { nameof(Context.LocalPushType), Context.LocalPushType?.ToString() } + }; + + public void LogPublishSuccess() + { + Log.LogTelemetry("sdk/container/publish/success", ContextProperties()); + } + + public void LogUnknownRepository() + { + var props = ContextProperties(); + props.Add("error", "unknown_repository"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogCredentialFailure(SourceImageReference _) + { + var props = ContextProperties(); + props.Add("error", "credential_failure"); + props.Add("direction", "pull"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogCredentialFailure(DestinationImageReference d) + { + var props = ContextProperties(); + props.Add("error", "credential_failure"); + props.Add("direction", "push"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogRidMismatch(string desiredRid, string[] availableRids) + { + var props = ContextProperties(); + props.Add("error", "rid_mismatch"); + props.Add("target_rid", desiredRid); + props.Add("available_rids", string.Join(",", availableRids)); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogMissingLocalBinary() + { + var props = ContextProperties(); + props.Add("error", "missing_binary"); + Log.LogTelemetry("sdk/container/publish/error", props); + } + + public void LogLocalLoadError() + { + var props = ContextProperties(); + props.Add("error", "local_load"); + Log.LogTelemetry("sdk/container/publish/error", props); + } +} \ No newline at end of file From 0399310797094f4e0a260625c398f89aaec62533 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 31 Jan 2025 19:11:53 +0100 Subject: [PATCH 07/50] move Telemetry class to another folder --- .../Microsoft.NET.Build.Containers/{Tasks => }/Telemetry.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Containers/Microsoft.NET.Build.Containers/{Tasks => }/Telemetry.cs (99%) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/Telemetry.cs b/src/Containers/Microsoft.NET.Build.Containers/Telemetry.cs similarity index 99% rename from src/Containers/Microsoft.NET.Build.Containers/Tasks/Telemetry.cs rename to src/Containers/Microsoft.NET.Build.Containers/Telemetry.cs index df4ee99ba950..009b93d7717c 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/Telemetry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Telemetry.cs @@ -3,7 +3,7 @@ using Microsoft.NET.Build.Containers.LocalDaemons; -namespace Microsoft.NET.Build.Containers.Tasks; +namespace Microsoft.NET.Build.Containers; internal class Telemetry { From ec26765bdddf53f3796cf0e72321f40ffbc0001e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 31 Jan 2025 21:40:40 +0100 Subject: [PATCH 08/50] extract publishing image logic into a seperate class --- .../ImagePublisher.cs | 365 ++++++++++++------ .../Tasks/CreateImageIndex.cs | 107 +---- .../Tasks/CreateNewImage.cs | 111 +----- 3 files changed, 263 insertions(+), 320 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index c877d2c004ba..e2b76e461867 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -1,121 +1,262 @@ -// // Licensed to the .NET Foundation under one or more agreements. -// // The .NET Foundation licenses this file to you under the MIT license. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -// using Microsoft.NET.Build.Containers.LocalDaemons; -// using Microsoft.NET.Build.Containers.Resources; +using Microsoft.Build.Framework; +using Microsoft.NET.Build.Containers.Resources; -// namespace Microsoft.NET.Build.Containers; +namespace Microsoft.NET.Build.Containers; +internal static class ImagePublisher +{ + public static async Task PublishImage( + BuiltImage image, + SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + Microsoft.Build.Utilities.TaskLoggingHelper Log, + IBuildEngine? BuildEngine, + Telemetry telemetry, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); -// internal static class ImagePublisher -// { -// public static async Task PublishImage(BuiltImage builtImage, SourceImageReference sourceImageReference, -// DestinationImageReference destinationImageReference, -// Telemetry telemetry, -// CancellationToken cancellationToken) -// { -// cancellationToken.ThrowIfCancellationRequested(); + switch (destinationImageReference.Kind) + { + case DestinationImageReferenceKind.LocalRegistry: + await PushToLocalRegistryAsync( + image, + sourceImageReference, + destinationImageReference, + Log, + BuildEngine, + telemetry, + cancellationToken).ConfigureAwait(false); + break; + case DestinationImageReferenceKind.RemoteRegistry: + await PushToRemoteRegistryAsync( + image, + sourceImageReference, + destinationImageReference, + Log, + BuildEngine, + cancellationToken).ConfigureAwait(false); + break; + default: + throw new ArgumentOutOfRangeException(); + } -// switch (destinationImageReference.Kind) -// { -// case DestinationImageReferenceKind.LocalRegistry: -// await PushToLocalRegistryAsync(builtImage, -// sourceImageReference, -// destinationImageReference, -// telemetry, -// cancellationToken).ConfigureAwait(false); -// break; -// case DestinationImageReferenceKind.RemoteRegistry: -// await PushToRemoteRegistryAsync(builtImage, -// sourceImageReference, -// destinationImageReference, -// cancellationToken).ConfigureAwait(false); -// break; -// default: -// throw new ArgumentOutOfRangeException(); -// } + telemetry.LogPublishSuccess(); + } -// telemetry.LogPublishSuccess(); -// } + public static async Task PublishImage( + BuiltImage[] images, + SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + Microsoft.Build.Utilities.TaskLoggingHelper Log, + IBuildEngine? BuildEngine, + Telemetry telemetry, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); -// private static async Task PushToLocalRegistryAsync( -// BuiltImage builtImage, -// SourceImageReference sourceImageReference, -// DestinationImageReference destinationImageReference, -// Telemetry telemetry, -// CancellationToken cancellationToken) -// { -// ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; -// if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) -// { -// telemetry.LogMissingLocalBinary(); -// Log.LogErrorWithCodeFromResources(nameof(Strings.LocalRegistryNotAvailable)); -// return; -// } -// try -// { -// await localRegistry.LoadAsync(builtImage, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); -// SafeLog(Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); + switch (destinationImageReference.Kind) + { + case DestinationImageReferenceKind.LocalRegistry: + await PushToLocalRegistryAsync( + images, + sourceImageReference, + destinationImageReference, + Log, + BuildEngine, + telemetry, + cancellationToken).ConfigureAwait(false); + break; + case DestinationImageReferenceKind.RemoteRegistry: + await PushToRemoteRegistryAsync( + images, + sourceImageReference, + destinationImageReference, + Log, + BuildEngine, + cancellationToken).ConfigureAwait(false); + break; + default: + throw new ArgumentOutOfRangeException(); + } -// if (localRegistry is ArchiveFileRegistry archive) -// { -// GeneratedArchiveOutputPath = archive.ArchiveOutputPath; -// } -// } -// catch (ContainerHttpException e) -// { -// if (BuildEngine != null) -// { -// Log.LogErrorFromException(e, true); -// } -// } -// catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) -// { -// telemetry.LogLocalLoadError(); -// Log.LogErrorFromException(dle, showStackTrace: false); -// } -// catch (ArgumentException argEx) -// { -// Log.LogErrorFromException(argEx, showStackTrace: false); -// } -// } + telemetry.LogPublishSuccess(); + } -// private static async Task PushToRemoteRegistryAsync( -// BuiltImage builtImage, -// SourceImageReference sourceImageReference, -// DestinationImageReference destinationImageReference, -// CancellationToken cancellationToken) -// { -// try -// { -// await destinationImageReference.RemoteRegistry!.PushAsync( -// builtImage, -// sourceImageReference, -// destinationImageReference, -// cancellationToken).ConfigureAwait(false); -// SafeLog(Strings.ContainerBuilder_ImageUploadedToRegistry, destinationImageReference, OutputRegistry); -// } -// catch (UnableToAccessRepositoryException) -// { -// if (BuildEngine != null) -// { -// Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); -// } -// } -// catch (ContainerHttpException e) -// { -// if (BuildEngine != null) -// { -// Log.LogErrorFromException(e, true); -// } -// } -// catch (Exception e) -// { -// if (BuildEngine != null) -// { -// Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); -// Log.LogMessage(MessageImportance.Low, "Details: {0}", e); -// } -// } -// } -// } \ No newline at end of file + private static async Task PushToLocalRegistryAsync( + BuiltImage image, + SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + Microsoft.Build.Utilities.TaskLoggingHelper Log, + IBuildEngine? BuildEngine, + Telemetry telemetry, + CancellationToken cancellationToken) + { + ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; + if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) + { + telemetry.LogMissingLocalBinary(); + Log.LogErrorWithCodeFromResources(nameof(Strings.LocalRegistryNotAvailable)); + return; + } + try + { + await localRegistry.LoadAsync(image, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); + if (BuildEngine != null) + { + Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); + } + } + catch (ContainerHttpException e) + { + if (BuildEngine != null) + { + Log.LogErrorFromException(e, true); + } + } + catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) + { + telemetry.LogLocalLoadError(); + Log.LogErrorFromException(dle, showStackTrace: false); + } + catch (ArgumentException argEx) + { + Log.LogErrorFromException(argEx, showStackTrace: false); + } + } + + private static async Task PushToLocalRegistryAsync( + BuiltImage[] images, + SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + Microsoft.Build.Utilities.TaskLoggingHelper Log, + IBuildEngine? BuildEngine, + Telemetry telemetry, + CancellationToken cancellationToken) + { + ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; + if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) + { + telemetry.LogMissingLocalBinary(); + Log.LogErrorWithCodeFromResources(nameof(Strings.LocalRegistryNotAvailable)); + return; + } + try + { + await localRegistry.LoadAsync(images, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); + if (BuildEngine != null) + { + Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); + } + } + catch (ContainerHttpException e) + { + if (BuildEngine != null) + { + Log.LogErrorFromException(e, true); + } + } + catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) + { + telemetry.LogLocalLoadError(); + Log.LogErrorFromException(dle, showStackTrace: false); + } + catch (ArgumentException argEx) + { + Log.LogErrorFromException(argEx, showStackTrace: false); + } + } + + private static async Task PushToRemoteRegistryAsync( + BuiltImage image, + SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + Microsoft.Build.Utilities.TaskLoggingHelper Log, + IBuildEngine? BuildEngine, + CancellationToken cancellationToken) + { + try + { + await destinationImageReference.RemoteRegistry!.PushAsync( + image, + sourceImageReference, + destinationImageReference, + cancellationToken).ConfigureAwait(false); + if (BuildEngine != null) + { + Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToRegistry, destinationImageReference, destinationImageReference.RemoteRegistry!.RegistryName); + } + } + catch (UnableToAccessRepositoryException) + { + if (BuildEngine != null) + { + Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); + } + } + catch (ContainerHttpException e) + { + if (BuildEngine != null) + { + Log.LogErrorFromException(e, true); + } + } + catch (Exception e) + { + if (BuildEngine != null) + { + Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); + Log.LogMessage(MessageImportance.Low, "Details: {0}", e); + } + } + } + + private static async Task PushToRemoteRegistryAsync( + BuiltImage[] images, + SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + Microsoft.Build.Utilities.TaskLoggingHelper Log, + IBuildEngine? BuildEngine, + CancellationToken cancellationToken) + { + try + { + (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images); + await destinationImageReference.RemoteRegistry!.PushManifestListAsync( + destinationImageReference.Repository, + destinationImageReference.Tags, + imageIndex, + mediaType, + cancellationToken).ConfigureAwait(false); + if (BuildEngine != null) + { + Log.LogMessage(MessageImportance.High, Strings.ImageIndexUploadedToRegistry, destinationImageReference, destinationImageReference.RemoteRegistry!.RegistryName); + } + } + catch (UnableToAccessRepositoryException) + { + if (BuildEngine != null) + { + Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); + } + } + catch (ContainerHttpException e) + { + if (BuildEngine != null) + { + Log.LogErrorFromException(e, true); + } + } + catch (Exception e) + { + if (BuildEngine != null) + { + Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); + Log.LogMessage(MessageImportance.Low, "Details: {0}", e); + } + } + } +} diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index e315adef8921..49326cd20cf9 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -151,115 +151,16 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) return false; } - var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); + GeneratedArchiveOutputPath = ArchiveOutputPath; - switch (destinationImageReference.Kind) - { - case DestinationImageReferenceKind.LocalRegistry: - await PushToLocalRegistryAsync(images, - sourceImageReference, - destinationImageReference, - telemetry, - cancellationToken).ConfigureAwait(false); - break; - case DestinationImageReferenceKind.RemoteRegistry: - await PushToRemoteRegistryAsync(images, - destinationImageReference, - cancellationToken).ConfigureAwait(false); - break; - default: - throw new ArgumentOutOfRangeException(); - } + var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); - telemetry.LogPublishSuccess(); + await ImagePublisher.PublishImage(images, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) + .ConfigureAwait(false); return !Log.HasLoggedErrors; } - private async Task PushToLocalRegistryAsync(BuiltImage[] images, SourceImageReference sourceImageReference, - DestinationImageReference destinationImageReference, - Telemetry telemetry, - CancellationToken cancellationToken) - { - ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; - if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) - { - telemetry.LogMissingLocalBinary(); - Log.LogErrorWithCodeFromResources(nameof(Strings.LocalRegistryNotAvailable)); - return; - } - try - { - await localRegistry.LoadAsync(images, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); - SafeLog(Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); - - if (localRegistry is ArchiveFileRegistry archive) - { - GeneratedArchiveOutputPath = archive.ArchiveOutputPath; - } - } - catch (ContainerHttpException e) - { - if (BuildEngine != null) - { - Log.LogErrorFromException(e, true); - } - } - catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) - { - telemetry.LogLocalLoadError(); - Log.LogErrorFromException(dle, showStackTrace: false); - } - catch (ArgumentException argEx) - { - Log.LogErrorFromException(argEx, showStackTrace: false); - } - } - - private async Task PushToRemoteRegistryAsync(BuiltImage[] images, - DestinationImageReference destinationImageReference, - CancellationToken cancellationToken) - { - try - { - (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images); - await destinationImageReference.RemoteRegistry!.PushManifestListAsync( - destinationImageReference.Repository, - destinationImageReference.Tags, - imageIndex, - mediaType, - cancellationToken).ConfigureAwait(false); - SafeLog(Strings.ImageIndexUploadedToRegistry, destinationImageReference, OutputRegistry); - } - catch (UnableToAccessRepositoryException) - { - if (BuildEngine != null) - { - Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); - } - } - catch (ContainerHttpException e) - { - if (BuildEngine != null) - { - Log.LogErrorFromException(e, true); - } - } - catch (Exception e) - { - if (BuildEngine != null) - { - Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); - Log.LogMessage(MessageImportance.Low, "Details: {0}", e); - } - } - } - - private void SafeLog(string message, params object[] formatParams) - { - if (BuildEngine != null) Log.LogMessage(MessageImportance.High, message, formatParams); - } - private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind) { var images = new BuiltImage[GeneratedContainers.Length]; diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index 89131d696e46..ba9eea570872 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -1,10 +1,8 @@ // 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 Microsoft.Build.Framework; using Microsoft.Extensions.Logging; -using Microsoft.NET.Build.Containers.LocalDaemons; using Microsoft.NET.Build.Containers.Logging; using Microsoft.NET.Build.Containers.Resources; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -122,7 +120,10 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) return !Log.HasLoggedErrors; } - SafeLog(Strings.ContainerBuilder_StartBuildingImage, Repository, String.Join(",", ImageTags), sourceImageReference); + if (BuildEngine != null) + { + Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_StartBuildingImage, Repository, String.Join(",", ImageTags), sourceImageReference); + } Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory, imageBuilder.IsWindows, imageBuilder.ManifestMediaType); imageBuilder.AddLayer(newLayer); @@ -177,107 +178,12 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) GeneratedContainerMediaType = builtImage.ManifestMediaType; GeneratedContainerNames = destinationImageReference.FullyQualifiedImageNames().Select(name => new Microsoft.Build.Utilities.TaskItem(name)).ToArray(); - switch (destinationImageReference.Kind) - { - case DestinationImageReferenceKind.LocalRegistry: - await PushToLocalRegistryAsync(builtImage, - sourceImageReference, - destinationImageReference, - telemetry, - cancellationToken).ConfigureAwait(false); - break; - case DestinationImageReferenceKind.RemoteRegistry: - await PushToRemoteRegistryAsync(builtImage, - sourceImageReference, - destinationImageReference, - cancellationToken).ConfigureAwait(false); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - telemetry.LogPublishSuccess(); + await ImagePublisher.PublishImage(builtImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) + .ConfigureAwait(false); return !Log.HasLoggedErrors; } - private async Task PushToLocalRegistryAsync(BuiltImage builtImage, SourceImageReference sourceImageReference, - DestinationImageReference destinationImageReference, - Telemetry telemetry, - CancellationToken cancellationToken) - { - ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; - if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) - { - telemetry.LogMissingLocalBinary(); - Log.LogErrorWithCodeFromResources(nameof(Strings.LocalRegistryNotAvailable)); - return; - } - try - { - await localRegistry.LoadAsync(builtImage, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); - SafeLog(Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); - - if (localRegistry is ArchiveFileRegistry archive) - { - GeneratedArchiveOutputPath = archive.ArchiveOutputPath; - } - } - catch (ContainerHttpException e) - { - if (BuildEngine != null) - { - Log.LogErrorFromException(e, true); - } - } - catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) - { - telemetry.LogLocalLoadError(); - Log.LogErrorFromException(dle, showStackTrace: false); - } - catch (ArgumentException argEx) - { - Log.LogErrorFromException(argEx, showStackTrace: false); - } - } - - private async Task PushToRemoteRegistryAsync(BuiltImage builtImage, SourceImageReference sourceImageReference, - DestinationImageReference destinationImageReference, - CancellationToken cancellationToken) - { - try - { - await destinationImageReference.RemoteRegistry!.PushAsync( - builtImage, - sourceImageReference, - destinationImageReference, - cancellationToken).ConfigureAwait(false); - SafeLog(Strings.ContainerBuilder_ImageUploadedToRegistry, destinationImageReference, OutputRegistry); - } - catch (UnableToAccessRepositoryException) - { - if (BuildEngine != null) - { - Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); - } - } - catch (ContainerHttpException e) - { - if (BuildEngine != null) - { - Log.LogErrorFromException(e, true); - } - } - catch (Exception e) - { - if (BuildEngine != null) - { - Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); - Log.LogMessage(MessageImportance.Low, "Details: {0}", e); - } - } - } - private void SetPorts(ImageBuilder image, ITaskItem[] exposedPorts) { foreach (var port in exposedPorts) @@ -324,11 +230,6 @@ private void SetEnvironmentVariables(ImageBuilder img, ITaskItem[] envVars) } } - private void SafeLog(string message, params object[] formatParams) - { - if (BuildEngine != null) Log.LogMessage(MessageImportance.High, message, formatParams); - } - public void Dispose() { _cancellationTokenSource.Dispose(); From 7acc5e91ae0c98cd3b29b99f89a038d29c7dbe06 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 31 Jan 2025 23:43:21 +0100 Subject: [PATCH 09/50] refactor ImagePublisher --- .../ImagePublisher.cs | 153 +++++------------- .../Tasks/CreateImageIndex.cs | 2 +- .../Tasks/CreateNewImage.cs | 2 +- 3 files changed, 45 insertions(+), 112 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index e2b76e461867..960a0b0c6027 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -8,8 +8,8 @@ namespace Microsoft.NET.Build.Containers; internal static class ImagePublisher { - public static async Task PublishImage( - BuiltImage image, + public static async Task PublishImageAsync( + BuiltImage singleArchImage, SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, @@ -23,22 +23,26 @@ public static async Task PublishImage( { case DestinationImageReferenceKind.LocalRegistry: await PushToLocalRegistryAsync( - image, + singleArchImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, - cancellationToken).ConfigureAwait(false); + cancellationToken, + destinationImageReference.LocalRegistry!.LoadAsync, + Strings.ContainerBuilder_ImageUploadedToLocalDaemon).ConfigureAwait(false); break; case DestinationImageReferenceKind.RemoteRegistry: await PushToRemoteRegistryAsync( - image, + singleArchImage, sourceImageReference, destinationImageReference, Log, BuildEngine, - cancellationToken).ConfigureAwait(false); + cancellationToken, + destinationImageReference.RemoteRegistry!.PushAsync, + Strings.ContainerBuilder_ImageUploadedToRegistry).ConfigureAwait(false); break; default: throw new ArgumentOutOfRangeException(); @@ -47,8 +51,8 @@ await PushToRemoteRegistryAsync( telemetry.LogPublishSuccess(); } - public static async Task PublishImage( - BuiltImage[] images, + public static async Task PublishImageAsync( + BuiltImage[] multiArchImage, SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, @@ -62,22 +66,35 @@ public static async Task PublishImage( { case DestinationImageReferenceKind.LocalRegistry: await PushToLocalRegistryAsync( - images, + multiArchImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, - cancellationToken).ConfigureAwait(false); + cancellationToken, + destinationImageReference.LocalRegistry!.LoadAsync, + Strings.ContainerBuilder_ImageUploadedToLocalDaemon).ConfigureAwait(false); break; case DestinationImageReferenceKind.RemoteRegistry: await PushToRemoteRegistryAsync( - images, + multiArchImage, sourceImageReference, destinationImageReference, Log, BuildEngine, - cancellationToken).ConfigureAwait(false); + cancellationToken, + async (images, source, destination, token) => + { + (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images); + await destinationImageReference.RemoteRegistry!.PushManifestListAsync( + destinationImageReference.Repository, + destinationImageReference.Tags, + imageIndex, + mediaType, + cancellationToken).ConfigureAwait(false); + }, + Strings.ImageIndexUploadedToRegistry).ConfigureAwait(false); break; default: throw new ArgumentOutOfRangeException(); @@ -86,14 +103,16 @@ await PushToRemoteRegistryAsync( telemetry.LogPublishSuccess(); } - private static async Task PushToLocalRegistryAsync( - BuiltImage image, + private static async Task PushToLocalRegistryAsync( + T image, SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, IBuildEngine? BuildEngine, Telemetry telemetry, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + Func loadFunc, + string successMessage) { ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) @@ -104,10 +123,10 @@ private static async Task PushToLocalRegistryAsync( } try { - await localRegistry.LoadAsync(image, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); + await loadFunc(image, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); if (BuildEngine != null) { - Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); + Log.LogMessage(MessageImportance.High, successMessage, destinationImageReference, localRegistry); } } catch (ContainerHttpException e) @@ -128,112 +147,26 @@ private static async Task PushToLocalRegistryAsync( } } - private static async Task PushToLocalRegistryAsync( - BuiltImage[] images, + private static async Task PushToRemoteRegistryAsync( + T image, SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, IBuildEngine? BuildEngine, - Telemetry telemetry, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + Func pushFunc, + string successMessage) { - ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; - if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) - { - telemetry.LogMissingLocalBinary(); - Log.LogErrorWithCodeFromResources(nameof(Strings.LocalRegistryNotAvailable)); - return; - } try { - await localRegistry.LoadAsync(images, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); - if (BuildEngine != null) - { - Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); - } - } - catch (ContainerHttpException e) - { - if (BuildEngine != null) - { - Log.LogErrorFromException(e, true); - } - } - catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) - { - telemetry.LogLocalLoadError(); - Log.LogErrorFromException(dle, showStackTrace: false); - } - catch (ArgumentException argEx) - { - Log.LogErrorFromException(argEx, showStackTrace: false); - } - } - - private static async Task PushToRemoteRegistryAsync( - BuiltImage image, - SourceImageReference sourceImageReference, - DestinationImageReference destinationImageReference, - Microsoft.Build.Utilities.TaskLoggingHelper Log, - IBuildEngine? BuildEngine, - CancellationToken cancellationToken) - { - try - { - await destinationImageReference.RemoteRegistry!.PushAsync( + await pushFunc( image, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); if (BuildEngine != null) { - Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToRegistry, destinationImageReference, destinationImageReference.RemoteRegistry!.RegistryName); - } - } - catch (UnableToAccessRepositoryException) - { - if (BuildEngine != null) - { - Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); - } - } - catch (ContainerHttpException e) - { - if (BuildEngine != null) - { - Log.LogErrorFromException(e, true); - } - } - catch (Exception e) - { - if (BuildEngine != null) - { - Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); - Log.LogMessage(MessageImportance.Low, "Details: {0}", e); - } - } - } - - private static async Task PushToRemoteRegistryAsync( - BuiltImage[] images, - SourceImageReference sourceImageReference, - DestinationImageReference destinationImageReference, - Microsoft.Build.Utilities.TaskLoggingHelper Log, - IBuildEngine? BuildEngine, - CancellationToken cancellationToken) - { - try - { - (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images); - await destinationImageReference.RemoteRegistry!.PushManifestListAsync( - destinationImageReference.Repository, - destinationImageReference.Tags, - imageIndex, - mediaType, - cancellationToken).ConfigureAwait(false); - if (BuildEngine != null) - { - Log.LogMessage(MessageImportance.High, Strings.ImageIndexUploadedToRegistry, destinationImageReference, destinationImageReference.RemoteRegistry!.RegistryName); + Log.LogMessage(MessageImportance.High, successMessage, destinationImageReference, destinationImageReference.RemoteRegistry!.RegistryName); } } catch (UnableToAccessRepositoryException) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 49326cd20cf9..69340f2642ac 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -155,7 +155,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); - await ImagePublisher.PublishImage(images, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) + await ImagePublisher.PublishImageAsync(images, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) .ConfigureAwait(false); return !Log.HasLoggedErrors; diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index ba9eea570872..64a298b83e53 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -178,7 +178,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) GeneratedContainerMediaType = builtImage.ManifestMediaType; GeneratedContainerNames = destinationImageReference.FullyQualifiedImageNames().Select(name => new Microsoft.Build.Utilities.TaskItem(name)).ToArray(); - await ImagePublisher.PublishImage(builtImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) + await ImagePublisher.PublishImageAsync(builtImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) .ConfigureAwait(false); return !Log.HasLoggedErrors; From 181b6fa27377cc7d6132d8a9617f5c5cd9dd9080 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sat, 1 Feb 2025 11:18:16 +0100 Subject: [PATCH 10/50] skip publishing individual images in case of tarball and local daemon docker publishing --- .../Tasks/CreateNewImage.Interface.cs | 5 +++++ .../Tasks/CreateNewImage.cs | 9 ++++++--- .../build/Microsoft.NET.Build.Containers.targets | 15 ++++++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) 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 3da5135b9b07..5e9c01de4e45 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs @@ -161,6 +161,11 @@ partial class CreateNewImage [Required] public bool GenerateDigestLabel { get; set; } + /// + /// If true, the tooling will skip the publishing step. + /// + public bool SkipPublishing { get; set; } + [Output] public string GeneratedContainerManifest { get; set; } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index 64a298b83e53..bcc429acf343 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -178,9 +178,12 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) GeneratedContainerMediaType = builtImage.ManifestMediaType; GeneratedContainerNames = destinationImageReference.FullyQualifiedImageNames().Select(name => new Microsoft.Build.Utilities.TaskItem(name)).ToArray(); - await ImagePublisher.PublishImageAsync(builtImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) - .ConfigureAwait(false); - + if (!SkipPublishing) + { + await ImagePublisher.PublishImageAsync(builtImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) + .ConfigureAwait(false); + } + return !Log.HasLoggedErrors; } diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets index 5309bdbeaf8b..8817641c790a 100644 --- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets +++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets @@ -274,6 +274,7 @@ ContainerRuntimeIdentifier="$(ContainerRuntimeIdentifier)" ContainerUser="$(ContainerUser)" RuntimeIdentifierGraphPath="$(RuntimeIdentifierGraphPath)" + SkipPublishing="$(_SkipContainerPublishing)" GenerateLabels="$(ContainerGenerateLabels)" GenerateDigestLabel="$(ContainerGenerateLabelsImageBaseDigest)"> @@ -302,6 +303,17 @@ !$(ContainerArchiveOutputPath.EndsWith('\\')) and $(ContainerArchiveOutputPath.EndsWith('.tar.gz'))" /> + + + + + <_IsTarballPublishing Condition="'$(ContainerArchiveOutputPath)' != ''">true + <_IsLocalDockerPublishing Condition="$(ContainerRegistry) == '' and ($(LocalRegistry) == '' or $(LocalRegistry) == 'Docker')">true + + <_SkipContainerPublishing>false + <_SkipContainerPublishing Condition="'$(_IsTarballPublishing)' == 'true' or '$(_IsLocalDockerPublishing)' == 'true'">true + + <_rids Include="$(ContainerRuntimeIdentifiers)" Condition="'$(ContainerRuntimeIdentifiers)' != ''" /> <_rids Include="$(RuntimeIdentifiers)" Condition="'$(ContainerRuntimeIdentifiers)' == '' and '$(RuntimeIdentifiers)' != ''" /> @@ -328,7 +340,8 @@ _ContainerEnvironmentVariables=@(ContainerEnvironmentVariable->'%(Identity):%(Value)'); ContainerUser=$(ContainerUser); ContainerGenerateLabels=$(ContainerGenerateLabels); - ContainerGenerateLabelsImageBaseDigest=$(ContainerGenerateLabelsImageBaseDigest) + ContainerGenerateLabelsImageBaseDigest=$(ContainerGenerateLabelsImageBaseDigest); + _SkipContainerPublishing=$(_SkipContainerPublishing) "/> <_rids Remove ="$(_rids)" /> From ec09d0501edad23835ba7d62768ee52ccaf4a3d1 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sat, 1 Feb 2025 11:19:04 +0100 Subject: [PATCH 11/50] update public api --- .../PublicAPI/net472/PublicAPI.Unshipped.txt | 2 ++ .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 2 ++ 2 files changed, 4 insertions(+) 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 43526e9c3ab4..9e6b0de71dc0 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 @@ -86,6 +86,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateLabels.get -> bool Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateLabels.set -> void Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateDigestLabel.get -> bool Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateDigestLabel.set -> void +Microsoft.NET.Build.Containers.Tasks.CreateNewImage.SkipPublishing.get -> bool +Microsoft.NET.Build.Containers.Tasks.CreateNewImage.SkipPublishing.set -> void Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerNames.get -> Microsoft.Build.Framework.ITaskItem![]! Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerNames.set -> void override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolName.get -> string! diff --git a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt index f7e2955bd01f..5c07a3871ceb 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -251,6 +251,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateLabels.get -> bool Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateLabels.set -> void Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateDigestLabel.get -> bool Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateDigestLabel.set -> void +Microsoft.NET.Build.Containers.Tasks.CreateNewImage.SkipPublishing.get -> bool +Microsoft.NET.Build.Containers.Tasks.CreateNewImage.SkipPublishing.set -> void Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerNames.get -> Microsoft.Build.Framework.ITaskItem![]! Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerNames.set -> void Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties From 5e38e739a16069ebea234e47c509ba7a81536783 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sat, 1 Feb 2025 13:37:57 +0100 Subject: [PATCH 12/50] skip CreateImageIndex task in case of local damon podman publishing as it is not supported --- .../Resources/Strings.Designer.cs | 9 +++++++++ .../Resources/Strings.resx | 4 ++++ .../Resources/xlf/Strings.cs.xlf | 5 +++++ .../Resources/xlf/Strings.de.xlf | 5 +++++ .../Resources/xlf/Strings.es.xlf | 5 +++++ .../Resources/xlf/Strings.fr.xlf | 5 +++++ .../Resources/xlf/Strings.it.xlf | 5 +++++ .../Resources/xlf/Strings.ja.xlf | 5 +++++ .../Resources/xlf/Strings.ko.xlf | 5 +++++ .../Resources/xlf/Strings.pl.xlf | 5 +++++ .../Resources/xlf/Strings.pt-BR.xlf | 5 +++++ .../Resources/xlf/Strings.ru.xlf | 5 +++++ .../Resources/xlf/Strings.tr.xlf | 5 +++++ .../Resources/xlf/Strings.zh-Hans.xlf | 5 +++++ .../Resources/xlf/Strings.zh-Hant.xlf | 5 +++++ .../Tasks/CreateImageIndex.cs | 8 ++++++-- .../build/Microsoft.NET.Build.Containers.targets | 12 +++++++----- 17 files changed, 91 insertions(+), 7 deletions(-) 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 aff92942f408..c2b9b565298b 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs @@ -402,6 +402,15 @@ internal static string InvalidImageMetadata { } } + /// + /// Looks up a localized string similar to Image index creation for Podman is not supported.. + /// + internal static string ImageIndex_PodmanNotSupported { + get { + return ResourceManager.GetString("ImageIndex_PodmanNotSupported", 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.. /// diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx index 6337f4861b8e..40e57e54665b 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx @@ -380,6 +380,10 @@ Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + + Image index creation for Podman is not supported. + + 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 f0d7173a9cea..8e8ff66ce2e9 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: Nepodařilo se načíst bitovou kopii z místního registru. stdout: {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 ea8fc611e0a0..c7be81fff503 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: Fehler beim Laden des Images aus der lokalen Registrierung. stdout: {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 b4921dcbdf47..cb3170c2954d 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: no se pudo cargar la imagen desde el registro local. Stdout: {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 b945393a8398..cab3d8d82aca 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: Échec du chargement de l'image à partir du registre local. sortie standard : {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 b42327bbfb94..a5f41cf91d2e 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: non è stato possibile caricare l'immagine dal registro locale. stdout: {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 5d2cf22f0f8c..cd52c02f2459 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: ローカル レジストリからイメージを読み込めませんでした。stdout: {0} 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 2ff624cec676..8f5d7f791e13 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: 로컬 레지스트리에서 이미지를 로드하지 못했습니다. stdout: {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 86288597ee7c..1cbda11cd891 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: Nie można załadować obrazu z rejestru lokalnego. stdout: {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 69fcf3548d8d..9467c4090ed2 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: falha ao carregar a imagem do registro local. stdout: {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 2989bc8e9687..295593dc4fa5 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: не удалось загрузить образ из локального реестра. stdout: {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 42fcb52485c7..49ce6f3ce305 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: Görüntü yerel kayıt defterinden yüklenemedi. stdout: {0} 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 8eb6429f7db6..dd9faa355ab4 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: 未能从本地注册表加载映像。stdout: {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 892b174bbe97..d7e262e3310a 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 @@ -164,6 +164,11 @@ Pushed image index '{0}' to registry '{1}'. + + Image index creation for Podman is not supported. + Image index creation for Podman is not supported. + + CONTAINER1009: Failed to load image from local registry. stdout: {0} CONTAINER1009: 無法從本機登錄載入映像。stdout: {0} diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 69340f2642ac..655b0e52a9d4 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -1,12 +1,10 @@ // 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 System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Build.Framework; using Microsoft.Extensions.Logging; -using Microsoft.NET.Build.Containers.LocalDaemons; using Microsoft.NET.Build.Containers.Logging; using Microsoft.NET.Build.Containers.Resources; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -128,6 +126,12 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + if (LocalRegistry == "Podman") + { + Log.LogError(Strings.ImageIndex_PodmanNotSupported); + return false; + } + using MSBuildLoggerProvider loggerProvider = new(Log); ILoggerFactory msbuildLoggerFactory = new LoggerFactory(new[] { loggerProvider }); ILogger logger = msbuildLoggerFactory.CreateLogger(); diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets index 8817641c790a..d092e5951cd4 100644 --- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets +++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets @@ -307,11 +307,12 @@ - <_IsTarballPublishing Condition="'$(ContainerArchiveOutputPath)' != ''">true - <_IsLocalDockerPublishing Condition="$(ContainerRegistry) == '' and ($(LocalRegistry) == '' or $(LocalRegistry) == 'Docker')">true - <_SkipContainerPublishing>false - <_SkipContainerPublishing Condition="'$(_IsTarballPublishing)' == 'true' or '$(_IsLocalDockerPublishing)' == 'true'">true + <_SkipContainerPublishing Condition="$(ContainerArchiveOutputPath) != '' or ( $(ContainerRegistry) == '' and ( $(LocalRegistry) == '' or $(LocalRegistry) == 'Docker' ) )">true + + + <_SkipCreateImageIndex>false + <_SkipCreateImageIndex Condition="$(ContainerArchiveOutputPath) == '' and $(ContainerRegistry) == '' and $(LocalRegistry) == 'Podman'">true @@ -353,7 +354,8 @@ - Date: Sat, 1 Feb 2025 16:46:10 +0100 Subject: [PATCH 13/50] refactor CreateImageIndex task; improve logging; log tip for enabling conrainerd --- .../ImagePublisher.cs | 15 +++- .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 2 - .../Resources/Strings.Designer.cs | 26 +++++- .../Resources/Strings.resx | 14 ++- .../Resources/xlf/Strings.cs.xlf | 22 +++-- .../Resources/xlf/Strings.de.xlf | 22 +++-- .../Resources/xlf/Strings.es.xlf | 22 +++-- .../Resources/xlf/Strings.fr.xlf | 22 +++-- .../Resources/xlf/Strings.it.xlf | 22 +++-- .../Resources/xlf/Strings.ja.xlf | 22 +++-- .../Resources/xlf/Strings.ko.xlf | 22 +++-- .../Resources/xlf/Strings.pl.xlf | 22 +++-- .../Resources/xlf/Strings.pt-BR.xlf | 22 +++-- .../Resources/xlf/Strings.ru.xlf | 22 +++-- .../Resources/xlf/Strings.tr.xlf | 22 +++-- .../Resources/xlf/Strings.zh-Hans.xlf | 22 +++-- .../Resources/xlf/Strings.zh-Hant.xlf | 22 +++-- .../Tasks/CreateImageIndex.Interface.cs | 80 +++++++++++++++++ .../Tasks/CreateImageIndex.cs | 90 +------------------ .../Tasks/CreateNewImage.cs | 5 +- .../Microsoft.NET.Build.Containers.targets | 4 +- .../CreateImageIndexTests.cs | 24 ++--- 22 files changed, 355 insertions(+), 191 deletions(-) create mode 100644 src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index 960a0b0c6027..fa890b82506a 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -74,7 +74,8 @@ await PushToLocalRegistryAsync( telemetry, cancellationToken, destinationImageReference.LocalRegistry!.LoadAsync, - Strings.ContainerBuilder_ImageUploadedToLocalDaemon).ConfigureAwait(false); + Strings.ContainerBuilder_ImageUploadedToLocalDaemon, + logWarningForMultiArch : true).ConfigureAwait(false); break; case DestinationImageReferenceKind.RemoteRegistry: await PushToRemoteRegistryAsync( @@ -112,7 +113,8 @@ private static async Task PushToLocalRegistryAsync( Telemetry telemetry, CancellationToken cancellationToken, Func loadFunc, - string successMessage) + string successMessage, + bool logWarningForMultiArch = false) { ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) @@ -145,6 +147,15 @@ private static async Task PushToLocalRegistryAsync( { Log.LogErrorFromException(argEx, showStackTrace: false); } + catch (DockerLoadException dle) + { + telemetry.LogLocalLoadError(); + Log.LogErrorFromException(dle, showStackTrace: false); + if (logWarningForMultiArch && dle.Message.Contains("no such file or directory")) + { + Log.LogMessage(MessageImportance.High, "Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings."); + } + } } private static async Task PushToRemoteRegistryAsync( diff --git a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 5c07a3871ceb..e5efa4bed6c1 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -34,8 +34,6 @@ Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedArchiveOutputPath 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![]! 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 c2b9b565298b..5750e050ba0e 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs @@ -204,6 +204,15 @@ internal static string ContainerBuilder_StartBuildingImage { } } + /// + /// Looks up a localized string similar to Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'.. + /// + internal static string ContainerBuilder_StartBuildingImageForRid { + get { + return ResourceManager.GetString("ContainerBuilder_StartBuildingImageForRid", resourceCulture); + } + } + /// /// Looks up a localized string similar to CONTAINER3001: Failed creating {0} process.. /// @@ -376,7 +385,7 @@ internal static string ImagePullNotSupported { } /// - /// Looks up a localized string similar to Cannot create manifest list (image index) because no images were provided.. + /// Looks up a localized string similar to Cannot create image index because no images were provided.. /// internal static string ImagesEmpty { get { @@ -394,7 +403,7 @@ 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.. + /// Looks up a localized string similar to Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata.. /// internal static string InvalidImageMetadata { get { @@ -402,6 +411,15 @@ internal static string InvalidImageMetadata { } } + /// + /// Looks up a localized string similar to Cannot create image index because at least one of the provided images manifest is invalid.. + /// + internal static string InvalidImageManifest { + get { + return ResourceManager.GetString("InvalidImageManifest", resourceCulture); + } + } + /// /// Looks up a localized string similar to Image index creation for Podman is not supported.. /// @@ -565,7 +583,7 @@ internal static string MissingPortNumber { } /// - /// Looks up a localized string similar to 'mediaType' of manifests should be the same in manifest list (image index).. + /// Looks up a localized string similar to 'mediaType' of manifests should be the same in image index.. /// internal static string MixedMediaTypes { get { @@ -817,7 +835,7 @@ internal static string UnrecognizedMediaType { } /// - /// Looks up a localized string similar to Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'.. + /// Looks up a localized string similar to Cannot create image index for the provided 'mediaType' = '{0}'.. /// internal static string UnsupportedMediaType { get { diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx index 40e57e54665b..a27e84824210 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx @@ -358,6 +358,10 @@ Building image '{0}' with tags '{1}' on top of base image '{2}'. + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + Building image index '{0}' on top of manifests {1}. @@ -369,15 +373,19 @@ - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) because no images were provided. + Cannot create 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. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + + + + Cannot create image index because at least one of the provided images manifest is invalid. 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 8e8ff66ce2e9..3d6e472adb12 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 @@ -79,6 +79,11 @@ Sestavení image {0} se značkami {1} nad základní imagí {2} + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: Nepovedlo se deserializovat token z JSON. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: '{1}' není platná proměnná prostředí. Ignorování. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 c7be81fff503..a9298e5a40de 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 @@ -79,6 +79,11 @@ Das Image „{0}“ mit den Tags „{1}“ wird auf dem Basisimage „{2}“ erstellt. + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: Das Token konnte nicht aus JSON deserialisiert werden. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: „{1}“ war keine gültige Umgebungsvariable. Sie wird ignoriert. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 cb3170c2954d..0e6827c5b8dd 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 @@ -79,6 +79,11 @@ Compilando imagen "{0}" con etiquetas "{1}" encima de la imagen base "{2}". + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: No se pudo deserializar el token de JSON. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: "{1}" no era una variable de entorno válida. Ignorando. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 cab3d8d82aca..d9fd1c30d615 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 @@ -79,6 +79,11 @@ Génération de l’image «{0}» avec des balises «{1}» au-dessus de l’image de base «{2}». + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: impossible de désérialiser le jeton à partir de JSON. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0} : '{1}' n’était pas une variable d’environnement valide. Ignorant. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 a5f41cf91d2e..8fb75366363b 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 @@ -79,6 +79,11 @@ Compilazione dell'immagine '{0}' con i tag '{1}' sopra l'immagine di base '{2}'. + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: non è stato possibile deserializzare il token da JSON. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: '{1}' non è una variabile di ambiente valida. Il valore verrà ignorato. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 cd52c02f2459..00aeda5edf91 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 @@ -79,6 +79,11 @@ 基本イメージ '{0}' の上にタグ '{1}' 付きのイメージ '{2}' をビルドしています。 + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: JSON からトークンを逆シリアル化できませんでした。 @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: '{1}' は有効な環境変数ではありませんでした。無視しています。 {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. 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 8f5d7f791e13..0adadf299470 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 @@ -79,6 +79,11 @@ 기본 이미지 '{2}' 위에 '{1}' 태그가 있는 '{0}' 이미지를 빌드하는 중입니다. + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: JSON에서 토큰을 역직렬화할 수 없습니다. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: '{1}'은(는) 유효한 환경 변수가 아닙니다. 무시 중. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 1cbda11cd891..1fc1770a7181 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 @@ -79,6 +79,11 @@ Tworzenie obrazu „{0}” z tagami „{1}” nad obrazem podstawowym „{2}”. + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: nie można zdeserializować tokenu z pliku JSON. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: „{1}” nie jest prawidłową zmienną środowiskową. Ignorowanie. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 9467c4090ed2..8e6ce774b100 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 @@ -79,6 +79,11 @@ Construindo imagem '{0}' com tags '{1}' em cima da imagem base '{2}'. + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: não foi possível desserializar o token do JSON. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: '{1}' não era uma variável de ambiente válida. Ignorando. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 295593dc4fa5..0d0d9417f2ca 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 @@ -79,6 +79,11 @@ Выполняется сборка образа "{0}" с тегами "{1}" поверх базового образа "{2}". + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: не удалось десериализовать токен из JSON. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: "{1}" не является допустимой переменной среды. Пропуск. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 49ce6f3ce305..9f65b5082fba 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 @@ -79,6 +79,11 @@ '{1}' etiketli '{0}' resmi, '{2}' temel görüntüsünün üzerinde oluşturuluyor. + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: Belirteç, JSON'dan seri durumdan çıkarılamadı. @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: '{1}' geçerli bir Ortam Değişkeni değildi. Görmezden geliniyor. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. 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 dd9faa355ab4..0f5ad6a2d7a4 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 @@ -79,6 +79,11 @@ 在基本映像“{2}”顶部构建标记为“{1}”的映像“{0}”。 + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: 无法从 JSON 反序列化令牌。 @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: "{1}" 不是有效的环境变量。忽略。 {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided '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 d7e262e3310a..1287300e7b18 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 @@ -79,6 +79,11 @@ 在基礎映像 '{2}' 上建立具有標記 '{1}' 的映像 '{0}'。 + + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + Building image '{0}' for runtime identifier '{1}' on top of base image '{2}'. + + CONTAINER1007: Could not deserialize token from JSON. CONTAINER1007: 無法從 JSON 還原序列化權杖。 @@ -180,8 +185,8 @@ {StrBegin="CONTAINER1010: "} - Cannot create manifest list (image index) because no images were provided. - Cannot create manifest list (image index) because no images were provided. + Cannot create image index because no images were provided. + Cannot create image index because no images were provided. @@ -189,9 +194,14 @@ CONTAINER2015: {0}: '{1}' 不是有效的環境變數。正在忽略。 {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images manifest is invalid. + + - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. - Cannot create manifest list (image index) because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. + Cannot create image index because provided images are invalid. Items must have 'Config', 'Manifest', 'ManifestMediaType' and 'ManifestDigest' metadata. @@ -420,8 +430,8 @@ {StrBegin="CONTAINER2001: "} - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. - Cannot create manifest list (image index) for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. + Cannot create image index for the provided 'mediaType' = '{0}'. diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs new file mode 100644 index 000000000000..128657b0a897 --- /dev/null +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.Framework; + +namespace Microsoft.NET.Build.Containers.Tasks; + +partial class CreateImageIndex +{ + /// + /// The base registry to pull from. + /// Ex: mcr.microsoft.com + /// + [Required] + public string BaseRegistry { get; set; } + + /// + /// The base image to pull. + /// Ex: dotnet/runtime + /// + [Required] + public string BaseImageName { get; set; } + + /// + /// The base image tag. + /// Ex: 6.0 + /// + [Required] + public string BaseImageTag { get; set; } + + /// + /// Manifests to include in the image index. + /// + [Required] + public ITaskItem[] GeneratedContainers { get; set; } + + /// + /// The registry to push the image index to. + /// + public string OutputRegistry { get; set; } + + /// + /// The file path to which to write a tar.gz archive of the container image. + /// + public string ArchiveOutputPath { get; set; } + + /// + /// The kind of local registry to use, if any. + /// + public string LocalRegistry { 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; } + + [Output] + public string GeneratedArchiveOutputPath { get; set; } + + public CreateImageIndex() + { + BaseRegistry = string.Empty; + BaseImageName = string.Empty; + BaseImageTag = string.Empty; + GeneratedContainers = Array.Empty(); + OutputRegistry = string.Empty; + ArchiveOutputPath = string.Empty; + LocalRegistry = string.Empty; + Repository = string.Empty; + ImageTags = Array.Empty(); + GeneratedArchiveOutputPath = string.Empty; + } +} \ No newline at end of file diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 655b0e52a9d4..eb5743d81846 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -12,88 +12,8 @@ namespace Microsoft.NET.Build.Containers.Tasks; -public sealed class CreateImageIndex : Microsoft.Build.Utilities.Task, ICancelableTask, IDisposable +public sealed partial class CreateImageIndex : Microsoft.Build.Utilities.Task, ICancelableTask, IDisposable { - #region Parameters - /// - /// The base registry to pull from. - /// Ex: mcr.microsoft.com - /// - [Required] - public string BaseRegistry { get; set; } - - /// - /// The base image to pull. - /// Ex: dotnet/runtime - /// - [Required] - public string BaseImageName { get; set; } - - /// - /// The base image tag. - /// Ex: 6.0 - /// - [Required] - public string BaseImageTag { get; set; } - - /// - /// Manifests to include in the image index. - /// - [Required] - public ITaskItem[] GeneratedContainers { get; set; } - - /// - /// The registry to push the image index to. - /// - public string OutputRegistry { get; set; } - - /// - /// The file path to which to write a tar.gz archive of the container image. - /// - public string ArchiveOutputPath { get; set; } - - /// - /// The kind of local registry to use, if any. - /// - public string LocalRegistry { 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; } - - [Output] - public string GeneratedArchiveOutputPath { get; set; } - - /// - /// The generated image index (manifest list) in JSON format. - /// - [Output] - public string GeneratedImageIndex { get; set; } - - public CreateImageIndex() - { - BaseRegistry = string.Empty; - BaseImageName = string.Empty; - BaseImageTag = string.Empty; - GeneratedContainers = Array.Empty(); - OutputRegistry = string.Empty; - ArchiveOutputPath = string.Empty; - LocalRegistry = string.Empty; - Repository = string.Empty; - ImageTags = Array.Empty(); - GeneratedArchiveOutputPath = string.Empty; - GeneratedImageIndex = string.Empty; - } - #endregion - private readonly CancellationTokenSource _cancellationTokenSource = new(); public void Cancel() => _cancellationTokenSource.Cancel(); @@ -178,18 +98,16 @@ private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind) string manifest = unparsedImage.GetMetadata("Manifest"); string manifestMediaType = unparsedImage.GetMetadata("ManifestMediaType"); - //TODO: add manifestmedia type to the error message - if (string.IsNullOrEmpty(config) || string.IsNullOrEmpty(manifestDigest) || string.IsNullOrEmpty(manifest)) + if (string.IsNullOrEmpty(config) || string.IsNullOrEmpty(manifestDigest) || string.IsNullOrEmpty(manifest) || string.IsNullOrEmpty(manifestMediaType)) { - Log.LogError(Strings.InvalidImageMetadata, unparsedImage.ItemSpec); + Log.LogError(Strings.InvalidImageMetadata); break; } var manifestV2 = JsonSerializer.Deserialize(manifest); if (manifestV2 == null) { - //TODO: log new error about manifest not deserealized - Log.LogError(Strings.InvalidImageMetadata, unparsedImage.ItemSpec); + Log.LogError(Strings.InvalidImageManifest); break; } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index bcc429acf343..997ca7b74973 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -122,7 +122,10 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) if (BuildEngine != null) { - Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_StartBuildingImage, Repository, String.Join(",", ImageTags), sourceImageReference); + (string message, object[] parameters) = SkipPublishing ? + ( Strings.ContainerBuilder_StartBuildingImageForRid, new object[] { Repository, ContainerRuntimeIdentifier, sourceImageReference }) : + ( Strings.ContainerBuilder_StartBuildingImage, new object[] { Repository, String.Join(",", ImageTags), sourceImageReference }); + Log.LogMessage(MessageImportance.High, message, parameters); } Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory, imageBuilder.IsWindows, imageBuilder.ManifestMediaType); diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets index d092e5951cd4..3854f88ef29f 100644 --- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets +++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets @@ -363,9 +363,7 @@ ImageTags="@(ContainerImageTags)" BaseRegistry="$(ContainerBaseRegistry)" BaseImageName="$(ContainerBaseName)" - BaseImageTag="$(ContainerBaseTag)"> - - + BaseImageTag="$(ContainerBaseTag)" /> diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs index 2615b2e178ee..18f0580c6935 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs @@ -44,18 +44,18 @@ public async Task CreateImageIndex_Baseline() 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 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); From 6b147e192ca5e17d5b35f0d58d29d1d84dab0113 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sat, 1 Feb 2025 21:44:28 +0100 Subject: [PATCH 14/50] add support for multi tagoci tarball --- .../LocalDaemons/DockerCli.cs | 77 +++++++++---------- .../Resources/Strings.Designer.cs | 11 +-- .../Resources/Strings.resx | 3 - .../Resources/xlf/Strings.cs.xlf | 5 -- .../Resources/xlf/Strings.de.xlf | 5 -- .../Resources/xlf/Strings.es.xlf | 5 -- .../Resources/xlf/Strings.fr.xlf | 5 -- .../Resources/xlf/Strings.it.xlf | 5 -- .../Resources/xlf/Strings.ja.xlf | 5 -- .../Resources/xlf/Strings.ko.xlf | 5 -- .../Resources/xlf/Strings.pl.xlf | 5 -- .../Resources/xlf/Strings.pt-BR.xlf | 5 -- .../Resources/xlf/Strings.ru.xlf | 5 -- .../Resources/xlf/Strings.tr.xlf | 5 -- .../Resources/xlf/Strings.zh-Hans.xlf | 5 -- .../Resources/xlf/Strings.zh-Hant.xlf | 5 -- 16 files changed, 37 insertions(+), 119 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 112887aa5a6e..2e9d41ff9e74 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -122,6 +122,7 @@ await writeStreamFunc(image, sourceReference, destinationReference, loadProcess. } public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) + // For loading to the local registry, we use the Docker format. Two reasons: one - compatibility with previous behavior before oci formatted publishing was available, two - Podman cannot load multi tag oci image tarball. => await LoadAsync(image, sourceReference, destinationReference, WriteDockerImageToStreamAsync, cancellationToken); public async Task LoadAsync(BuiltImage[] images, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) @@ -406,11 +407,6 @@ public static async Task WriteMultiArchOciImageToStreamAsync( { cancellationToken.ThrowIfCancellationRequested(); - if (destinationReference.Tags.Length > 1) - { - throw new ArgumentException(Resource.FormatString(nameof(Strings.OciImageMultipleTagsNotSupported))); - } - using TarWriter writer = new(imageStream, TarEntryFormat.Pax, leaveOpen: true); foreach (var image in images) @@ -435,11 +431,6 @@ private static async Task WriteOciImageToStreamAsync( { cancellationToken.ThrowIfCancellationRequested(); - if (destinationReference.Tags.Length > 1) - { - throw new ArgumentException(Resource.FormatString(nameof(Strings.OciImageMultipleTagsNotSupported))); - } - using TarWriter writer = new(imageStream, TarEntryFormat.Pax, leaveOpen: true); await WriteOciImageToBlobs(writer, image, sourceReference, cancellationToken) @@ -511,8 +502,6 @@ private static async Task WriteIndexJsonForMultiArchOciImage( // 1. create manifest list for the blobs cancellationToken.ThrowIfCancellationRequested(); - string tag = destinationReference.Tags[0]; - var manifests = new PlatformSpecificOciManifest[images.Length]; for (int i = 0; i < images.Length; i++) { @@ -554,24 +543,28 @@ private static async Task WriteIndexJsonForMultiArchOciImage( // 2. create index json that points to manifest list in the blobs cancellationToken.ThrowIfCancellationRequested(); + var manifestsIndexJson = new PlatformSpecificOciManifest[destinationReference.Tags.Length]; + for (int i = 0; i < destinationReference.Tags.Length; i++) + { + var tag = destinationReference.Tags[i]; + manifestsIndexJson[i] = new PlatformSpecificOciManifest + { + mediaType = SchemaTypes.OciImageIndexV1, + size = manifestListJson.Length, + digest = manifestListDigest, + annotations = new Dictionary + { + { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, + { "org.opencontainers.image.ref.name", tag } + } + }; + } + var index = new ImageIndexV1 { schemaVersion = 2, mediaType = SchemaTypes.OciImageIndexV1, - manifests = - [ - new PlatformSpecificOciManifest - { - mediaType = SchemaTypes.OciImageIndexV1, - size = manifestListJson.Length, - digest = manifestListDigest, - annotations = new Dictionary - { - { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, - { "org.opencontainers.image.ref.name", tag } - }, - } - ] + manifests = manifestsIndexJson }; using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(JsonSerializer.SerializeToNode(index, options)!.ToJsonString()))) @@ -592,26 +585,28 @@ private static async Task WriteIndexJsonForOciImage( { cancellationToken.ThrowIfCancellationRequested(); - string tag = destinationReference.Tags[0]; + var manifests = new PlatformSpecificOciManifest[destinationReference.Tags.Length]; + for (int i = 0; i < destinationReference.Tags.Length; i++) + { + var tag = destinationReference.Tags[i]; + manifests[i] = new PlatformSpecificOciManifest + { + mediaType = SchemaTypes.OciManifestV1, + size = image.Manifest.Length, + digest = image.ManifestDigest, + annotations = new Dictionary + { + { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, + { "org.opencontainers.image.ref.name", tag } + } + }; + } var index = new ImageIndexV1 { schemaVersion = 2, mediaType = SchemaTypes.OciImageIndexV1, - manifests = - [ - new PlatformSpecificOciManifest - { - mediaType = SchemaTypes.OciManifestV1, - size = image.Manifest.Length, - digest = image.ManifestDigest, - annotations = new Dictionary - { - { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, - { "org.opencontainers.image.ref.name", tag } - } - } - ] + manifests = manifests }; var options = new JsonSerializerOptions 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 5750e050ba0e..7ad9616ae2c1 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs @@ -608,16 +608,7 @@ internal static string NormalizedContainerName { return ResourceManager.GetString("NormalizedContainerName", resourceCulture); } } - - /// - /// Looks up a localized string similar to Unable to create tarball for oci image with multiple tags.. - /// - internal static string OciImageMultipleTagsNotSupported { - get { - return ResourceManager.GetString("OciImageMultipleTagsNotSupported", resourceCulture); - } - } - + /// /// Looks up a localized string similar to CONTAINER2011: {0} '{1}' does not exist. /// diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx index a27e84824210..15f377704a50 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx @@ -244,9 +244,6 @@ CONTAINER2004: Unable to download layer with descriptor '{0}' from registry '{1}' because it does not exist. {StrBegin="CONTAINER2004: "} - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for mediaType '{0}'. 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 3d6e472adb12..bc89aedef99e 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 @@ -304,11 +304,6 @@ '{0}' nebyl platný název image kontejneru, byl normalizován na '{1}' - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} '{1}' neexistuje. 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 a9298e5a40de..23f4eeafe797 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 @@ -304,11 +304,6 @@ „{0}“ war kein gültiger Containerimagename, er wurde zu „{1}“ normalisiert. - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} „{1}“ ist nicht vorhanden. 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 0e6827c5b8dd..8533ff49f64d 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 @@ -304,11 +304,6 @@ "{0}" no era un nombre de imagen de contenedor válido, se normalizó a "{1}" - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} "{1}" no existe 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 d9fd1c30d615..127a97b70635 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 @@ -304,11 +304,6 @@ '{0}' n’était pas un nom d’image conteneur valide, il a été normalisé pour '{1}' - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} '{1}' n’existe pas 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 8fb75366363b..2c6f97c87e93 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 @@ -304,11 +304,6 @@ '{0}' non è un nome di immagine contenitore valido, è stato normalizzato in '{1}' - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} '{1}' non esiste 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 00aeda5edf91..feb83548f3c1 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 @@ -304,11 +304,6 @@ '{0}' は有効なコンテナー イメージ名ではありませんでした。'{1}' に正規化されました - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} '{1}' が存在しません 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 0adadf299470..c696a7cb649c 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 @@ -304,11 +304,6 @@ '{0}'은(는) 유효한 컨테이너 이미지 이름이 아닙니다. '{1}'(으)로 정규화되었습니다. - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} '{1}'이(가) 존재하지 않습니다. 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 1fc1770a7181..165f11851003 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 @@ -304,11 +304,6 @@ Nazwa „{0}” nie była prawidłową nazwą obrazu kontenera, została znormalizowana do „{1}” - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} „{1}” nie istnieje 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 8e6ce774b100..82b3f1b1a646 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 @@ -304,11 +304,6 @@ '{0}' não era um nome de imagem de contêiner válido, foi normalizado para '{1}' - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} '{1}' não existe 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 0d0d9417f2ca..8a4e2d1ba8f0 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 @@ -304,11 +304,6 @@ "{0}" не является допустимым именем образа контейнера, оно нормализовано до "{1}" - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} "{1}" не существует 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 9f65b5082fba..4b2b0147c029 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 @@ -304,11 +304,6 @@ '{0}', geçerli bir kapsayıcı görüntüsü adı değildi, '{1}' olarak normalleştirildi - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} '{1}' yok 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 0f5ad6a2d7a4..a02595dc1a7b 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 @@ -304,11 +304,6 @@ “{0}”不是有效的容器映像名称,已规范化为“{1}” - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} {1} 不存在 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 1287300e7b18..e1e859d58559 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 @@ -304,11 +304,6 @@ '{0}' 不是有效的容器映像名稱,已標準化為 '{1}' - - Unable to create tarball for oci image with multiple tags. - Unable to create tarball for oci image with multiple tags. - - CONTAINER2011: {0} '{1}' does not exist CONTAINER2011: {0} '{1}' 不存在 From 999368fd3332cedf47bfde4e018eb40ce0b3b095 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sun, 2 Feb 2025 16:17:42 +0100 Subject: [PATCH 15/50] fix test EndToEndMultiArch_LocalRegistry --- .../ContainerCli.cs | 3 ++ .../EndToEndTests.cs | 37 ++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs index 82ccc3d4a2c9..a7de99fc1faf 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs @@ -37,6 +37,9 @@ public static RunExeCommand LoadCommand(ITestOutputHelper log, params string[] a public static RunExeCommand PortCommand(ITestOutputHelper log, string containerName, int port) => CreateCommand(log, "port", containerName, port.ToString()); + public static RunExeCommand ImagesCommand(ITestOutputHelper log, params string[] args) + => CreateCommand(log, "images", args); + private static RunExeCommand CreateCommand(ITestOutputHelper log, string command, params string[] args) { string commandPath = IsPodman ? "podman" : "docker"; diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 40083f8fc58e..306b35858372 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -726,9 +726,8 @@ public void EndToEnd_SingleArch_NoRid() public void EndToEndMultiArch_LocalRegistry() { string imageName = NewImageName(); - string imageTag = "1.0"; - string imageX64 = $"{imageName}:{imageTag}-linux-x64"; - string imageArm64 = $"{imageName}:{imageTag}-linux-arm64"; + string tag = "1.0"; + string image = $"{imageName}:{tag}"; // Create a new console project DirectoryInfo newProjectDir = CreateNewProject("console"); @@ -741,7 +740,7 @@ public void EndToEndMultiArch_LocalRegistry() "/p:RuntimeIdentifiers=\"linux-x64;linux-arm64\"", $"/p:ContainerBaseImage={DockerRegistryManager.FullyQualifiedBaseImageAspNet}", $"/p:ContainerRepository={imageName}", - $"/p:ContainerImageTag={imageTag}", + $"/p:ContainerImageTag={tag}", "/p:EnableSdkContainerSupport=true") .WithWorkingDirectory(newProjectDir.FullName) .Execute(); @@ -752,26 +751,33 @@ public void EndToEndMultiArch_LocalRegistry() commandResult.Should().Pass() .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) - .And.HaveStdOutContaining($"Pushed image '{imageX64}' to local registry") - .And.HaveStdOutContaining($"Pushed image '{imageArm64}' to local registry") - .And.NotHaveStdOutContaining("Pushed image index"); + .And.HaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-x64'") + .And.HaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-arm64'") + .And.HaveStdOutContaining($"Pushed image '{image}' to local registry"); + + //Multi-arch oci images that are loaded to docker can only be run by their image id + string imageId = GetImageId(image); // Check that the containers can be run CommandResult processResultX64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/amd64", "--name", $"test-container-{imageName}-x64", - imageX64) + imageId) .Execute(); processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); CommandResult processResultArm64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/arm64", "--name", $"test-container-{imageName}-arm64", - imageArm64) + imageId) .Execute(); processResultArm64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -779,6 +785,19 @@ public void EndToEndMultiArch_LocalRegistry() newProjectDir.Delete(true); } + private string GetImageId(string image) + { + CommandResult commandResult = ContainerCli.ImagesCommand(_testOutput, "--format", "\"{{.ID}}\"", image) + .Execute(); + commandResult.Should().Pass(); + + var output = commandResult.StdOut.Split("\n").Select(s => s.Trim('"')).ToList(); + + output.Should().NotBeNullOrEmpty().And.OnlyContain(s => s == output[0]); + + return output[0]; + } + [DockerAvailableFact] public void MultiArchStillAllowsSingleRID() { From 0c9bdd1eb0167f127317c1953230f4f8572e4b8e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 4 Dec 2024 14:32:25 +0100 Subject: [PATCH 16/50] check docker availability before arch support in tests --- .../ContainerCli.cs | 5 +++++ .../DockerSupportsArchFact.cs | 16 ++++++++-------- .../DockerSupportsArchInlineData.cs | 2 ++ .../EndToEndTests.cs | 10 +++++----- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs index a7de99fc1faf..1315d341f94f 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs +++ b/src/Tests/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); @@ -63,4 +65,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/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs index 984bff2c0b37..36ad9fbc0c9a 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs @@ -3,17 +3,17 @@ namespace Microsoft.NET.Build.Containers.IntegrationTests; -public class DockerSupportsArchFactAttribute : FactAttribute +public class DockerIsAvailableAndSupportsArchFactAttribute : FactAttribute { - private readonly string _arch; - - public DockerSupportsArchFactAttribute(string arch) + public DockerIsAvailableAndSupportsArchFactAttribute(string arch) { - _arch = arch; - - if (!DockerSupportsArchHelper.DaemonSupportsArch(_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}."; + base.Skip = $"Skipping test because Docker daemon does not support {arch}."; } } } diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs index 022a8e90c16a..caafbaea4b45 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs @@ -34,6 +34,8 @@ public override IEnumerable GetData(MethodInfo testMethod) internal static class DockerSupportsArchHelper { + internal static bool DaemonIsAvailable => ContainerCli.IsAvailable; + internal static bool DaemonSupportsArch(string arch) { // an optimization - this doesn't change over time so we can compute it once diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 306b35858372..a9d694adaddc 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -722,7 +722,7 @@ public void EndToEnd_SingleArch_NoRid() processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); } - [DockerSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64")] public void EndToEndMultiArch_LocalRegistry() { string imageName = NewImageName(); @@ -923,7 +923,7 @@ private DirectoryInfo CreateNewProject(string template, [CallerMemberName] strin private string GetPublishArtifactsPath(string projectDir, string rid, string configuration = "Debug") => Path.Combine(projectDir, "bin", configuration, ToolsetInfo.CurrentTargetFramework, rid, "publish"); - [DockerSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64")] public void EndToEndMultiArch_ArchivePublishing() { string imageName = NewImageName(); @@ -996,7 +996,7 @@ public void EndToEndMultiArch_ArchivePublishing() newProjectDir.Delete(true); } - [DockerSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64")] public void EndToEndMultiArch_RemoteRegistry() { string imageName = NewImageName(); @@ -1125,7 +1125,7 @@ public void EndToEndMultiArch_ContainerRuntimeIdentifiersOverridesRuntimeIdentif newProjectDir.Delete(true); } - [DockerSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64")] public void EndToEndMultiArch_EnvVariables() { string imageName = NewImageName(); @@ -1192,7 +1192,7 @@ public void EndToEndMultiArch_EnvVariables() newProjectDir.Delete(true); } - [DockerSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64")] public void EndToEndMultiArch_Ports() { string imageName = NewImageName(); From 7b09eb9675ce92d6da17b5c8b7a373522816b55a Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sun, 2 Feb 2025 18:42:01 +0100 Subject: [PATCH 17/50] refactoring --- .../Microsoft.NET.Build.Containers/ImagePublisher.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index fa890b82506a..163c5e5efb26 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -30,8 +30,7 @@ await PushToLocalRegistryAsync( BuildEngine, telemetry, cancellationToken, - destinationImageReference.LocalRegistry!.LoadAsync, - Strings.ContainerBuilder_ImageUploadedToLocalDaemon).ConfigureAwait(false); + destinationImageReference.LocalRegistry!.LoadAsync).ConfigureAwait(false); break; case DestinationImageReferenceKind.RemoteRegistry: await PushToRemoteRegistryAsync( @@ -74,7 +73,6 @@ await PushToLocalRegistryAsync( telemetry, cancellationToken, destinationImageReference.LocalRegistry!.LoadAsync, - Strings.ContainerBuilder_ImageUploadedToLocalDaemon, logWarningForMultiArch : true).ConfigureAwait(false); break; case DestinationImageReferenceKind.RemoteRegistry: @@ -113,7 +111,6 @@ private static async Task PushToLocalRegistryAsync( Telemetry telemetry, CancellationToken cancellationToken, Func loadFunc, - string successMessage, bool logWarningForMultiArch = false) { ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; @@ -128,7 +125,7 @@ private static async Task PushToLocalRegistryAsync( await loadFunc(image, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); if (BuildEngine != null) { - Log.LogMessage(MessageImportance.High, successMessage, destinationImageReference, localRegistry); + Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); } } catch (ContainerHttpException e) From 6e1035ba2621c7ad7e3c415dbdffa52015956c74 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sun, 2 Feb 2025 18:42:28 +0100 Subject: [PATCH 18/50] fixed multi-arch e2e tests --- .../EndToEndTests.cs | 101 ++++++++++-------- 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index a9d694adaddc..2881ae159aa6 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -746,8 +746,7 @@ public void EndToEndMultiArch_LocalRegistry() .Execute(); // Check that the app was published for each RID, - // images were created locally for each RID - // and image index was NOT created + // one image was created locally commandResult.Should().Pass() .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) @@ -755,7 +754,7 @@ public void EndToEndMultiArch_LocalRegistry() .And.HaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-arm64'") .And.HaveStdOutContaining($"Pushed image '{image}' to local registry"); - //Multi-arch oci images that are loaded to docker can only be run by their image id + //Multi-arch oci tarballs that are loaded to docker can only be run by their image id string imageId = GetImageId(image); // Check that the containers can be run @@ -927,12 +926,10 @@ private string GetPublishArtifactsPath(string projectDir, string rid, string con public void EndToEndMultiArch_ArchivePublishing() { string imageName = NewImageName(); - string imageTag = "1.0"; - string imageX64 = $"{imageName}:{imageTag}-linux-x64"; - string imageArm64 = $"{imageName}:{imageTag}-linux-arm64"; + string tag = "1.0"; + string image = $"{imageName}:{tag}"; string archiveOutput = Path.Combine(TestSettings.TestArtifactsDirectory, "tarballs-output"); - string imageX64Tarball = Path.Combine(archiveOutput, $"{imageName}-linux-x64.tar.gz"); - string imageArm64Tarball = Path.Combine(archiveOutput, $"{imageName}-linux-arm64.tar.gz"); + string imageTarball = Path.Combine(archiveOutput, $"{imageName}.tar.gz"); // Create a new console project DirectoryInfo newProjectDir = CreateNewProject("console"); @@ -946,49 +943,51 @@ public void EndToEndMultiArch_ArchivePublishing() $"/p:ContainerArchiveOutputPath={archiveOutput}", $"/p:ContainerBaseImage={DockerRegistryManager.FullyQualifiedBaseImageAspNet}", $"/p:ContainerRepository={imageName}", - $"/p:ContainerImageTag={imageTag}", + $"/p:ContainerImageTag={tag}", "/p:EnableSdkContainerSupport=true") .WithWorkingDirectory(newProjectDir.FullName) .Execute(); // Check that the app was published for each RID, - // images were created locally for each RID - // and image index was NOT created + // one image was created in local archive commandResult.Should().Pass() .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) - .And.HaveStdOutContaining($"Pushed image '{imageX64}' to local archive at '{imageX64Tarball}'") - .And.HaveStdOutContaining($"Pushed image '{imageArm64}' to local archive at '{imageArm64Tarball}'") - .And.NotHaveStdOutContaining("Pushed image index"); + .And.HaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-x64'") + .And.HaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-arm64'") + .And.HaveStdOutContaining($"Pushed image '{image}' to local archive at '{imageTarball}'"); // Check that tarballs were created - File.Exists(imageX64Tarball).Should().BeTrue(); - File.Exists(imageArm64Tarball).Should().BeTrue(); + File.Exists(imageTarball).Should().BeTrue(); - // Load the images from the tarballs - ContainerCli.LoadCommand(_testOutput, "--input", imageX64Tarball) - .Execute() - .Should().Pass(); - ContainerCli.LoadCommand(_testOutput, "--input", imageArm64Tarball) + // Load the multi-arch image from the tarball + ContainerCli.LoadCommand(_testOutput, "--input", imageTarball) .Execute() .Should().Pass(); + //Multi-arch oci tarballs that are loaded to docker can only be run by their image id + string imageId = GetImageId(image); + // Check that the containers can be run CommandResult processResultX64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/amd64", "--name", $"test-container-{imageName}-x64", - imageX64) + imageId) .Execute(); processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); CommandResult processResultArm64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/arm64", "--name", $"test-container-{imageName}-arm64", - imageArm64) + imageId) .Execute(); processResultArm64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -1118,8 +1117,8 @@ public void EndToEndMultiArch_ContainerRuntimeIdentifiersOverridesRuntimeIdentif commandResult.Should().Pass() .And.NotHaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) - .And.NotHaveStdOutContaining($"Pushed image '{imageName}:{imageTag}-linux-x64' to local registry") - .And.HaveStdOutContaining($"Pushed image '{imageName}:{imageTag}-linux-arm64' to local registry"); + .And.NotHaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-x64'") + .And.HaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-arm64'"); // Cleanup newProjectDir.Delete(true); @@ -1129,9 +1128,8 @@ public void EndToEndMultiArch_ContainerRuntimeIdentifiersOverridesRuntimeIdentif public void EndToEndMultiArch_EnvVariables() { string imageName = NewImageName(); - string imageTag = "1.0"; - string imageX64 = $"{imageName}:{imageTag}-linux-x64"; - string imageArm64 = $"{imageName}:{imageTag}-linux-arm64"; + string tag = "1.0"; + string image = $"{imageName}:{tag}"; // Create new console app, set ContainerEnvironmentVariables, and set to output env variable DirectoryInfo newProjectDir = CreateNewProject("console"); @@ -1160,31 +1158,37 @@ public void EndToEndMultiArch_EnvVariables() "/p:RuntimeIdentifiers=\"linux-x64;linux-arm64\"", $"/p:ContainerBaseImage={DockerRegistryManager.FullyQualifiedBaseImageAspNet}", $"/p:ContainerRepository={imageName}", - $"/p:ContainerImageTag={imageTag}", + $"/p:ContainerImageTag={tag}", "/p:EnableSdkContainerSupport=true") .WithWorkingDirectory(newProjectDir.FullName) .Execute() .Should().Pass(); - // Check that the env var is printed + string imageId = GetImageId(image); + + // Check that the env var is printed for linux/amd64 platform string containerNameX64 = $"test-container-{imageName}-x64"; CommandResult processResultX64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/amd64", "--name", containerNameX64, - imageX64) + imageId) .Execute(); processResultX64.Should().Pass().And.HaveStdOut("FooBar"); - // Check that the env var is printed + // Check that the env var is printed for linux/arm64 platform string containerNameArm64 = $"test-container-{imageName}-arm64"; CommandResult processResultArm64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/arm64", "--name", containerNameArm64, - imageArm64) + imageId) .Execute(); processResultArm64.Should().Pass().And.HaveStdOut("FooBar"); @@ -1196,9 +1200,8 @@ public void EndToEndMultiArch_EnvVariables() public void EndToEndMultiArch_Ports() { string imageName = NewImageName(); - string imageTag = "1.0"; - string imageX64 = $"{imageName}:{imageTag}-linux-x64"; - string imageArm64 = $"{imageName}:{imageTag}-linux-arm64"; + string tag = "1.0"; + string image = $"{imageName}:{tag}"; // Create new web app, set ContainerPort DirectoryInfo newProjectDir = CreateNewProject("webapp"); @@ -1222,38 +1225,44 @@ public void EndToEndMultiArch_Ports() "/p:RuntimeIdentifiers=\"linux-x64;linux-arm64\"", $"/p:ContainerBaseImage={DockerRegistryManager.FullyQualifiedBaseImageAspNet}", $"/p:ContainerRepository={imageName}", - $"/p:ContainerImageTag={imageTag}", + $"/p:ContainerImageTag={tag}", "/p:EnableSdkContainerSupport=true") .WithWorkingDirectory(newProjectDir.FullName) .Execute() .Should().Pass(); - // Check that the ports are correct + string imageId = GetImageId(image); + + // Check that the ports are correct for linux/amd64 platform var containerNameX64 = $"test-container-{imageName}-x64"; CommandResult processResultX64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/amd64", "--name", containerNameX64, "-P", "--detach", - imageX64) + imageId) .Execute(); processResultX64.Should().Pass(); // 8080 is the default port CheckPorts(containerNameX64, [8080, 8082, 8083], [8081]); - // Check that the ports are correct + // Check that the ports are correct for linux/arm64 platform var containerNameArm64 = $"test-container-{imageName}-arm64"; CommandResult processResultArm64 = ContainerCli.RunCommand( _testOutput, "--rm", + "--platform", + "linux/arm64", "--name", containerNameArm64, "-P", "--detach", - imageArm64) + imageId) .Execute(); processResultArm64.Should().Pass(); @@ -1291,8 +1300,8 @@ private void CheckPorts(string containerName, int[] correctPorts, int[] incorrec public void EndToEndMultiArch_Labels() { string imageName = NewImageName(); - string imageTag = "1.0"; - string imageX64 = $"{imageName}:{imageTag}-linux-x64"; + string tag = "1.0"; + string image = $"{imageName}:{tag}"; // Create new console app DirectoryInfo newProjectDir = CreateNewProject("webapp"); @@ -1305,17 +1314,19 @@ public void EndToEndMultiArch_Labels() "/p:RuntimeIdentifiers=\"linux-x64;linux-arm64\"", $"/p:ContainerBaseImage={DockerRegistryManager.FullyQualifiedBaseImageAspNet}", $"/p:ContainerRepository={imageName}", - $"/p:ContainerImageTag={imageTag}", + $"/p:ContainerImageTag={tag}", "/p:EnableSdkContainerSupport=true") .WithWorkingDirectory(newProjectDir.FullName) .Execute() .Should().Pass(); + string imageId = GetImageId(image); + // Check that labels are set CommandResult inspectResult = ContainerCli.InspectCommand( _testOutput, "--format={{json .Config.Labels}}", - imageX64) + imageId) .Execute(); inspectResult.Should().Pass(); var labels = JsonSerializer.Deserialize>(inspectResult.StdOut); From 9b3fd7e61177fd8e59531377b16e1c2ad25aca1e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sun, 2 Feb 2025 19:19:42 +0100 Subject: [PATCH 19/50] cleanup --- .../CreateImageIndexTests.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs index 18f0580c6935..24703040cda6 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs @@ -44,19 +44,6 @@ public async Task CreateImageIndex_Baseline() 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)); From fb049829dd90854b59f6e1a455f80b86d7943fea Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sun, 2 Feb 2025 19:38:08 +0100 Subject: [PATCH 20/50] refactoring: make BuiltImage a class instead of a struct --- src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs | 6 +++--- .../Microsoft.NET.Build.Containers/ImageIndexGenerator.cs | 4 ++-- .../LocalDaemons/DockerCli.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs index 0a76191db727..bd31ccc8cbf5 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs @@ -6,7 +6,7 @@ namespace Microsoft.NET.Build.Containers; /// /// Represents constructed image ready for further processing. /// -internal readonly struct BuiltImage +internal sealed class BuiltImage { /// /// Gets image configuration in JSON format. @@ -46,12 +46,12 @@ internal readonly struct BuiltImage /// /// Gets image OS. /// - internal string OS { get; init; } + internal string? OS { get; init; } /// /// Gets image architecture. /// - internal string Architecture { get; init; } + internal string? Architecture { get; init; } /// /// Gets layers descriptors. diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index b0cedc4a60af..413fa5e75591 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -71,8 +71,8 @@ private static (string, string) GenerateImageIndex(BuiltImage[] images, string m digest = image.ManifestDigest, platform = new PlatformInformation { - architecture = image.Architecture, - os = image.OS + architecture = image.Architecture!, + os = image.OS! } }; manifests[i] = manifest; diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 2e9d41ff9e74..7f79aab2ddf2 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -510,7 +510,7 @@ private static async Task WriteIndexJsonForMultiArchOciImage( mediaType = SchemaTypes.OciManifestV1, size = images[i].Manifest.Length, digest = images[i].ManifestDigest, - platform = new PlatformInformation { architecture = images[i].Architecture, os = images[i].OS } + platform = new PlatformInformation { architecture = images[i].Architecture!, os = images[i].OS! } }; manifests[i] = manifest; } From 73b120d080b6f25074cadd6583a594ce7df6b111 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sun, 2 Feb 2025 20:36:25 +0100 Subject: [PATCH 21/50] rearrange functions order in DockerCli for easier review --- .../LocalDaemons/DockerCli.cs | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 7f79aab2ddf2..820c129672e2 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -398,30 +398,6 @@ private static async Task WriteManifestForDockerImage( } } - public static async Task WriteMultiArchOciImageToStreamAsync( - BuiltImage[] images, - SourceImageReference sourceReference, - DestinationImageReference destinationReference, - Stream imageStream, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - using TarWriter writer = new(imageStream, TarEntryFormat.Pax, leaveOpen: true); - - foreach (var image in images) - { - await WriteOciImageToBlobs(writer, image, sourceReference, cancellationToken) - .ConfigureAwait(false); - } - - await WriteIndexJsonForMultiArchOciImage(writer, images, destinationReference, cancellationToken) - .ConfigureAwait(false); - - await WriteOciLayout(writer, cancellationToken) - .ConfigureAwait(false); - } - private static async Task WriteOciImageToStreamAsync( BuiltImage image, SourceImageReference sourceReference, @@ -443,22 +419,6 @@ await WriteOciLayout(writer, cancellationToken) .ConfigureAwait(false); } - private static async Task WriteOciImageToBlobs( - TarWriter writer, - BuiltImage image, - SourceImageReference sourceReference, - CancellationToken cancellationToken) - { - await WriteImageLayers(writer, image, sourceReference, d => $"{_blobsPath}/{d.Substring("sha256:".Length)}", cancellationToken) - .ConfigureAwait(false); - - await WriteImageConfig(writer, image, $"{_blobsPath}/{image.ImageSha}", cancellationToken) - .ConfigureAwait(false); - - await WriteManifestForOciImage(writer, image, cancellationToken) - .ConfigureAwait(false); - } - private static async Task WriteOciLayout(TarWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -493,6 +453,46 @@ private static async Task WriteManifestForOciImage( } } + private static async Task WriteOciImageToBlobs( + TarWriter writer, + BuiltImage image, + SourceImageReference sourceReference, + CancellationToken cancellationToken) + { + await WriteImageLayers(writer, image, sourceReference, d => $"{_blobsPath}/{d.Substring("sha256:".Length)}", cancellationToken) + .ConfigureAwait(false); + + await WriteImageConfig(writer, image, $"{_blobsPath}/{image.ImageSha}", cancellationToken) + .ConfigureAwait(false); + + await WriteManifestForOciImage(writer, image, cancellationToken) + .ConfigureAwait(false); + } + + public static async Task WriteMultiArchOciImageToStreamAsync( + BuiltImage[] images, + SourceImageReference sourceReference, + DestinationImageReference destinationReference, + Stream imageStream, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + using TarWriter writer = new(imageStream, TarEntryFormat.Pax, leaveOpen: true); + + foreach (var image in images) + { + await WriteOciImageToBlobs(writer, image, sourceReference, cancellationToken) + .ConfigureAwait(false); + } + + await WriteIndexJsonForMultiArchOciImage(writer, images, destinationReference, cancellationToken) + .ConfigureAwait(false); + + await WriteOciLayout(writer, cancellationToken) + .ConfigureAwait(false); + } + private static async Task WriteIndexJsonForMultiArchOciImage( TarWriter writer, BuiltImage[] images, From c8d4e091b88502ff287c5c93ce7c5384ce875e0e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sun, 2 Feb 2025 20:39:43 +0100 Subject: [PATCH 22/50] rearrange functions again --- .../LocalDaemons/DockerCli.cs | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 820c129672e2..208a10c24725 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -453,6 +453,52 @@ private static async Task WriteManifestForOciImage( } } + private static async Task WriteIndexJsonForOciImage( + TarWriter writer, + BuiltImage image, + DestinationImageReference destinationReference, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var manifests = new PlatformSpecificOciManifest[destinationReference.Tags.Length]; + for (int i = 0; i < destinationReference.Tags.Length; i++) + { + var tag = destinationReference.Tags[i]; + manifests[i] = new PlatformSpecificOciManifest + { + mediaType = SchemaTypes.OciManifestV1, + size = image.Manifest.Length, + digest = image.ManifestDigest, + annotations = new Dictionary + { + { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, + { "org.opencontainers.image.ref.name", tag } + } + }; + } + + var index = new ImageIndexV1 + { + schemaVersion = 2, + mediaType = SchemaTypes.OciImageIndexV1, + manifests = manifests + }; + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(JsonSerializer.SerializeToNode(index, options)!.ToJsonString()))) + { + PaxTarEntry indexEntry = new(TarEntryType.RegularFile, "index.json") + { + DataStream = indexStream + }; + await writer.WriteEntryAsync(indexEntry, cancellationToken).ConfigureAwait(false); + } + } + private static async Task WriteOciImageToBlobs( TarWriter writer, BuiltImage image, @@ -577,52 +623,6 @@ private static async Task WriteIndexJsonForMultiArchOciImage( } } - private static async Task WriteIndexJsonForOciImage( - TarWriter writer, - BuiltImage image, - DestinationImageReference destinationReference, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var manifests = new PlatformSpecificOciManifest[destinationReference.Tags.Length]; - for (int i = 0; i < destinationReference.Tags.Length; i++) - { - var tag = destinationReference.Tags[i]; - manifests[i] = new PlatformSpecificOciManifest - { - mediaType = SchemaTypes.OciManifestV1, - size = image.Manifest.Length, - digest = image.ManifestDigest, - annotations = new Dictionary - { - { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, - { "org.opencontainers.image.ref.name", tag } - } - }; - } - - var index = new ImageIndexV1 - { - schemaVersion = 2, - mediaType = SchemaTypes.OciImageIndexV1, - manifests = manifests - }; - - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(JsonSerializer.SerializeToNode(index, options)!.ToJsonString()))) - { - PaxTarEntry indexEntry = new(TarEntryType.RegularFile, "index.json") - { - DataStream = indexStream - }; - await writer.WriteEntryAsync(indexEntry, cancellationToken).ConfigureAwait(false); - } - } - private async ValueTask GetCommandAsync(CancellationToken cancellationToken) { if (_command != null) From f4f1754153b0a0496e9c270999498ff1478800b3 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sun, 2 Feb 2025 21:23:50 +0100 Subject: [PATCH 23/50] add back log message about building image index --- .../Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index eb5743d81846..60d868554100 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -69,19 +69,20 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) LocalRegistry); var images = ParseImages(destinationImageReference.Kind); - if (Log.HasLoggedErrors) { return false; } - GeneratedArchiveOutputPath = ArchiveOutputPath; + logger.LogInformation(Strings.BuildingImageIndex, destinationImageReference, string.Join(", ", images.Select(i => i.ManifestDigest))); var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); await ImagePublisher.PublishImageAsync(images, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) .ConfigureAwait(false); + GeneratedArchiveOutputPath = ArchiveOutputPath; + return !Log.HasLoggedErrors; } From fc80348f5979836e47fbba1689dd3665faba61f2 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 3 Feb 2025 00:01:41 +0100 Subject: [PATCH 24/50] extract error messages into Strings --- .../ImagePublisher.cs | 8 ++--- .../Resources/Strings.Designer.cs | 36 +++++++++++++++++++ .../Resources/Strings.resx | 20 +++++++++-- .../Resources/xlf/Strings.cs.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.de.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.es.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.fr.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.it.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.ja.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.ko.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.pl.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.pt-BR.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.ru.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.tr.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.zh-Hans.xlf | 28 ++++++++++++--- .../Resources/xlf/Strings.zh-Hant.xlf | 28 ++++++++++++--- .../Tasks/CreateImageIndex.cs | 29 +++++++++------ 17 files changed, 389 insertions(+), 68 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index 163c5e5efb26..cd6a6a15ebd8 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -73,7 +73,7 @@ await PushToLocalRegistryAsync( telemetry, cancellationToken, destinationImageReference.LocalRegistry!.LoadAsync, - logWarningForMultiArch : true).ConfigureAwait(false); + logTipAboutContainerd : true).ConfigureAwait(false); break; case DestinationImageReferenceKind.RemoteRegistry: await PushToRemoteRegistryAsync( @@ -111,7 +111,7 @@ private static async Task PushToLocalRegistryAsync( Telemetry telemetry, CancellationToken cancellationToken, Func loadFunc, - bool logWarningForMultiArch = false) + bool logTipAboutContainerd = false) { ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) @@ -148,9 +148,9 @@ private static async Task PushToLocalRegistryAsync( { telemetry.LogLocalLoadError(); Log.LogErrorFromException(dle, showStackTrace: false); - if (logWarningForMultiArch && dle.Message.Contains("no such file or directory")) + if (logTipAboutContainerd && dle.Message.Contains("no such file or directory")) { - Log.LogMessage(MessageImportance.High, "Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings."); + Log.LogMessage(MessageImportance.High, Strings.TipToEnableContainerdForMultiArch); } } } 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 7ad9616ae2c1..07beb07321fd 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs @@ -420,6 +420,33 @@ internal static string InvalidImageManifest { } } + /// + /// Looks up a localized string similar to Cannot create image index because at least one of the provided images config is invalid.. + /// + internal static string InvalidImageConfig { + get { + return ResourceManager.GetString("InvalidImageConfig", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot create image index because at least one of the provided images' config is missing 'architecture'.. + /// + internal static string ImageConfigMissingArchitecture { + get { + return ResourceManager.GetString("ImageConfigMissingArchitecture", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot create image index because at least one of the provided images' config is missing 'os'.. + /// + internal static string ImageConfigMissingOs { + get { + return ResourceManager.GetString("ImageConfigMissingOs", resourceCulture); + } + } + /// /// Looks up a localized string similar to Image index creation for Podman is not supported.. /// @@ -429,6 +456,15 @@ internal static string ImageIndex_PodmanNotSupported { } } + /// + /// Looks up a localized string similar to Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings.. + /// + internal static string TipToEnableContainerdForMultiArch { + get { + return ResourceManager.GetString("TipToEnableContainerdForMultiArch", 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.. /// diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx index 15f377704a50..753d0ba091f4 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx @@ -366,7 +366,7 @@ - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. @@ -382,13 +382,29 @@ - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + + + + Cannot create image index because at least one of the provided images' config is invalid. + + + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. Image index creation for Podman is not supported. + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + 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 bc89aedef99e..930f2cd9500f 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 @@ -164,6 +164,16 @@ Nebyl zjištěn žádný objekt hostitele. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: '{1}' není platná proměnná prostředí. Ignorování. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: Požadovaná vlastnost '{0}' nebyla nastavena nebo je prázdná. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: Příliš mnoho opakovaných pokusů, zastavuje se. 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 23f4eeafe797..f851c41fbf92 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 @@ -164,6 +164,16 @@ Es wurde kein Hostobjekt erkannt. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: „{1}“ war keine gültige Umgebungsvariable. Sie wird ignoriert. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: Die erforderliche Eigenschaft „{0}“ wurde nicht festgelegt oder ist leer. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: Zu viele Wiederholungsversuche, Vorgang wird beendet. 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 8533ff49f64d..9f36072ccbe2 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 @@ -164,6 +164,16 @@ No se detectó ningún objeto host. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: "{1}" no era una variable de entorno válida. Ignorando. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: La propiedad necesaria "{0}" no se estableció o estaba vacía. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: Demasiados reintentos, deteniendo. 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 127a97b70635..50def6bb6b9d 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 @@ -164,6 +164,16 @@ Aucun objet hôte détecté. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0} : '{1}' n’était pas une variable d’environnement valide. Ignorant. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: la propriété requise '{0}' n’a pas été définie ou vide. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: trop de tentatives, arrêt. 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 2c6f97c87e93..c32f91206de4 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 @@ -164,6 +164,16 @@ Nessun oggetto host rilevato. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: '{1}' non è una variabile di ambiente valida. Il valore verrà ignorato. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: la proprietà obbligatoria '{0}' non è stata impostata o è vuota. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: troppi tentativi, arresto. 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 feb83548f3c1..74b016f07d27 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 @@ -164,6 +164,16 @@ ホスト オブジェクトが検出されませんでした。 + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: '{1}' は有効な環境変数ではありませんでした。無視しています。 {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: 必要なプロパティ '{0}' が設定されていなかったか、空でした。 {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: 再試行回数が多すぎます。停止しています。 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 c696a7cb649c..0ab031e4eb14 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 @@ -164,6 +164,16 @@ 호스트 개체가 검색되지 않았습니다. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: '{1}'은(는) 유효한 환경 변수가 아닙니다. 무시 중. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: 필수 속성 '{0}'이(가) 설정되지 않았거나 비어 있습니다. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: 다시 시도가 너무 많아 중지 중입니다. 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 165f11851003..6f7cadee6322 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 @@ -164,6 +164,16 @@ Nie wykryto obiektu hosta. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: „{1}” nie jest prawidłową zmienną środowiskową. Ignorowanie. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: wymagana właściwość „{0}” nie została ustawiona lub jest pusta. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: zbyt wiele ponownych prób, zatrzymywanie. 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 82b3f1b1a646..bd00f8b3f7f3 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 @@ -164,6 +164,16 @@ Nenhum objeto de host detectado. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: '{1}' não era uma variável de ambiente válida. Ignorando. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: A propriedade obrigatória '{0}' não foi definida ou está vazia. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: Muitas tentativas, parando. 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 8a4e2d1ba8f0..932f51f124fa 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 @@ -164,6 +164,16 @@ Объект узла не обнаружен. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: "{1}" не является допустимой переменной среды. Пропуск. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: обязательное свойство "{0}" не установлено или пусто. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: слишком много повторных попыток, остановка. 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 4b2b0147c029..dab91d854f1d 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 @@ -164,6 +164,16 @@ Ana bilgisayar nesnesi algılanmadı. + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: '{1}' geçerli bir Ortam Değişkeni değildi. Görmezden geliniyor. {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: Gerekli '{0}' özelliği ayarlanmadı veya boş değil. {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: Çok fazla yeniden deneme, durduruluyor. 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 a02595dc1a7b..ebf98c8f1540 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 @@ -164,6 +164,16 @@ 未检测到主机对象。 + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: "{1}" 不是有效的环境变量。忽略。 {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: 未设置必需属性 "{0}" 或为空。 {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: 重试次数过多,正在停止。 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 e1e859d58559..01de44e08b8b 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 @@ -164,6 +164,16 @@ 未偵測到主機物件。 + + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + Cannot create image index because at least one of the provided images' config is missing 'architecture'. + + + + Cannot create image index because at least one of the provided images' config is missing 'os'. + Cannot create image index because at least one of the provided images' config is missing 'os'. + + Pushed image index '{0}' to registry '{1}'. Pushed image index '{0}' to registry '{1}'. @@ -194,9 +204,14 @@ CONTAINER2015: {0}: '{1}' 不是有效的環境變數。正在忽略。 {StrBegin="CONTAINER2015: "} + + Cannot create image index because at least one of the provided images' config is invalid. + Cannot create image index because at least one of the provided images' config is invalid. + + - Cannot create image index because at least one of the provided images manifest is invalid. - Cannot create image index because at least one of the provided images manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. + Cannot create image index because at least one of the provided images' manifest is invalid. @@ -290,8 +305,8 @@ {StrBegin="CONTAINER2016: "} - 'mediaType' of manifests should be the same in manifest list (image index). - 'mediaType' of manifests should be the same in manifest list (image index). + 'mediaType' of manifests should be the same in image index. + 'mediaType' of manifests should be the same in image index. @@ -394,6 +409,11 @@ CONTAINER4001: 必要的屬性 '{0}' 未設定或是空的。 {StrBegin="CONTAINER4001: "} + + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + + CONTAINER1006: Too many retries, stopping. CONTAINER1006: 重試太多次,正在停止。 diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 60d868554100..d56ae92d9b62 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -135,17 +135,26 @@ private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind) return images; } - private static (string, string) GetArchitectureAndOsFromConfig(string config) + private (string, string) GetArchitectureAndOsFromConfig(string config) { - var configJson = JsonNode.Parse(config) as JsonObject ?? - throw new ArgumentException("Image config should be a JSON object."); - - var architecture = configJson["architecture"]?.ToString() ?? - throw new ArgumentException("Image config should contain 'architecture'."); - - var os = configJson["os"]?.ToString() ?? - throw new ArgumentException("Image config should contain 'os'."); - + var configJson = JsonNode.Parse(config) as JsonObject; + if (configJson is null) + { + Log.LogError(Strings.InvalidImageConfig); + return (string.Empty, string.Empty); + } + var architecture = configJson["architecture"]?.ToString(); + if (architecture is null) + { + Log.LogError(Strings.ImageConfigMissingArchitecture); + return (string.Empty, string.Empty); + } + var os = configJson["os"]?.ToString(); + if (os is null) + { + Log.LogError(Strings.ImageConfigMissingOs); + return (string.Empty, string.Empty); + } return (architecture, os); } } From f2909c14d9dcb11dd3e1f6613b7fad3eb64b2513 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 3 Feb 2025 09:59:10 +0100 Subject: [PATCH 25/50] fix typo in log message --- .../Resources/Strings.Designer.cs | 2 +- .../Microsoft.NET.Build.Containers/Resources/Strings.resx | 2 +- .../Resources/xlf/Strings.cs.xlf | 4 ++-- .../Resources/xlf/Strings.de.xlf | 4 ++-- .../Resources/xlf/Strings.es.xlf | 4 ++-- .../Resources/xlf/Strings.fr.xlf | 4 ++-- .../Resources/xlf/Strings.it.xlf | 4 ++-- .../Resources/xlf/Strings.ja.xlf | 4 ++-- .../Resources/xlf/Strings.ko.xlf | 4 ++-- .../Resources/xlf/Strings.pl.xlf | 4 ++-- .../Resources/xlf/Strings.pt-BR.xlf | 4 ++-- .../Resources/xlf/Strings.ru.xlf | 4 ++-- .../Resources/xlf/Strings.tr.xlf | 4 ++-- .../Resources/xlf/Strings.zh-Hans.xlf | 4 ++-- .../Resources/xlf/Strings.zh-Hant.xlf | 4 ++-- .../Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs | 2 ++ 16 files changed, 30 insertions(+), 28 deletions(-) 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 07beb07321fd..4f2dee24ba99 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs @@ -457,7 +457,7 @@ internal static string ImageIndex_PodmanNotSupported { } /// - /// Looks up a localized string similar to Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings.. + /// Looks up a localized string similar to Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings.. /// internal static string TipToEnableContainerdForMultiArch { get { diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx index 753d0ba091f4..42d9dddffea2 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx @@ -402,7 +402,7 @@ - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 930f2cd9500f..20499b00e426 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 f851c41fbf92..313ffe15f9a1 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 9f36072ccbe2..a1c4e1a7107f 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 50def6bb6b9d..705c8e48ae85 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 c32f91206de4..22089bdb50c6 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 74b016f07d27..3efb830d3e14 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 0ab031e4eb14..75380d38355f 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 6f7cadee6322..f34e4c1e4b31 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 bd00f8b3f7f3..6b5da780f9fb 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 932f51f124fa..e9d2290783c0 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 dab91d854f1d..fac7e81d6444 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 ebf98c8f1540..411f5221a963 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. 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 01de44e08b8b..580bb931de64 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 @@ -410,8 +410,8 @@ {StrBegin="CONTAINER4001: "} - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensude that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. + Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index d56ae92d9b62..217f2909fffe 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -52,6 +52,8 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) return false; } + + using MSBuildLoggerProvider loggerProvider = new(Log); ILoggerFactory msbuildLoggerFactory = new LoggerFactory(new[] { loggerProvider }); ILogger logger = msbuildLoggerFactory.CreateLogger(); From a61aad5637c4dbbedde843777ee827260ed1d4e7 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 3 Feb 2025 11:45:37 +0100 Subject: [PATCH 26/50] extract image index creation from DockerCli into ImageIndexGenerator --- .../ImageIndexGenerator.cs | 66 ++++++++++--- .../LocalDaemons/DockerCli.cs | 95 ++++--------------- .../ImageIndexGeneratorTests.cs | 4 +- 3 files changed, 70 insertions(+), 95 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index 413fa5e75591..e4d167a007df 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; -using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using Microsoft.NET.Build.Containers.Resources; -using Microsoft.NET.Build.Containers.Tasks; namespace Microsoft.NET.Build.Containers; @@ -43,11 +42,11 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images) if (manifestMediaType == SchemaTypes.DockerManifestV2) { - return GenerateImageIndex(images, SchemaTypes.DockerManifestV2, SchemaTypes.DockerManifestListV2); + return (GenerateImageIndex(images, SchemaTypes.DockerManifestV2, SchemaTypes.DockerManifestListV2), SchemaTypes.DockerManifestListV2); } else if (manifestMediaType == SchemaTypes.OciManifestV1) { - return GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1); + return (GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1), SchemaTypes.OciImageIndexV1); } else { @@ -55,36 +54,73 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images) } } - private static (string, string) GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType) + internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType) { // 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++) { - var image = images[i]; - - var manifest = new PlatformSpecificManifest + manifests[i] = new PlatformSpecificManifest { mediaType = manifestMediaType, - size = image.Manifest.Length, - digest = image.ManifestDigest, + size = images[i].Manifest.Length, + digest = images[i].ManifestDigest, platform = new PlatformInformation { - architecture = image.Architecture!, - os = image.OS! + architecture = images[i].Architecture!, + os = images[i].OS! } }; - manifests[i] = manifest; } - var manifestList = new ManifestListV2 + var imageIndex = new ManifestListV2 { schemaVersion = 2, mediaType = imageIndexMediaType, manifests = manifests }; - return (JsonSerializer.SerializeToNode(manifestList)?.ToJsonString() ?? "", manifestList.mediaType); + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + return JsonSerializer.SerializeToNode(imageIndex, options)?.ToJsonString() ?? ""; + } + + internal static string GenerateImageIndexWithAnnotations(string manifestMediaType, string manifestDigest, long manifestSize, string repository, string[] tags) + { + 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 + { + { "io.containerd.image.name", $"{repository}:{tag}" }, + { "org.opencontainers.image.ref.name", tag } + } + }; + } + + var index = new ImageIndexV1 + { + schemaVersion = 2, + mediaType = SchemaTypes.OciImageIndexV1, + manifests = manifests + }; + + var options = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + return JsonSerializer.SerializeToNode(index, options)?.ToJsonString() ?? ""; } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 208a10c24725..01bb7bb3bda4 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -7,7 +7,6 @@ #endif using System.Text.Json; using System.Text.Json.Nodes; -using System.Text.Json.Serialization; using Microsoft.DotNet.Cli.Utils; using Microsoft.Extensions.Logging; using Microsoft.NET.Build.Containers.Resources; @@ -461,35 +460,14 @@ private static async Task WriteIndexJsonForOciImage( { cancellationToken.ThrowIfCancellationRequested(); - var manifests = new PlatformSpecificOciManifest[destinationReference.Tags.Length]; - for (int i = 0; i < destinationReference.Tags.Length; i++) - { - var tag = destinationReference.Tags[i]; - manifests[i] = new PlatformSpecificOciManifest - { - mediaType = SchemaTypes.OciManifestV1, - size = image.Manifest.Length, - digest = image.ManifestDigest, - annotations = new Dictionary - { - { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, - { "org.opencontainers.image.ref.name", tag } - } - }; - } + string indexJson = ImageIndexGenerator.GenerateImageIndexWithAnnotations( + SchemaTypes.OciManifestV1, + image.ManifestDigest, + image.Manifest.Length, + destinationReference.Repository, + destinationReference.Tags); - var index = new ImageIndexV1 - { - schemaVersion = 2, - mediaType = SchemaTypes.OciImageIndexV1, - manifests = manifests - }; - - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(JsonSerializer.SerializeToNode(index, options)!.ToJsonString()))) + using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(indexJson))) { PaxTarEntry indexEntry = new(TarEntryType.RegularFile, "index.json") { @@ -548,31 +526,9 @@ private static async Task WriteIndexJsonForMultiArchOciImage( // 1. create manifest list for the blobs cancellationToken.ThrowIfCancellationRequested(); - var manifests = new PlatformSpecificOciManifest[images.Length]; - for (int i = 0; i < images.Length; i++) - { - var manifest = new PlatformSpecificOciManifest - { - mediaType = SchemaTypes.OciManifestV1, - size = images[i].Manifest.Length, - digest = images[i].ManifestDigest, - platform = new PlatformInformation { architecture = images[i].Architecture!, os = images[i].OS! } - }; - manifests[i] = manifest; - } + // For multi-arch we publish only oci-formatted image tarballs. + string manifestListJson = ImageIndexGenerator.GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1); - var manifestList = new ImageIndexV1 - { - schemaVersion = 2, - mediaType = SchemaTypes.OciImageIndexV1, - manifests = manifests - }; - - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - var manifestListJson = JsonSerializer.SerializeToNode(manifestList, options)!.ToJsonString(); var manifestListDigest = DigestUtils.GetDigest(manifestListJson); var manifestListSha = DigestUtils.GetShaFromDigest(manifestListDigest); var manifestListPath = $"{_blobsPath}/{manifestListSha}"; @@ -586,34 +542,17 @@ private static async Task WriteIndexJsonForMultiArchOciImage( await writer.WriteEntryAsync(indexEntry, cancellationToken).ConfigureAwait(false); } - // 2. create index json that points to manifest list in the blobs + // 2. create index.json that points to manifest list in the blobs cancellationToken.ThrowIfCancellationRequested(); - var manifestsIndexJson = new PlatformSpecificOciManifest[destinationReference.Tags.Length]; - for (int i = 0; i < destinationReference.Tags.Length; i++) - { - var tag = destinationReference.Tags[i]; - manifestsIndexJson[i] = new PlatformSpecificOciManifest - { - mediaType = SchemaTypes.OciImageIndexV1, - size = manifestListJson.Length, - digest = manifestListDigest, - annotations = new Dictionary - { - { "io.containerd.image.name", $"{destinationReference.Repository}:{tag}" }, - { "org.opencontainers.image.ref.name", tag } - } - }; - } - - var index = new ImageIndexV1 - { - schemaVersion = 2, - mediaType = SchemaTypes.OciImageIndexV1, - manifests = manifestsIndexJson - }; + string indexJson = ImageIndexGenerator.GenerateImageIndexWithAnnotations( + SchemaTypes.OciImageIndexV1, + manifestListDigest, + manifestListJson.Length, + destinationReference.Repository, + destinationReference.Tags); - using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(JsonSerializer.SerializeToNode(index, options)!.ToJsonString()))) + using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(indexJson))) { PaxTarEntry indexEntry = new(TarEntryType.RegularFile, "index.json") { diff --git a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs index 9bc573b5b0e7..3d6e54969aeb 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs @@ -96,7 +96,7 @@ public void GenerateDockerManifestList() ]; var (imageIndex, mediaType) = ImageIndexGenerator.GenerateImageIndex(images); - Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.docker.distribution.manifest.list.v2\\u002Bjson\",\"manifests\":[{\"mediaType\":\"application/vnd.docker.distribution.manifest.v2\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\",\"variant\":null,\"features\":null,\"os.version\":null}},{\"mediaType\":\"application/vnd.docker.distribution.manifest.v2\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\",\"variant\":null,\"features\":null,\"os.version\":null}}]}", imageIndex); + Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.docker.distribution.manifest.list.v2\\u002Bjson\",\"manifests\":[{\"mediaType\":\"application/vnd.docker.distribution.manifest.v2\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\"}},{\"mediaType\":\"application/vnd.docker.distribution.manifest.v2\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\"}}]}", imageIndex); Assert.Equal(SchemaTypes.DockerManifestListV2, mediaType); } @@ -130,7 +130,7 @@ public void GenerateOciImageIndex() ]; var (imageIndex, mediaType) = ImageIndexGenerator.GenerateImageIndex(images); - Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1\\u002Bjson\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\",\"variant\":null,\"features\":null,\"os.version\":null}},{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\",\"variant\":null,\"features\":null,\"os.version\":null}}]}", imageIndex); + Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1\\u002Bjson\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\"}},{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\"}}]}", imageIndex); Assert.Equal(SchemaTypes.OciImageIndexV1, mediaType); } } From 51d9cc80aa044ab9f579c367eaff17daad4a550c Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 3 Feb 2025 12:03:30 +0100 Subject: [PATCH 27/50] cleanup --- .../ImageIndexGenerator.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index e4d167a007df..27f7fbe9cd5f 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -7,16 +7,6 @@ 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 { /// From a0c3a5673449c9a812a689dd1bb9af6594f457b3 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 3 Feb 2025 12:26:35 +0100 Subject: [PATCH 28/50] add more ImageIndexGenertorTests --- .../ImageIndexGenerator.cs | 5 +++++ .../ImageIndexGeneratorTests.cs | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index 27f7fbe9cd5f..cc9b32ec4e9d 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -46,6 +46,11 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images) internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType) { + if (images.Length == 0) + { + throw new ArgumentException(string.Format(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]; diff --git a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs index 3d6e54969aeb..f89d2601b9b8 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs @@ -15,6 +15,14 @@ public void ImagesCannotBeEmpty() Assert.Equal(Strings.ImagesEmpty, ex.Message); } + [Fact] + public void ImagesCannotBeEmpty_SpecifiedMediaType() + { + BuiltImage[] images = Array.Empty(); + var ex = Assert.Throws(() => ImageIndexGenerator.GenerateImageIndex(images, "manifestMediaType", "imageIndexMediaType")); + Assert.Equal(Strings.ImagesEmpty, ex.Message); + } + [Fact] public void UnsupportedMediaTypeThrows() { @@ -133,4 +141,11 @@ public void GenerateOciImageIndex() Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1\\u002Bjson\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\"}},{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\"}}]}", imageIndex); Assert.Equal(SchemaTypes.OciImageIndexV1, mediaType); } + + [Fact] + public void GenerateImageIndexWithAnnotations() + { + string imageIndex = ImageIndexGenerator.GenerateImageIndexWithAnnotations("mediaType", "sha256:digest", 3, "repository", ["1.0", "2.0"]); + Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1\\u002Bjson\",\"manifests\":[{\"mediaType\":\"mediaType\",\"size\":3,\"digest\":\"sha256:digest\",\"platform\":{},\"annotations\":{\"io.containerd.image.name\":\"repository:1.0\",\"org.opencontainers.image.ref.name\":\"1.0\"}},{\"mediaType\":\"mediaType\",\"size\":3,\"digest\":\"sha256:digest\",\"platform\":{},\"annotations\":{\"io.containerd.image.name\":\"repository:2.0\",\"org.opencontainers.image.ref.name\":\"2.0\"}}]}", imageIndex); + } } From 3078ea8728d9035756c101cfbc73d0381e4a3098 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 3 Feb 2025 14:38:05 +0100 Subject: [PATCH 29/50] check if containerd store is enabled before loading --- .../ImagePublisher.cs | 10 +---- .../LocalDaemons/DockerCli.cs | 41 ++++++++++++++++++- .../Resources/Strings.Designer.cs | 6 +-- .../Resources/Strings.resx | 8 ++-- .../Resources/xlf/Strings.cs.xlf | 10 ++--- .../Resources/xlf/Strings.de.xlf | 10 ++--- .../Resources/xlf/Strings.es.xlf | 10 ++--- .../Resources/xlf/Strings.fr.xlf | 10 ++--- .../Resources/xlf/Strings.it.xlf | 10 ++--- .../Resources/xlf/Strings.ja.xlf | 10 ++--- .../Resources/xlf/Strings.ko.xlf | 10 ++--- .../Resources/xlf/Strings.pl.xlf | 10 ++--- .../Resources/xlf/Strings.pt-BR.xlf | 10 ++--- .../Resources/xlf/Strings.ru.xlf | 10 ++--- .../Resources/xlf/Strings.tr.xlf | 10 ++--- .../Resources/xlf/Strings.zh-Hans.xlf | 10 ++--- .../Resources/xlf/Strings.zh-Hant.xlf | 10 ++--- 17 files changed, 113 insertions(+), 82 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index cd6a6a15ebd8..bc3e330441af 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -72,8 +72,7 @@ await PushToLocalRegistryAsync( BuildEngine, telemetry, cancellationToken, - destinationImageReference.LocalRegistry!.LoadAsync, - logTipAboutContainerd : true).ConfigureAwait(false); + destinationImageReference.LocalRegistry!.LoadAsync).ConfigureAwait(false); break; case DestinationImageReferenceKind.RemoteRegistry: await PushToRemoteRegistryAsync( @@ -110,8 +109,7 @@ private static async Task PushToLocalRegistryAsync( IBuildEngine? BuildEngine, Telemetry telemetry, CancellationToken cancellationToken, - Func loadFunc, - bool logTipAboutContainerd = false) + Func loadFunc) { ILocalRegistry localRegistry = destinationImageReference.LocalRegistry!; if (!(await localRegistry.IsAvailableAsync(cancellationToken).ConfigureAwait(false))) @@ -148,10 +146,6 @@ private static async Task PushToLocalRegistryAsync( { telemetry.LogLocalLoadError(); Log.LogErrorFromException(dle, showStackTrace: false); - if (logTipAboutContainerd && dle.Message.Contains("no such file or directory")) - { - Log.LogMessage(MessageImportance.High, Strings.TipToEnableContainerdForMultiArch); - } } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 01bb7bb3bda4..6b7471cafb76 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -87,10 +87,16 @@ private async Task LoadAsync( SourceImageReference sourceReference, DestinationImageReference destinationReference, Func writeStreamFunc, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + bool checkContainerdStore = false) { cancellationToken.ThrowIfCancellationRequested(); + if (checkContainerdStore && !IsContainerdStoreEnabledForDocker()) + { + throw new DockerLoadException(Strings.ImageLoadFailed_ContainerdStoreDisabled); + } + string commandPath = await FindFullCommandPath(cancellationToken); // call `docker load` and get it ready to receive input @@ -125,7 +131,7 @@ public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReferen => await LoadAsync(image, sourceReference, destinationReference, WriteDockerImageToStreamAsync, cancellationToken); public async Task LoadAsync(BuiltImage[] images, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) - => await LoadAsync(images, sourceReference, destinationReference, WriteMultiArchOciImageToStreamAsync, cancellationToken); + => await LoadAsync(images, sourceReference, destinationReference, WriteMultiArchOciImageToStreamAsync, cancellationToken, checkContainerdStore: true); public async Task IsAvailableAsync(CancellationToken cancellationToken) { @@ -619,6 +625,37 @@ private static bool IsPodmanAlias() } } + private static bool IsContainerdStoreEnabledForDocker() + { + try + { + // We don't need to check if this is docker, because there is no "DriverStatus" for podman + if (!GetDockerConfig().RootElement.TryGetProperty("DriverStatus", out var driverStatus) || driverStatus.ValueKind != JsonValueKind.Array) + { + return false; + } + + foreach (var item in driverStatus.EnumerateArray()) + { + if (item.ValueKind != JsonValueKind.Array || item.GetArrayLength() != 2) continue; + + var array = item.EnumerateArray().ToArray(); + // The usual output is [driver-type io.containerd.snapshotter.v1] + if (array[0].GetString() == "driver-type" && array[1].GetString()!.StartsWith("io.containerd.snapshotter")) + { + return true; + } + } + + return false; + } + catch + { + return false; + } + } + + #if NET private async Task TryRunVersionCommandAsync(string command, CancellationToken cancellationToken) { 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 4f2dee24ba99..59a9f28b0b83 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs @@ -457,11 +457,11 @@ internal static string ImageIndex_PodmanNotSupported { } /// - /// Looks up a localized string similar to Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings.. + /// Looks up a localized string similar to CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings.. /// - internal static string TipToEnableContainerdForMultiArch { + internal static string ImageLoadFailed_ContainerdStoreDisabled { get { - return ResourceManager.GetString("TipToEnableContainerdForMultiArch", resourceCulture); + return ResourceManager.GetString("ImageLoadFailed_ContainerdStoreDisabled", resourceCulture); } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx index 42d9dddffea2..bac283c4ac0c 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx +++ b/src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx @@ -189,6 +189,10 @@ CONTAINER1009: Failed to load image from local registry. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER2015: {0}: '{1}' was not a valid Environment Variable. Ignoring. {StrBegin="CONTAINER2015: "} @@ -401,10 +405,6 @@ Image index creation for Podman is not supported. - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - 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 20499b00e426..f916d5f3d916 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 @@ -189,6 +189,11 @@ CONTAINER1009: Nepodařilo se načíst bitovou kopii z místního registru. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: Načítání imagí z místního registru se nepodporuje. @@ -409,11 +414,6 @@ CONTAINER4001: Požadovaná vlastnost '{0}' nebyla nastavena nebo je prázdná. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: Příliš mnoho opakovaných pokusů, zastavuje se. 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 313ffe15f9a1..d0ac67812ab7 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 @@ -189,6 +189,11 @@ CONTAINER1009: Fehler beim Laden des Images aus der lokalen Registrierung. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: Das Pullen von Images aus der lokalen Registrierung wird nicht unterstützt. @@ -409,11 +414,6 @@ CONTAINER4001: Die erforderliche Eigenschaft „{0}“ wurde nicht festgelegt oder ist leer. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: Zu viele Wiederholungsversuche, Vorgang wird beendet. 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 a1c4e1a7107f..b1febfedfbc6 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 @@ -189,6 +189,11 @@ CONTAINER1009: no se pudo cargar la imagen desde el registro local. Stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: No se admite la extracción de imágenes del registro local. @@ -409,11 +414,6 @@ CONTAINER4001: La propiedad necesaria "{0}" no se estableció o estaba vacía. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: Demasiados reintentos, deteniendo. 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 705c8e48ae85..2566778c2a07 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 @@ -189,6 +189,11 @@ CONTAINER1009: Échec du chargement de l'image à partir du registre local. sortie standard : {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: L'extraction d'images à partir du registre local n'est pas prise en charge. @@ -409,11 +414,6 @@ CONTAINER4001: la propriété requise '{0}' n’a pas été définie ou vide. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: trop de tentatives, arrêt. 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 22089bdb50c6..e6b319944c11 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 @@ -189,6 +189,11 @@ CONTAINER1009: non è stato possibile caricare l'immagine dal registro locale. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: il pull di immagini dal registro locale non è supportato. @@ -409,11 +414,6 @@ CONTAINER4001: la proprietà obbligatoria '{0}' non è stata impostata o è vuota. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: troppi tentativi, arresto. 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 3efb830d3e14..16e01793da1d 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 @@ -189,6 +189,11 @@ CONTAINER1009: ローカル レジストリからイメージを読み込めませんでした。stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: ローカル レジストリからのイメージのプルはサポートされていません。 @@ -409,11 +414,6 @@ CONTAINER4001: 必要なプロパティ '{0}' が設定されていなかったか、空でした。 {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: 再試行回数が多すぎます。停止しています。 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 75380d38355f..370181275c81 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 @@ -189,6 +189,11 @@ CONTAINER1009: 로컬 레지스트리에서 이미지를 로드하지 못했습니다. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: 로컬 레지스트리에서 이미지 끌어오기가 지원되지 않습니다. @@ -409,11 +414,6 @@ CONTAINER4001: 필수 속성 '{0}'이(가) 설정되지 않았거나 비어 있습니다. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: 다시 시도가 너무 많아 중지 중입니다. 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 f34e4c1e4b31..d92686d7d371 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 @@ -189,6 +189,11 @@ CONTAINER1009: Nie można załadować obrazu z rejestru lokalnego. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: Ściąganie obrazów z rejestru lokalnego nie jest obsługiwane. @@ -409,11 +414,6 @@ CONTAINER4001: wymagana właściwość „{0}” nie została ustawiona lub jest pusta. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: zbyt wiele ponownych prób, zatrzymywanie. 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 6b5da780f9fb..651ea8dd65b6 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 @@ -189,6 +189,11 @@ CONTAINER1009: falha ao carregar a imagem do registro local. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: A extração de imagens do registro local não é suportada. @@ -409,11 +414,6 @@ CONTAINER4001: A propriedade obrigatória '{0}' não foi definida ou está vazia. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: Muitas tentativas, parando. 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 e9d2290783c0..a52774d219e5 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 @@ -189,6 +189,11 @@ CONTAINER1009: не удалось загрузить образ из локального реестра. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: извлечение образов из локального реестра не поддерживается. @@ -409,11 +414,6 @@ CONTAINER4001: обязательное свойство "{0}" не установлено или пусто. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: слишком много повторных попыток, остановка. 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 fac7e81d6444..f513dfc84940 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 @@ -189,6 +189,11 @@ CONTAINER1009: Görüntü yerel kayıt defterinden yüklenemedi. stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: Yerel kayıt defterinden görüntü çekme desteklenmiyor. @@ -409,11 +414,6 @@ CONTAINER4001: Gerekli '{0}' özelliği ayarlanmadı veya boş değil. {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: Çok fazla yeniden deneme, durduruluyor. 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 411f5221a963..82b415641aa9 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 @@ -189,6 +189,11 @@ CONTAINER1009: 未能从本地注册表加载映像。stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: 不支持从本地注册表拉取映像。 @@ -409,11 +414,6 @@ CONTAINER4001: 未设置必需属性 "{0}" 或为空。 {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: 重试次数过多,正在停止。 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 580bb931de64..ef91ab2848e8 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 @@ -189,6 +189,11 @@ CONTAINER1009: 無法從本機登錄載入映像。stdout: {0} {StrBegin="CONTAINER1009: "} + + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + CONTAINER1020: Failed to load image because containerd image store is not enabled for Docker. Tip: You can enable it by checking 'Use containerd for pulling and storing images' in Docker Desktop settings. + {StrBegin="CONTAINER1020: "} + CONTAINER1010: Pulling images from local registry is not supported. CONTAINER1010: 不支援從本機登錄提取映像。 @@ -409,11 +414,6 @@ CONTAINER4001: 必要的屬性 '{0}' 未設定或是空的。 {StrBegin="CONTAINER4001: "} - - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - Tip: For multi-arch image publishing, ensure that 'Use containerd for pulling and storing images' is checked in Docker Desktop settings. - - CONTAINER1006: Too many retries, stopping. CONTAINER1006: 重試太多次,正在停止。 From af3a5482eb2e11b585e8da28b394bc05861bacef Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 14:36:06 +0100 Subject: [PATCH 30/50] cleanup targets --- .../build/Microsoft.NET.Build.Containers.targets | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets index 3854f88ef29f..51b28a1db5f6 100644 --- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets +++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets @@ -297,12 +297,6 @@ - - @@ -400,10 +394,6 @@ Include="$([System.String]::Copy('%(_ParsedContainerEnvironmentVariables.Identity)').Split(':')[0])" Value="$([System.String]::Copy('%(_ParsedContainerEnvironmentVariables.Identity)').Split(':')[1])" /> - - - $([System.IO.Path]::Combine($(ContainerArchiveOutputPath), $(ContainerRepository)-$(ContainerRuntimeIdentifier).tar.gz)) - Date: Wed, 5 Feb 2025 14:39:06 +0100 Subject: [PATCH 31/50] add docker.io/library prefix to image name annotation; fix json serialization symbol escaping --- .../ImageIndexGenerator.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index cc9b32ec4e9d..8abbe315d0c8 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -1,6 +1,7 @@ // 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; @@ -46,11 +47,6 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images) internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType) { - if (images.Length == 0) - { - throw new ArgumentException(string.Format(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]; @@ -77,12 +73,7 @@ internal static string GenerateImageIndex(BuiltImage[] images, string manifestMe manifests = manifests }; - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - return JsonSerializer.SerializeToNode(imageIndex, options)?.ToJsonString() ?? ""; + return GetJsonStringFromImageIndex(imageIndex); } internal static string GenerateImageIndexWithAnnotations(string manifestMediaType, string manifestDigest, long manifestSize, string repository, string[] tags) @@ -98,7 +89,7 @@ internal static string GenerateImageIndexWithAnnotations(string manifestMediaTyp digest = manifestDigest, annotations = new Dictionary { - { "io.containerd.image.name", $"{repository}:{tag}" }, + { "io.containerd.image.name", $"docker.io/library/{repository}:{tag}" }, { "org.opencontainers.image.ref.name", tag } } }; @@ -111,11 +102,20 @@ internal static string GenerateImageIndexWithAnnotations(string manifestMediaTyp manifests = manifests }; - var options = new JsonSerializerOptions + return GetJsonStringFromImageIndex(index); + } + + private static string GetJsonStringFromImageIndex(T imageIndex) + { + var nullIgnoreOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; + var escapeOptions = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; - return JsonSerializer.SerializeToNode(index, options)?.ToJsonString() ?? ""; + return JsonSerializer.SerializeToNode(imageIndex, nullIgnoreOptions)?.ToJsonString(escapeOptions) ?? ""; } } From a821ac2cc768396ea97c0d54bd329d030ec9e26e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 14:39:42 +0100 Subject: [PATCH 32/50] delete empty lines --- .../Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 217f2909fffe..d56ae92d9b62 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -52,8 +52,6 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) return false; } - - using MSBuildLoggerProvider loggerProvider = new(Log); ILoggerFactory msbuildLoggerFactory = new LoggerFactory(new[] { loggerProvider }); ILogger logger = msbuildLoggerFactory.CreateLogger(); From 8bea2ec53b15a3a33b49b971916f27e532c7bd1e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 15:39:14 +0100 Subject: [PATCH 33/50] fix EndToEndMultiArch_ArchivePublishing test --- .../EndToEndTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 2881ae159aa6..19e04dd0f132 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -928,7 +928,7 @@ public void EndToEndMultiArch_ArchivePublishing() string imageName = NewImageName(); string tag = "1.0"; string image = $"{imageName}:{tag}"; - string archiveOutput = Path.Combine(TestSettings.TestArtifactsDirectory, "tarballs-output"); + string archiveOutput = TestSettings.TestArtifactsDirectory; string imageTarball = Path.Combine(archiveOutput, $"{imageName}.tar.gz"); // Create a new console project From 2e6adabb97b8db0450bf9a67295607b6998027ac Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 15:54:15 +0100 Subject: [PATCH 34/50] update EndToEndMultiArch_RemoteRegistry test since containerd store is enabled --- .../ContainerCli.cs | 3 -- .../EndToEndTests.cs | 48 +++++++------------ 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs index 1315d341f94f..13aa9928633d 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs @@ -12,9 +12,6 @@ static class ContainerCli public static RunExeCommand PullCommand(ITestOutputHelper log, params string[] args) => CreateCommand(log, "pull", args); - public static RunExeCommand TagCommand(ITestOutputHelper log, params string[] args) - => CreateCommand(log, "tag", args); - public static RunExeCommand PushCommand(ITestOutputHelper log, params string[] args) => CreateCommand(log, "push", args); diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 19e04dd0f132..ea6135c5bcbe 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -1004,6 +1004,7 @@ public void EndToEndMultiArch_RemoteRegistry() string imageX64 = $"{imageName}:{imageTag}-linux-x64"; string imageArm64 = $"{imageName}:{imageTag}-linux-arm64"; string imageIndex = $"{imageName}:{imageTag}"; + string imageFromRegistry = $"{registry}/{imageIndex}"; // Create a new console project DirectoryInfo newProjectDir = CreateNewProject("console"); @@ -1031,59 +1032,42 @@ public void EndToEndMultiArch_RemoteRegistry() .And.HaveStdOutContaining($"Pushed image '{imageX64}' to registry '{registry}'.") .And.HaveStdOutContaining($"Pushed image '{imageArm64}' to registry '{registry}'.") .And.HaveStdOutContaining($"Pushed image index '{imageIndex}' to registry '{registry}'."); - - - // Check that the containers can be run - // First pull the image from the registry, then tag so the image won't be overwritten - string imageX64Tagged = $"{registry}/test-image-{imageName}-x64"; + + // First pull the image from the registry for each platform ContainerCli.PullCommand( _testOutput, "--platform", "linux/amd64", - $"{registry}/{imageIndex}") + imageFromRegistry) .Execute() .Should().Pass(); - ContainerCli.TagCommand( + ContainerCli.PullCommand( _testOutput, - $"{registry}/{imageIndex}", - imageX64Tagged) + "--platform", + "linux/arm64", + imageFromRegistry) .Execute() .Should().Pass(); - CommandResult processResultX64 = ContainerCli.RunCommand( + + // Check that the containers can be run + ContainerCli.RunCommand( _testOutput, "--rm", "--platform", "linux/amd64", "--name", $"test-container-{imageName}-x64", - imageX64Tagged) - .Execute(); - processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); - - string imageArm64Tagged = $"{registry}/test-image-{imageName}-arm64"; - ContainerCli.PullCommand( - _testOutput, - "--platform", - "linux/arm64", - $"{registry}/{imageIndex}") - .Execute() - .Should().Pass(); - ContainerCli.TagCommand( - _testOutput, - $"{registry}/{imageIndex}", - imageArm64Tagged) - .Execute() - .Should().Pass(); - CommandResult processResultArm64 = ContainerCli.RunCommand( + imageFromRegistry) + .Execute().Should().Pass().And.HaveStdOut("Hello, World!"); + ContainerCli.RunCommand( _testOutput, "--rm", "--platform", "linux/arm64", "--name", $"test-container-{imageName}-arm64", - imageArm64Tagged) - .Execute(); - processResultArm64.Should().Pass().And.HaveStdOut("Hello, World!"); + imageFromRegistry) + .Execute().Should().Pass().And.HaveStdOut("Hello, World!"); // Cleanup newProjectDir.Delete(true); From 929c2b327de5ddacc50c36dccd3375d7b445acc4 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 16:51:17 +0100 Subject: [PATCH 35/50] go back to running containers by image and not imageId for multi-arch tests --- .../ContainerCli.cs | 3 - .../EndToEndTests.cs | 62 ++++++------------- 2 files changed, 18 insertions(+), 47 deletions(-) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs index 13aa9928633d..b69f070da2e2 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs @@ -36,9 +36,6 @@ public static RunExeCommand LoadCommand(ITestOutputHelper log, params string[] a public static RunExeCommand PortCommand(ITestOutputHelper log, string containerName, int port) => CreateCommand(log, "port", containerName, port.ToString()); - public static RunExeCommand ImagesCommand(ITestOutputHelper log, params string[] args) - => CreateCommand(log, "images", args); - private static RunExeCommand CreateCommand(ITestOutputHelper log, string command, params string[] args) { string commandPath = IsPodman ? "podman" : "docker"; diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index ea6135c5bcbe..888d8f48cfc1 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -745,8 +745,7 @@ public void EndToEndMultiArch_LocalRegistry() .WithWorkingDirectory(newProjectDir.FullName) .Execute(); - // Check that the app was published for each RID, - // one image was created locally + // Check that the app was published for each RID, one image was created locally commandResult.Should().Pass() .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) @@ -754,9 +753,6 @@ public void EndToEndMultiArch_LocalRegistry() .And.HaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-arm64'") .And.HaveStdOutContaining($"Pushed image '{image}' to local registry"); - //Multi-arch oci tarballs that are loaded to docker can only be run by their image id - string imageId = GetImageId(image); - // Check that the containers can be run CommandResult processResultX64 = ContainerCli.RunCommand( _testOutput, @@ -765,7 +761,7 @@ public void EndToEndMultiArch_LocalRegistry() "linux/amd64", "--name", $"test-container-{imageName}-x64", - imageId) + image) .Execute(); processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -776,7 +772,7 @@ public void EndToEndMultiArch_LocalRegistry() "linux/arm64", "--name", $"test-container-{imageName}-arm64", - imageId) + image) .Execute(); processResultArm64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -784,19 +780,6 @@ public void EndToEndMultiArch_LocalRegistry() newProjectDir.Delete(true); } - private string GetImageId(string image) - { - CommandResult commandResult = ContainerCli.ImagesCommand(_testOutput, "--format", "\"{{.ID}}\"", image) - .Execute(); - commandResult.Should().Pass(); - - var output = commandResult.StdOut.Split("\n").Select(s => s.Trim('"')).ToList(); - - output.Should().NotBeNullOrEmpty().And.OnlyContain(s => s == output[0]); - - return output[0]; - } - [DockerAvailableFact] public void MultiArchStillAllowsSingleRID() { @@ -948,8 +931,7 @@ public void EndToEndMultiArch_ArchivePublishing() .WithWorkingDirectory(newProjectDir.FullName) .Execute(); - // Check that the app was published for each RID, - // one image was created in local archive + // Check that the app was published for each RID, one image was created in local archive commandResult.Should().Pass() .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) @@ -957,7 +939,7 @@ public void EndToEndMultiArch_ArchivePublishing() .And.HaveStdOutContaining($"Building image '{imageName}' for runtime identifier 'linux-arm64'") .And.HaveStdOutContaining($"Pushed image '{image}' to local archive at '{imageTarball}'"); - // Check that tarballs were created + // Check that tarball were created File.Exists(imageTarball).Should().BeTrue(); // Load the multi-arch image from the tarball @@ -965,9 +947,6 @@ public void EndToEndMultiArch_ArchivePublishing() .Execute() .Should().Pass(); - //Multi-arch oci tarballs that are loaded to docker can only be run by their image id - string imageId = GetImageId(image); - // Check that the containers can be run CommandResult processResultX64 = ContainerCli.RunCommand( _testOutput, @@ -976,7 +955,7 @@ public void EndToEndMultiArch_ArchivePublishing() "linux/amd64", "--name", $"test-container-{imageName}-x64", - imageId) + image) .Execute(); processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -987,7 +966,7 @@ public void EndToEndMultiArch_ArchivePublishing() "linux/arm64", "--name", $"test-container-{imageName}-arm64", - imageId) + image) .Execute(); processResultArm64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -1024,8 +1003,8 @@ public void EndToEndMultiArch_RemoteRegistry() .Execute(); // Check that the app was published for each RID, - // images were created locally for each RID - // and image index was created + // images for each RID were pushed to remote registry + // and image index was pushed to remote registry commandResult.Should().Pass() .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) @@ -1033,6 +1012,7 @@ public void EndToEndMultiArch_RemoteRegistry() .And.HaveStdOutContaining($"Pushed image '{imageArm64}' to registry '{registry}'.") .And.HaveStdOutContaining($"Pushed image index '{imageIndex}' to registry '{registry}'."); + // Check that the containers can be run // First pull the image from the registry for each platform ContainerCli.PullCommand( _testOutput, @@ -1048,8 +1028,8 @@ public void EndToEndMultiArch_RemoteRegistry() imageFromRegistry) .Execute() .Should().Pass(); - - // Check that the containers can be run + + // Run the containers ContainerCli.RunCommand( _testOutput, "--rm", @@ -1097,7 +1077,7 @@ public void EndToEndMultiArch_ContainerRuntimeIdentifiersOverridesRuntimeIdentif .Execute(); // Check that the app was published only for RID from ContainerRuntimeIdentifiers - // images were created locally only for RID for from ContainerRuntimeIdentifiers + // images were built only for RID for from ContainerRuntimeIdentifiers commandResult.Should().Pass() .And.NotHaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-x64")) .And.HaveStdOutContaining(GetPublishArtifactsPath(newProjectDir.FullName, "linux-arm64")) @@ -1148,8 +1128,6 @@ public void EndToEndMultiArch_EnvVariables() .Execute() .Should().Pass(); - string imageId = GetImageId(image); - // Check that the env var is printed for linux/amd64 platform string containerNameX64 = $"test-container-{imageName}-x64"; CommandResult processResultX64 = ContainerCli.RunCommand( @@ -1159,7 +1137,7 @@ public void EndToEndMultiArch_EnvVariables() "linux/amd64", "--name", containerNameX64, - imageId) + image) .Execute(); processResultX64.Should().Pass().And.HaveStdOut("FooBar"); @@ -1172,7 +1150,7 @@ public void EndToEndMultiArch_EnvVariables() "linux/arm64", "--name", containerNameArm64, - imageId) + image) .Execute(); processResultArm64.Should().Pass().And.HaveStdOut("FooBar"); @@ -1215,8 +1193,6 @@ public void EndToEndMultiArch_Ports() .Execute() .Should().Pass(); - string imageId = GetImageId(image); - // Check that the ports are correct for linux/amd64 platform var containerNameX64 = $"test-container-{imageName}-x64"; CommandResult processResultX64 = ContainerCli.RunCommand( @@ -1228,7 +1204,7 @@ public void EndToEndMultiArch_Ports() containerNameX64, "-P", "--detach", - imageId) + image) .Execute(); processResultX64.Should().Pass(); @@ -1246,7 +1222,7 @@ public void EndToEndMultiArch_Ports() containerNameArm64, "-P", "--detach", - imageId) + image) .Execute(); processResultArm64.Should().Pass(); @@ -1304,13 +1280,11 @@ public void EndToEndMultiArch_Labels() .Execute() .Should().Pass(); - string imageId = GetImageId(image); - // Check that labels are set CommandResult inspectResult = ContainerCli.InspectCommand( _testOutput, "--format={{json .Config.Labels}}", - imageId) + image) .Execute(); inspectResult.Should().Pass(); var labels = JsonSerializer.Deserialize>(inspectResult.StdOut); From e46389b934c41a357ca7f82f7446786b0b1b295e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 18:01:17 +0100 Subject: [PATCH 36/50] implement skipping tests if containerd image store is not enabled --- .../LocalDaemons/DockerCli.cs | 2 +- .../ContainerCli.cs | 2 ++ .../DockerSupportsArchFact.cs | 6 +++++- .../DockerSupportsArchInlineData.cs | 2 ++ .../EndToEndTests.cs | 14 +++++++------- .../DockerAvailableUtils.cs | 11 +++++++---- 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 6b7471cafb76..12ac89441ea7 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -625,7 +625,7 @@ private static bool IsPodmanAlias() } } - private static bool IsContainerdStoreEnabledForDocker() + internal static bool IsContainerdStoreEnabledForDocker() { try { diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs index b69f070da2e2..690cb28fe1e3 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ContainerCli.cs @@ -9,6 +9,8 @@ static class ContainerCli public static bool IsAvailable => _isAvailable.Value; + public static bool IsContainerdStoreEnabledForDocker => DockerCli.IsContainerdStoreEnabledForDocker(); + public static RunExeCommand PullCommand(ITestOutputHelper log, params string[] args) => CreateCommand(log, "pull", args); diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs index 36ad9fbc0c9a..ae0e76f3c153 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs @@ -5,12 +5,16 @@ namespace Microsoft.NET.Build.Containers.IntegrationTests; public class DockerIsAvailableAndSupportsArchFactAttribute : FactAttribute { - public DockerIsAvailableAndSupportsArchFactAttribute(string arch) + public DockerIsAvailableAndSupportsArchFactAttribute(string arch, bool checkContainerdStoreAvailability = false) { if (!DockerSupportsArchHelper.DaemonIsAvailable) { base.Skip = "Skipping test because Docker is not available on this host."; } + else if (checkContainerdStoreAvailability && !DockerSupportsArchHelper.IsContainerdStoreEnabledForDocker) + { + base.Skip = "Skipping test because Docker daemon is not using containerd as the storage driver."; + } else if (!DockerSupportsArchHelper.DaemonSupportsArch(arch)) { base.Skip = $"Skipping test because Docker daemon does not support {arch}."; diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs index caafbaea4b45..7944436b39c9 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchInlineData.cs @@ -36,6 +36,8 @@ internal static class DockerSupportsArchHelper { internal static bool DaemonIsAvailable => ContainerCli.IsAvailable; + internal static bool IsContainerdStoreEnabledForDocker => ContainerCli.IsContainerdStoreEnabledForDocker; + internal static bool DaemonSupportsArch(string arch) { // an optimization - this doesn't change over time so we can compute it once diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 888d8f48cfc1..49b8ffec61f6 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -722,7 +722,7 @@ public void EndToEnd_SingleArch_NoRid() processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); } - [DockerIsAvailableAndSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64", checkContainerdStoreAvailability: true)] public void EndToEndMultiArch_LocalRegistry() { string imageName = NewImageName(); @@ -905,7 +905,7 @@ private DirectoryInfo CreateNewProject(string template, [CallerMemberName] strin private string GetPublishArtifactsPath(string projectDir, string rid, string configuration = "Debug") => Path.Combine(projectDir, "bin", configuration, ToolsetInfo.CurrentTargetFramework, rid, "publish"); - [DockerIsAvailableAndSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64", checkContainerdStoreAvailability: true)] public void EndToEndMultiArch_ArchivePublishing() { string imageName = NewImageName(); @@ -974,7 +974,7 @@ public void EndToEndMultiArch_ArchivePublishing() newProjectDir.Delete(true); } - [DockerIsAvailableAndSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64", checkContainerdStoreAvailability: true)] public void EndToEndMultiArch_RemoteRegistry() { string imageName = NewImageName(); @@ -1053,7 +1053,7 @@ public void EndToEndMultiArch_RemoteRegistry() newProjectDir.Delete(true); } - [DockerAvailableFact] + [DockerAvailableFact(checkContainerdStoreAvailability: true)] public void EndToEndMultiArch_ContainerRuntimeIdentifiersOverridesRuntimeIdentifiers() { // Create a new console project @@ -1088,7 +1088,7 @@ public void EndToEndMultiArch_ContainerRuntimeIdentifiersOverridesRuntimeIdentif newProjectDir.Delete(true); } - [DockerIsAvailableAndSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64", checkContainerdStoreAvailability: true)] public void EndToEndMultiArch_EnvVariables() { string imageName = NewImageName(); @@ -1158,7 +1158,7 @@ public void EndToEndMultiArch_EnvVariables() newProjectDir.Delete(true); } - [DockerIsAvailableAndSupportsArchFact("linux/arm64")] + [DockerIsAvailableAndSupportsArchFact("linux/arm64", checkContainerdStoreAvailability: true)] public void EndToEndMultiArch_Ports() { string imageName = NewImageName(); @@ -1256,7 +1256,7 @@ private void CheckPorts(string containerName, int[] correctPorts, int[] incorrec } } - [DockerAvailableFact] + [DockerAvailableFact(checkContainerdStoreAvailability: true)] public void EndToEndMultiArch_Labels() { string imageName = NewImageName(); diff --git a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/DockerAvailableUtils.cs b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/DockerAvailableUtils.cs index 74184ec675b1..61c5f2dde3bf 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/DockerAvailableUtils.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/DockerAvailableUtils.cs @@ -25,17 +25,20 @@ public class DockerAvailableFactAttribute : FactAttribute { public static string LocalRegistry => DockerCliStatus.LocalRegistry; - public DockerAvailableFactAttribute(bool skipPodman = false) + public DockerAvailableFactAttribute(bool skipPodman = false, bool checkContainerdStoreAvailability = false) { if (!DockerCliStatus.IsAvailable) { base.Skip = "Skipping test because Docker is not available on this host."; } - - if (skipPodman && DockerCliStatus.Command == DockerCli.PodmanCommand) + else if (checkContainerdStoreAvailability && !DockerCli.IsContainerdStoreEnabledForDocker()) { - base.Skip = $"Skipping test with {DockerCliStatus.Command} cli."; + base.Skip = "Skipping test because Docker daemon is not using containerd as the storage driver."; } + else if (skipPodman && DockerCliStatus.Command == DockerCli.PodmanCommand) + { + base.Skip = $"Skipping test with {DockerCliStatus.Command} cli."; + } } } From 299e6d107182a4b63761220de8d0dda4dc0a1541 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 18:19:27 +0100 Subject: [PATCH 37/50] remove using --- .../EndToEndTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 49b8ffec61f6..ccef1a3b41c4 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -4,7 +4,6 @@ using System.Formats.Tar; using System.Runtime.CompilerServices; using System.Text.Json; -using Microsoft.Build.Logging; using Microsoft.DotNet.Cli.Utils; using Microsoft.NET.Build.Containers.LocalDaemons; using Microsoft.NET.Build.Containers.Resources; From 674ee0ef816f9234760d03b60050abc3f041f909 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 21:34:46 +0100 Subject: [PATCH 38/50] refactor to bring back GeneratedImageIndex; fix ImageIndexGeneratorTests --- .../ImageIndexGenerator.cs | 5 +++ .../ImagePublisher.cs | 13 ++----- .../LocalDaemons/ArchiveFileRegistry.cs | 4 +-- .../LocalDaemons/DockerCli.cs | 23 ++++++------ .../LocalDaemons/ILocalRegistry.cs | 2 +- .../MultiArchImage.cs | 16 +++++++++ .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 2 ++ .../Registry/Registry.cs | 10 ++++-- .../Tasks/CreateImageIndex.Interface.cs | 10 ++++++ .../Tasks/CreateImageIndex.cs | 36 ++++++++++++++++--- .../Microsoft.NET.Build.Containers.targets | 5 ++- .../CreateImageIndexTests.cs | 13 +++++++ .../ImageIndexGeneratorTests.cs | 6 ++-- 13 files changed, 107 insertions(+), 38 deletions(-) create mode 100644 src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index 8abbe315d0c8..d2bc53a5900d 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -47,6 +47,11 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images) internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType) { + if (images.Length == 0) + { + throw new ArgumentException(string.Format(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]; diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index bc3e330441af..8dc164f41f49 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -51,7 +51,7 @@ await PushToRemoteRegistryAsync( } public static async Task PublishImageAsync( - BuiltImage[] multiArchImage, + MultiArchImage multiArchImage, SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, @@ -82,16 +82,7 @@ await PushToRemoteRegistryAsync( Log, BuildEngine, cancellationToken, - async (images, source, destination, token) => - { - (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images); - await destinationImageReference.RemoteRegistry!.PushManifestListAsync( - destinationImageReference.Repository, - destinationImageReference.Tags, - imageIndex, - mediaType, - cancellationToken).ConfigureAwait(false); - }, + destinationImageReference.RemoteRegistry!.PushManifestListAsync, Strings.ImageIndexUploadedToRegistry).ConfigureAwait(false); break; default: diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs index 5af3edd5a546..c23e22d8b9aa 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs @@ -46,10 +46,10 @@ public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReferen => await LoadAsync(image, sourceReference, destinationReference, cancellationToken, DockerCli.WriteImageToStreamAsync); - public async Task LoadAsync(BuiltImage[] images, SourceImageReference sourceReference, + public async Task LoadAsync(MultiArchImage multiArchImage, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) - => await LoadAsync(images, sourceReference, destinationReference, cancellationToken, + => await LoadAsync(multiArchImage, sourceReference, destinationReference, cancellationToken, DockerCli.WriteMultiArchOciImageToStreamAsync); public Task IsAvailableAsync(CancellationToken cancellationToken) => Task.FromResult(true); diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index 12ac89441ea7..d5b8fb2b3c5f 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -130,8 +130,8 @@ public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReferen // For loading to the local registry, we use the Docker format. Two reasons: one - compatibility with previous behavior before oci formatted publishing was available, two - Podman cannot load multi tag oci image tarball. => await LoadAsync(image, sourceReference, destinationReference, WriteDockerImageToStreamAsync, cancellationToken); - public async Task LoadAsync(BuiltImage[] images, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) - => await LoadAsync(images, sourceReference, destinationReference, WriteMultiArchOciImageToStreamAsync, cancellationToken, checkContainerdStore: true); + public async Task LoadAsync(MultiArchImage multiArchImage, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) + => await LoadAsync(multiArchImage, sourceReference, destinationReference, WriteMultiArchOciImageToStreamAsync, cancellationToken, checkContainerdStore: true); public async Task IsAvailableAsync(CancellationToken cancellationToken) { @@ -500,7 +500,7 @@ await WriteManifestForOciImage(writer, image, cancellationToken) } public static async Task WriteMultiArchOciImageToStreamAsync( - BuiltImage[] images, + MultiArchImage multiArchImage, SourceImageReference sourceReference, DestinationImageReference destinationReference, Stream imageStream, @@ -510,13 +510,13 @@ public static async Task WriteMultiArchOciImageToStreamAsync( using TarWriter writer = new(imageStream, TarEntryFormat.Pax, leaveOpen: true); - foreach (var image in images) + foreach (var image in multiArchImage.Images!) { await WriteOciImageToBlobs(writer, image, sourceReference, cancellationToken) .ConfigureAwait(false); } - await WriteIndexJsonForMultiArchOciImage(writer, images, destinationReference, cancellationToken) + await WriteIndexJsonForMultiArchOciImage(writer, multiArchImage, destinationReference, cancellationToken) .ConfigureAwait(false); await WriteOciLayout(writer, cancellationToken) @@ -525,21 +525,18 @@ await WriteOciLayout(writer, cancellationToken) private static async Task WriteIndexJsonForMultiArchOciImage( TarWriter writer, - BuiltImage[] images, + MultiArchImage multiArchImage, DestinationImageReference destinationReference, CancellationToken cancellationToken) { // 1. create manifest list for the blobs cancellationToken.ThrowIfCancellationRequested(); - // For multi-arch we publish only oci-formatted image tarballs. - string manifestListJson = ImageIndexGenerator.GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1); - - var manifestListDigest = DigestUtils.GetDigest(manifestListJson); + var manifestListDigest = DigestUtils.GetDigest(multiArchImage.ImageIndex); var manifestListSha = DigestUtils.GetShaFromDigest(manifestListDigest); var manifestListPath = $"{_blobsPath}/{manifestListSha}"; - using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(manifestListJson))) + using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(multiArchImage.ImageIndex))) { PaxTarEntry indexEntry = new(TarEntryType.RegularFile, manifestListPath) { @@ -552,9 +549,9 @@ private static async Task WriteIndexJsonForMultiArchOciImage( cancellationToken.ThrowIfCancellationRequested(); string indexJson = ImageIndexGenerator.GenerateImageIndexWithAnnotations( - SchemaTypes.OciImageIndexV1, + multiArchImage.ImageIndexMediaType, manifestListDigest, - manifestListJson.Length, + multiArchImage.ImageIndex.Length, destinationReference.Repository, destinationReference.Tags); diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ILocalRegistry.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ILocalRegistry.cs index 34ccb13d093d..c4ee04a7f5d6 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ILocalRegistry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ILocalRegistry.cs @@ -16,7 +16,7 @@ internal interface ILocalRegistry { /// /// Loads a multi-arch image (presumably from a tarball) into the local registry. /// - public Task LoadAsync(BuiltImage[] images, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken); + public Task LoadAsync(MultiArchImage multiArchImage, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken); /// /// Checks to see if the local registry is available. This is used to give nice errors to the user. diff --git a/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs b/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs new file mode 100644 index 000000000000..2e671b186d9e --- /dev/null +++ b/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs @@ -0,0 +1,16 @@ +// 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; + +/// +/// Represents constructed image ready for further processing. +/// +internal sealed class MultiArchImage +{ + internal required string ImageIndex { get; init; } + + internal required string ImageIndexMediaType { get; init; } + + internal BuiltImage[]? Images { get; init; } +} \ No newline at end of file diff --git a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt index e5efa4bed6c1..5c07a3871ceb 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -34,6 +34,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateImageIndex.GeneratedArchiveOutputPath 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![]! diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs index 2e754986334a..87671575f34a 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs @@ -525,13 +525,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) + public async Task PushManifestListAsync( + MultiArchImage multiArchImage, + SourceImageReference sourceImageReference, + DestinationImageReference destinationImageReference, + CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - foreach (var tag in tags) + foreach (var tag in destinationImageReference.Tags) { _logger.LogInformation(Strings.Registry_TagUploadStarted, tag, RegistryName); - await _registryAPI.Manifest.PutAsync(repositoryName, tag, manifestListJson, mediaType, cancellationToken).ConfigureAwait(false); + await _registryAPI.Manifest.PutAsync(destinationImageReference.Repository, tag, multiArchImage.ImageIndex, multiArchImage.ImageIndexMediaType, cancellationToken).ConfigureAwait(false); _logger.LogInformation(Strings.Registry_TagUploaded, tag, RegistryName); } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs index 128657b0a897..2751eb5ebbc3 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs @@ -61,9 +61,18 @@ partial class CreateImageIndex [Required] public string[] ImageTags { get; set; } + /// + /// The generated archive output path. + /// [Output] public string GeneratedArchiveOutputPath { get; set; } + /// + /// The generated image index (manifest list) in JSON format. + /// + [Output] + public string GeneratedImageIndex { get; set; } + public CreateImageIndex() { BaseRegistry = string.Empty; @@ -76,5 +85,6 @@ public CreateImageIndex() Repository = string.Empty; ImageTags = Array.Empty(); GeneratedArchiveOutputPath = string.Empty; + GeneratedImageIndex = string.Empty; } } \ No newline at end of file diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index d56ae92d9b62..65443e816fd6 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -74,14 +74,17 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) return false; } + var multiArchImage = CreateMultiArchImage(images, destinationImageReference.Kind); + + GeneratedImageIndex = multiArchImage.ImageIndex; + GeneratedArchiveOutputPath = ArchiveOutputPath; + logger.LogInformation(Strings.BuildingImageIndex, destinationImageReference, string.Join(", ", images.Select(i => i.ManifestDigest))); var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); - await ImagePublisher.PublishImageAsync(images, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) - .ConfigureAwait(false); - - GeneratedArchiveOutputPath = ArchiveOutputPath; + await ImagePublisher.PublishImageAsync(multiArchImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) + .ConfigureAwait(false); return !Log.HasLoggedErrors; } @@ -157,4 +160,29 @@ private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind) } return (architecture, os); } + + private static MultiArchImage CreateMultiArchImage(BuiltImage[] images, DestinationImageReferenceKind destinationImageKind) + { + switch (destinationImageKind) + { + case DestinationImageReferenceKind.LocalRegistry: + return new MultiArchImage() + { + // For multi-arch we publish only oci-formatted image tarballs. + ImageIndex = ImageIndexGenerator.GenerateImageIndex(images, SchemaTypes.OciManifestV1, SchemaTypes.OciImageIndexV1), + ImageIndexMediaType = SchemaTypes.OciImageIndexV1, + Images = images + }; + case DestinationImageReferenceKind.RemoteRegistry: + (string imageIndex, string mediaType) = ImageIndexGenerator.GenerateImageIndex(images); + return new MultiArchImage() + { + ImageIndex = imageIndex, + ImageIndexMediaType = mediaType, + // For remote registry we don't need individual images, as they should be pushed already + }; + default: + throw new ArgumentOutOfRangeException(); + } + } } diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets index 51b28a1db5f6..d7cf4bc5ecbf 100644 --- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets +++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets @@ -357,7 +357,10 @@ ImageTags="@(ContainerImageTags)" BaseRegistry="$(ContainerBaseRegistry)" BaseImageName="$(ContainerBaseName)" - BaseImageTag="$(ContainerBaseTag)" /> + BaseImageTag="$(ContainerBaseTag)"> + + + diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs index 24703040cda6..2615b2e178ee 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateImageIndexTests.cs @@ -44,6 +44,19 @@ public async Task CreateImageIndex_Baseline() 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)); diff --git a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs index f89d2601b9b8..6b87a502d6a6 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs @@ -104,7 +104,7 @@ public void GenerateDockerManifestList() ]; var (imageIndex, mediaType) = ImageIndexGenerator.GenerateImageIndex(images); - Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.docker.distribution.manifest.list.v2\\u002Bjson\",\"manifests\":[{\"mediaType\":\"application/vnd.docker.distribution.manifest.v2\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\"}},{\"mediaType\":\"application/vnd.docker.distribution.manifest.v2\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\"}}]}", imageIndex); + Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.docker.distribution.manifest.list.v2+json\",\"manifests\":[{\"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\"}},{\"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\"}}]}", imageIndex); Assert.Equal(SchemaTypes.DockerManifestListV2, mediaType); } @@ -138,7 +138,7 @@ public void GenerateOciImageIndex() ]; var (imageIndex, mediaType) = ImageIndexGenerator.GenerateImageIndex(images); - Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1\\u002Bjson\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\"}},{\"mediaType\":\"application/vnd.oci.image.manifest.v1\\u002Bjson\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\"}}]}", imageIndex); + Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1+json\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"size\":3,\"digest\":\"sha256:digest1\",\"platform\":{\"architecture\":\"arch1\",\"os\":\"os1\"}},{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"size\":3,\"digest\":\"sha256:digest2\",\"platform\":{\"architecture\":\"arch2\",\"os\":\"os2\"}}]}", imageIndex); Assert.Equal(SchemaTypes.OciImageIndexV1, mediaType); } @@ -146,6 +146,6 @@ public void GenerateOciImageIndex() public void GenerateImageIndexWithAnnotations() { string imageIndex = ImageIndexGenerator.GenerateImageIndexWithAnnotations("mediaType", "sha256:digest", 3, "repository", ["1.0", "2.0"]); - Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1\\u002Bjson\",\"manifests\":[{\"mediaType\":\"mediaType\",\"size\":3,\"digest\":\"sha256:digest\",\"platform\":{},\"annotations\":{\"io.containerd.image.name\":\"repository:1.0\",\"org.opencontainers.image.ref.name\":\"1.0\"}},{\"mediaType\":\"mediaType\",\"size\":3,\"digest\":\"sha256:digest\",\"platform\":{},\"annotations\":{\"io.containerd.image.name\":\"repository:2.0\",\"org.opencontainers.image.ref.name\":\"2.0\"}}]}", imageIndex); + Assert.Equal("{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1+json\",\"manifests\":[{\"mediaType\":\"mediaType\",\"size\":3,\"digest\":\"sha256:digest\",\"platform\":{},\"annotations\":{\"io.containerd.image.name\":\"docker.io/library/repository:1.0\",\"org.opencontainers.image.ref.name\":\"1.0\"}},{\"mediaType\":\"mediaType\",\"size\":3,\"digest\":\"sha256:digest\",\"platform\":{},\"annotations\":{\"io.containerd.image.name\":\"docker.io/library/repository:2.0\",\"org.opencontainers.image.ref.name\":\"2.0\"}}]}", imageIndex); } } From adaffdd48928a927d3cf8cb2bdb7a381cf34c67b Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 5 Feb 2025 21:36:58 +0100 Subject: [PATCH 39/50] small fix --- .../Microsoft.NET.Build.Containers/ImageIndexGenerator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index d2bc53a5900d..ce6d91c1edbf 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -21,7 +21,7 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images) { if (images.Length == 0) { - throw new ArgumentException(string.Format(Strings.ImagesEmpty)); + throw new ArgumentException(Strings.ImagesEmpty); } string manifestMediaType = images[0].ManifestMediaType; @@ -49,9 +49,9 @@ internal static string GenerateImageIndex(BuiltImage[] images, string manifestMe { if (images.Length == 0) { - throw new ArgumentException(string.Format(Strings.ImagesEmpty)); + 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]; From 3285270e2870a1a0025b9530bb669abb6ba74a9e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 6 Feb 2025 15:47:25 +0100 Subject: [PATCH 40/50] make BuiltImage and MultiArchImage readonly structs --- src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs | 2 +- src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs index bd31ccc8cbf5..fd14bce11c58 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs @@ -6,7 +6,7 @@ namespace Microsoft.NET.Build.Containers; /// /// Represents constructed image ready for further processing. /// -internal sealed class BuiltImage +internal readonly struct BuiltImage { /// /// Gets image configuration in JSON format. diff --git a/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs b/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs index 2e671b186d9e..b24fbf65e87f 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs @@ -6,7 +6,7 @@ namespace Microsoft.NET.Build.Containers; /// /// Represents constructed image ready for further processing. /// -internal sealed class MultiArchImage +internal readonly struct MultiArchImage { internal required string ImageIndex { get; init; } From 9cea231503a99fddc5eacacca09ec102d0260690 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 6 Feb 2025 21:00:16 +0100 Subject: [PATCH 41/50] if ArchiveOutputPath does't have file extension then treat it as directory; add ArchiveFileRegistryTests --- .../LocalDaemons/ArchiveFileRegistry.cs | 12 ++- .../ArchiveFileRegistryTests.cs | 73 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ArchiveFileRegistryTests.cs diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs index c23e22d8b9aa..86c170fcb1f9 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs @@ -14,14 +14,22 @@ public ArchiveFileRegistry(string archiveOutputPath) ArchiveOutputPath = archiveOutputPath; } - private async Task LoadAsync(T image, SourceImageReference sourceReference, + internal async Task LoadAsync(T image, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken, Func writeStreamFunc) { var fullPath = Path.GetFullPath(ArchiveOutputPath); + var directorySeparatorChar = Path.DirectorySeparatorChar; + + // if doesn't end with a file extension, assume it's a directory + if (!fullPath.Split(directorySeparatorChar).Last().Contains('.')) + { + fullPath += Path.DirectorySeparatorChar; + } + // pointing to a directory? -> append default name - if (Directory.Exists(fullPath) || ArchiveOutputPath.EndsWith("/") || ArchiveOutputPath.EndsWith("\\")) + if (fullPath.EndsWith(directorySeparatorChar)) { fullPath = Path.Combine(fullPath, destinationReference.Repository + ".tar.gz"); } diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ArchiveFileRegistryTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ArchiveFileRegistryTests.cs new file mode 100644 index 000000000000..e5bfc54711d0 --- /dev/null +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ArchiveFileRegistryTests.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.NET.Build.Containers.LocalDaemons; + +namespace Microsoft.NET.Build.Containers.IntegrationTests; + +public class ArchiveFileRegistryTests +{ + [Fact] + public async Task ArchiveOutputPathIsExistingDirectory_CreatesFileWithRepositoryNameAndTarGz() + { + string archiveOutputPath = TestSettings.TestArtifactsDirectory; + string expectedCreatedFilePath = Path.Combine(TestSettings.TestArtifactsDirectory, "repository.tar.gz"); + + await CreateRegistryAndCallLoadAsync(archiveOutputPath).ConfigureAwait(false); + + Assert.True(File.Exists(expectedCreatedFilePath)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ArchiveOutputPathIsNonExistingDirectory_CreatesDirectoryAndFileWithRepositoryNameAndTarGz(bool includeDirectorySeperatorAtTheEnd) + { + string archiveOutputPath = Path.Combine( + TestSettings.TestArtifactsDirectory, + "nonexisting" + (includeDirectorySeperatorAtTheEnd ? Path.DirectorySeparatorChar : "")); + string expectedCreatedFilePath = Path.Combine(archiveOutputPath, "repository.tar.gz"); + + await CreateRegistryAndCallLoadAsync(archiveOutputPath).ConfigureAwait(false); + + Assert.True(File.Exists(expectedCreatedFilePath)); + } + + [Fact] + public async Task ArchiveOutputPathIsCustomFileNameInExistingDirectory_CreatesFileWithThatName() + { + string archiveOutputPath = Path.Combine(TestSettings.TestArtifactsDirectory, "custom-name.withextension"); + string expectedCreatedFilePath = archiveOutputPath; + + await CreateRegistryAndCallLoadAsync(archiveOutputPath).ConfigureAwait(false); + + Assert.True(File.Exists(expectedCreatedFilePath)); + } + + [Fact] + public async Task ArchiveOutputPathIsCustomFileNameInNonExistingDirectory_CreatesDirectoryAndFileWithThatName() + { + string archiveOutputPath = Path.Combine(TestSettings.TestArtifactsDirectory, $"nonexisting-directory{Path.AltDirectorySeparatorChar}custom-name.withextension"); + string expectedCreatedFilePath = archiveOutputPath; + + await CreateRegistryAndCallLoadAsync(archiveOutputPath).ConfigureAwait(false); + + Assert.True(File.Exists(expectedCreatedFilePath)); + } + + private async Task CreateRegistryAndCallLoadAsync(string archiveOutputPath) + { + var registry = new ArchiveFileRegistry(archiveOutputPath); + var destinationImageReference = new DestinationImageReference(registry, "repository", ["tag"]); + + await registry.LoadAsync( + "test image", + new SourceImageReference(), + destinationImageReference, + CancellationToken.None, + async (img, srcRef, destRef, stream, token) => + { + await Task.CompletedTask; + }).ConfigureAwait(false); + } +} \ No newline at end of file From d9985105ecd7f4730bbcc6a921269a17f88ea06a Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 6 Feb 2025 21:09:17 +0100 Subject: [PATCH 42/50] trim BaseRegistry before checking null or empty --- .../Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs | 2 +- .../Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 65443e816fd6..6382a642cc1b 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -23,7 +23,7 @@ public void Dispose() _cancellationTokenSource.Dispose(); } - private bool IsLocalPull => string.IsNullOrEmpty(BaseRegistry); + private bool IsLocalPull => string.IsNullOrEmpty(BaseRegistry.Trim()); public override bool Execute() { diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index 997ca7b74973..a5e98431d802 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -23,7 +23,7 @@ public sealed partial class CreateNewImage : Microsoft.Build.Utilities.Task, ICa /// public string ToolPath { get; set; } - private bool IsLocalPull => string.IsNullOrEmpty(BaseRegistry); + private bool IsLocalPull => string.IsNullOrEmpty(BaseRegistry.Trim()); public void Cancel() => _cancellationTokenSource.Cancel(); From 5094a413f5ea7e1dc5db83ab73cb495fcb6fb2d7 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 6 Feb 2025 21:25:40 +0100 Subject: [PATCH 43/50] BaseRegistry shouldn't be required parameter --- .../Tasks/CreateImageIndex.Interface.cs | 1 - .../Tasks/CreateNewImage.Interface.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs index 2751eb5ebbc3..6e64ede53469 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs @@ -11,7 +11,6 @@ partial class CreateImageIndex /// The base registry to pull from. /// Ex: mcr.microsoft.com /// - [Required] public string BaseRegistry { get; set; } /// 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 5e9c01de4e45..b23a8482f3cf 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.Interface.cs @@ -20,7 +20,6 @@ partial class CreateNewImage /// The base registry to pull from. /// Ex: mcr.microsoft.com /// - [Required] public string BaseRegistry { get; set; } /// From e9f4af3870ee2346e1ec0036a16c5252ed28337b Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sat, 8 Feb 2025 09:59:15 +0100 Subject: [PATCH 44/50] address comments --- .../Microsoft.NET.Build.Containers/DigestUtils.cs | 2 +- .../ImageIndexGenerator.cs | 12 +++++++++++- .../LocalDaemons/ArchiveFileRegistry.cs | 2 +- .../Tasks/CreateImageIndex.cs | 2 +- .../Tasks/CreateNewImage.cs | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs b/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs index 4171589c025d..a6f680436c39 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs @@ -21,7 +21,7 @@ internal static string GetShaFromDigest(string digest) { if (!digest.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentException("Invalid digest format. Digest must start with 'sha256:'."); + throw new ArgumentException($"Invalid digest '{digest}'. Digest must start with 'sha256:'."); } return digest.Substring("sha256:".Length); diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index ce6d91c1edbf..86e8d0a5c0c5 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -13,7 +13,7 @@ internal static class ImageIndexGenerator /// /// Generates an image index from the given images. /// - /// + /// Images to generate image index from. /// Returns json string of image index and image index mediaType. /// /// @@ -45,6 +45,15 @@ internal static (string, string) GenerateImageIndex(BuiltImage[] images) } } + /// + /// Generates an image index from the given images. + /// + /// Images to generate image index from. + /// Media type of the manifest. + /// Media type of the produced image index. + /// Returns json string of image index and image index mediaType. + /// + /// internal static string GenerateImageIndex(BuiltImage[] images, string manifestMediaType, string imageIndexMediaType) { if (images.Length == 0) @@ -116,6 +125,7 @@ private static string GetJsonStringFromImageIndex(T imageIndex) { 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 diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs index 86c170fcb1f9..d127635ba584 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs @@ -23,7 +23,7 @@ internal async Task LoadAsync(T image, SourceImageReference sourceReference, var directorySeparatorChar = Path.DirectorySeparatorChar; // if doesn't end with a file extension, assume it's a directory - if (!fullPath.Split(directorySeparatorChar).Last().Contains('.')) + if (!Path.HasExtension(fullPath)) { fullPath += Path.DirectorySeparatorChar; } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 6382a642cc1b..918cd60394ac 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -23,7 +23,7 @@ public void Dispose() _cancellationTokenSource.Dispose(); } - private bool IsLocalPull => string.IsNullOrEmpty(BaseRegistry.Trim()); + private bool IsLocalPull => string.IsNullOrWhiteSpace(BaseRegistry); public override bool Execute() { diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index a5e98431d802..6f3dbbdf8530 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -23,7 +23,7 @@ public sealed partial class CreateNewImage : Microsoft.Build.Utilities.Task, ICa /// public string ToolPath { get; set; } - private bool IsLocalPull => string.IsNullOrEmpty(BaseRegistry.Trim()); + private bool IsLocalPull => string.IsNullOrWhiteSpace(BaseRegistry); public void Cancel() => _cancellationTokenSource.Cancel(); From 65837f687913ac2d3f8f8e796bccb42eae04be84 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sat, 8 Feb 2025 10:34:08 +0100 Subject: [PATCH 45/50] refactor ImagePublisher --- .../ImagePublisher.cs | 62 +++++++++---------- .../Tasks/CreateImageIndex.cs | 2 +- .../Tasks/CreateNewImage.cs | 2 +- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index 8dc164f41f49..c44d7fec09fc 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -13,7 +13,7 @@ public static async Task PublishImageAsync( SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, - IBuildEngine? BuildEngine, + bool isSafeLog, Telemetry telemetry, CancellationToken cancellationToken) { @@ -27,7 +27,7 @@ await PushToLocalRegistryAsync( sourceImageReference, destinationImageReference, Log, - BuildEngine, + isSafeLog, telemetry, cancellationToken, destinationImageReference.LocalRegistry!.LoadAsync).ConfigureAwait(false); @@ -38,7 +38,7 @@ await PushToRemoteRegistryAsync( sourceImageReference, destinationImageReference, Log, - BuildEngine, + isSafeLog, cancellationToken, destinationImageReference.RemoteRegistry!.PushAsync, Strings.ContainerBuilder_ImageUploadedToRegistry).ConfigureAwait(false); @@ -55,7 +55,7 @@ public static async Task PublishImageAsync( SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, - IBuildEngine? BuildEngine, + bool isSafeLog, Telemetry telemetry, CancellationToken cancellationToken) { @@ -69,7 +69,7 @@ await PushToLocalRegistryAsync( sourceImageReference, destinationImageReference, Log, - BuildEngine, + isSafeLog, telemetry, cancellationToken, destinationImageReference.LocalRegistry!.LoadAsync).ConfigureAwait(false); @@ -80,7 +80,7 @@ await PushToRemoteRegistryAsync( sourceImageReference, destinationImageReference, Log, - BuildEngine, + isSafeLog, cancellationToken, destinationImageReference.RemoteRegistry!.PushManifestListAsync, Strings.ImageIndexUploadedToRegistry).ConfigureAwait(false); @@ -97,7 +97,7 @@ private static async Task PushToLocalRegistryAsync( SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, - IBuildEngine? BuildEngine, + bool isSafeLog, Telemetry telemetry, CancellationToken cancellationToken, Func loadFunc) @@ -112,31 +112,34 @@ private static async Task PushToLocalRegistryAsync( try { await loadFunc(image, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); - if (BuildEngine != null) + if (isSafeLog) { Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); } } - catch (ContainerHttpException e) + catch (ContainerHttpException e) when (isSafeLog) { - if (BuildEngine != null) - { - Log.LogErrorFromException(e, true); - } + Log.LogErrorFromException(e, true); } catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) { telemetry.LogLocalLoadError(); - Log.LogErrorFromException(dle, showStackTrace: false); + if (isSafeLog) + { + Log.LogErrorFromException(dle, showStackTrace: false); + } } - catch (ArgumentException argEx) + catch (ArgumentException argEx) when (isSafeLog) { Log.LogErrorFromException(argEx, showStackTrace: false); } catch (DockerLoadException dle) { telemetry.LogLocalLoadError(); - Log.LogErrorFromException(dle, showStackTrace: false); + if (isSafeLog) + { + Log.LogErrorFromException(dle, showStackTrace: false); + } } } @@ -145,7 +148,7 @@ private static async Task PushToRemoteRegistryAsync( SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, - IBuildEngine? BuildEngine, + bool isSafeLog, CancellationToken cancellationToken, Func pushFunc, string successMessage) @@ -157,32 +160,23 @@ await pushFunc( sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); - if (BuildEngine != null) + if (isSafeLog) { Log.LogMessage(MessageImportance.High, successMessage, destinationImageReference, destinationImageReference.RemoteRegistry!.RegistryName); } } - catch (UnableToAccessRepositoryException) + catch (UnableToAccessRepositoryException) when (isSafeLog) { - if (BuildEngine != null) - { - Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); - } + Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); } - catch (ContainerHttpException e) + catch (ContainerHttpException e) when (isSafeLog) { - if (BuildEngine != null) - { - Log.LogErrorFromException(e, true); - } + Log.LogErrorFromException(e, true); } - catch (Exception e) + catch (Exception e) when (isSafeLog) { - if (BuildEngine != null) - { - Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); - Log.LogMessage(MessageImportance.Low, "Details: {0}", e); - } + Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); + Log.LogMessage(MessageImportance.Low, "Details: {0}", e); } } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 918cd60394ac..64ca1939bdab 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -83,7 +83,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); - await ImagePublisher.PublishImageAsync(multiArchImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) + await ImagePublisher.PublishImageAsync(multiArchImage, sourceImageReference, destinationImageReference, Log, BuildEngine != null, telemetry, cancellationToken) .ConfigureAwait(false); return !Log.HasLoggedErrors; diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index 6f3dbbdf8530..c6e1fba6ce43 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -183,7 +183,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) if (!SkipPublishing) { - await ImagePublisher.PublishImageAsync(builtImage, sourceImageReference, destinationImageReference, Log, BuildEngine, telemetry, cancellationToken) + await ImagePublisher.PublishImageAsync(builtImage, sourceImageReference, destinationImageReference, Log, BuildEngine != null, telemetry, cancellationToken) .ConfigureAwait(false); } From 960bb7d19ddfd923270d8f9e17066330c8c098d6 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sat, 8 Feb 2025 10:57:41 +0100 Subject: [PATCH 46/50] remove BuildEngine null check before logging --- .../ImagePublisher.cs | 38 +++++-------------- .../Tasks/CreateImageIndex.cs | 2 +- .../Tasks/CreateNewImage.cs | 13 +++---- 3 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs index c44d7fec09fc..1a16cbbad500 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImagePublisher.cs @@ -13,7 +13,6 @@ public static async Task PublishImageAsync( SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, - bool isSafeLog, Telemetry telemetry, CancellationToken cancellationToken) { @@ -27,7 +26,6 @@ await PushToLocalRegistryAsync( sourceImageReference, destinationImageReference, Log, - isSafeLog, telemetry, cancellationToken, destinationImageReference.LocalRegistry!.LoadAsync).ConfigureAwait(false); @@ -38,7 +36,6 @@ await PushToRemoteRegistryAsync( sourceImageReference, destinationImageReference, Log, - isSafeLog, cancellationToken, destinationImageReference.RemoteRegistry!.PushAsync, Strings.ContainerBuilder_ImageUploadedToRegistry).ConfigureAwait(false); @@ -55,7 +52,6 @@ public static async Task PublishImageAsync( SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, - bool isSafeLog, Telemetry telemetry, CancellationToken cancellationToken) { @@ -69,7 +65,6 @@ await PushToLocalRegistryAsync( sourceImageReference, destinationImageReference, Log, - isSafeLog, telemetry, cancellationToken, destinationImageReference.LocalRegistry!.LoadAsync).ConfigureAwait(false); @@ -80,7 +75,6 @@ await PushToRemoteRegistryAsync( sourceImageReference, destinationImageReference, Log, - isSafeLog, cancellationToken, destinationImageReference.RemoteRegistry!.PushManifestListAsync, Strings.ImageIndexUploadedToRegistry).ConfigureAwait(false); @@ -97,7 +91,6 @@ private static async Task PushToLocalRegistryAsync( SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, - bool isSafeLog, Telemetry telemetry, CancellationToken cancellationToken, Func loadFunc) @@ -112,34 +105,25 @@ private static async Task PushToLocalRegistryAsync( try { await loadFunc(image, sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); - if (isSafeLog) - { - Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); - } + Log.LogMessage(MessageImportance.High, Strings.ContainerBuilder_ImageUploadedToLocalDaemon, destinationImageReference, localRegistry); } - catch (ContainerHttpException e) when (isSafeLog) + catch (ContainerHttpException e) { Log.LogErrorFromException(e, true); } catch (AggregateException ex) when (ex.InnerException is DockerLoadException dle) { telemetry.LogLocalLoadError(); - if (isSafeLog) - { - Log.LogErrorFromException(dle, showStackTrace: false); - } + Log.LogErrorFromException(dle, showStackTrace: false); } - catch (ArgumentException argEx) when (isSafeLog) + catch (ArgumentException argEx) { Log.LogErrorFromException(argEx, showStackTrace: false); } catch (DockerLoadException dle) { telemetry.LogLocalLoadError(); - if (isSafeLog) - { - Log.LogErrorFromException(dle, showStackTrace: false); - } + Log.LogErrorFromException(dle, showStackTrace: false); } } @@ -148,7 +132,6 @@ private static async Task PushToRemoteRegistryAsync( SourceImageReference sourceImageReference, DestinationImageReference destinationImageReference, Microsoft.Build.Utilities.TaskLoggingHelper Log, - bool isSafeLog, CancellationToken cancellationToken, Func pushFunc, string successMessage) @@ -160,20 +143,17 @@ await pushFunc( sourceImageReference, destinationImageReference, cancellationToken).ConfigureAwait(false); - if (isSafeLog) - { - Log.LogMessage(MessageImportance.High, successMessage, destinationImageReference, destinationImageReference.RemoteRegistry!.RegistryName); - } + Log.LogMessage(MessageImportance.High, successMessage, destinationImageReference, destinationImageReference.RemoteRegistry!.RegistryName); } - catch (UnableToAccessRepositoryException) when (isSafeLog) + catch (UnableToAccessRepositoryException) { Log.LogErrorWithCodeFromResources(nameof(Strings.UnableToAccessRepository), destinationImageReference.Repository, destinationImageReference.RemoteRegistry!.RegistryName); } - catch (ContainerHttpException e) when (isSafeLog) + catch (ContainerHttpException e) { Log.LogErrorFromException(e, true); } - catch (Exception e) when (isSafeLog) + catch (Exception e) { Log.LogErrorWithCodeFromResources(nameof(Strings.RegistryOutputPushFailed), e.Message); Log.LogMessage(MessageImportance.Low, "Details: {0}", e); diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index 64ca1939bdab..f168e157ddc7 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -83,7 +83,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); - await ImagePublisher.PublishImageAsync(multiArchImage, sourceImageReference, destinationImageReference, Log, BuildEngine != null, telemetry, cancellationToken) + await ImagePublisher.PublishImageAsync(multiArchImage, sourceImageReference, destinationImageReference, Log, telemetry, cancellationToken) .ConfigureAwait(false); return !Log.HasLoggedErrors; diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index c6e1fba6ce43..df46fd354709 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -120,13 +120,10 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) return !Log.HasLoggedErrors; } - if (BuildEngine != null) - { - (string message, object[] parameters) = SkipPublishing ? - ( Strings.ContainerBuilder_StartBuildingImageForRid, new object[] { Repository, ContainerRuntimeIdentifier, sourceImageReference }) : - ( Strings.ContainerBuilder_StartBuildingImage, new object[] { Repository, String.Join(",", ImageTags), sourceImageReference }); - Log.LogMessage(MessageImportance.High, message, parameters); - } + (string message, object[] parameters) = SkipPublishing ? + ( Strings.ContainerBuilder_StartBuildingImageForRid, new object[] { Repository, ContainerRuntimeIdentifier, sourceImageReference }) : + ( Strings.ContainerBuilder_StartBuildingImage, new object[] { Repository, String.Join(",", ImageTags), sourceImageReference }); + Log.LogMessage(MessageImportance.High, message, parameters); Layer newLayer = Layer.FromDirectory(PublishDirectory, WorkingDirectory, imageBuilder.IsWindows, imageBuilder.ManifestMediaType); imageBuilder.AddLayer(newLayer); @@ -183,7 +180,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) if (!SkipPublishing) { - await ImagePublisher.PublishImageAsync(builtImage, sourceImageReference, destinationImageReference, Log, BuildEngine != null, telemetry, cancellationToken) + await ImagePublisher.PublishImageAsync(builtImage, sourceImageReference, destinationImageReference, Log, telemetry, cancellationToken) .ConfigureAwait(false); } From 63847bab1e80d361f1e8fe28cd5cd2ecc3258a66 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Sat, 8 Feb 2025 12:15:53 +0100 Subject: [PATCH 47/50] register task resources for CreateImageIndex --- .../Tasks/CreateImageIndex.Interface.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs index 6e64ede53469..bb69651add0e 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.Framework; +using Microsoft.NET.Build.Containers.Resources; namespace Microsoft.NET.Build.Containers.Tasks; @@ -85,5 +86,7 @@ public CreateImageIndex() ImageTags = Array.Empty(); GeneratedArchiveOutputPath = string.Empty; GeneratedImageIndex = string.Empty; + + TaskResources = Resource.Manager; } } \ No newline at end of file From 72b4bf04d2a987b1c6c7fdbc331b36e53ab2ada7 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 10 Feb 2025 09:17:24 +0100 Subject: [PATCH 48/50] optimize ParseImages in CreateImageIndex --- .../BuiltImage.cs | 4 +-- .../LocalDaemons/DockerCli.cs | 4 +-- .../Registry/Registry.cs | 2 +- .../Tasks/CreateImageIndex.cs | 30 ++++++++++++------- .../ImageIndexGeneratorTests.cs | 14 --------- 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs index fd14bce11c58..91f47193ce66 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs @@ -16,12 +16,12 @@ internal readonly struct BuiltImage /// /// Gets image digest. /// - internal required string ImageDigest { get; init; } + internal string? ImageDigest { get; init; } /// /// Gets image SHA. /// - internal required string ImageSha { get; init; } + internal string? ImageSha { get; init; } /// /// Gets image manifest. diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index d5b8fb2b3c5f..5bdb7f40c8a4 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -311,7 +311,7 @@ private static async Task WriteDockerImageToStreamAsync( await WriteImageLayers(writer, image, sourceReference, d => $"{d.Substring("sha256:".Length)}/layer.tar", cancellationToken, layerTarballPaths) .ConfigureAwait(false); - string configTarballPath = $"{image.ImageSha}.json"; + string configTarballPath = $"{image.ImageSha!}.json"; await WriteImageConfig(writer, image, configTarballPath, cancellationToken) .ConfigureAwait(false); @@ -492,7 +492,7 @@ private static async Task WriteOciImageToBlobs( await WriteImageLayers(writer, image, sourceReference, d => $"{_blobsPath}/{d.Substring("sha256:".Length)}", cancellationToken) .ConfigureAwait(false); - await WriteImageConfig(writer, image, $"{_blobsPath}/{image.ImageSha}", cancellationToken) + await WriteImageConfig(writer, image, $"{_blobsPath}/{image.ImageSha!}", cancellationToken) .ConfigureAwait(false); await WriteManifestForOciImage(writer, image, cancellationToken) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs index 87671575f34a..1b01ebd5bdc4 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs @@ -595,7 +595,7 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source, cancellationToken.ThrowIfCancellationRequested(); using (MemoryStream stringStream = new MemoryStream(Encoding.UTF8.GetBytes(builtImage.Config))) { - var configDigest = builtImage.ImageDigest; + var configDigest = builtImage.ImageDigest!; _logger.LogInformation(Strings.Registry_ConfigUploadStarted, configDigest); await UploadBlobAsync(destination.Repository, configDigest, stringStream, cancellationToken).ConfigureAwait(false); _logger.LogInformation(Strings.Registry_ConfigUploaded); diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs index f168e157ddc7..25658f612e06 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -108,19 +108,27 @@ private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind) break; } - var manifestV2 = JsonSerializer.Deserialize(manifest); - if (manifestV2 == null) - { - Log.LogError(Strings.InvalidImageManifest); - break; - } - - string imageDigest = manifestV2.Config.digest; - string imageSha = DigestUtils.GetShaFromDigest(imageDigest); - // We don't need layers for remote registry, as the individual images should be pushed already - var layers = destinationKind == DestinationImageReferenceKind.RemoteRegistry ? null : manifestV2.Layers; (string architecture, string os) = GetArchitectureAndOsFromConfig(config); + // We don't need ImageDigest, ImageSha, Layers for remote registry, as the individual images should be pushed already + string? imageDigest = null; + string? imageSha = null; + List? layers = null; + + if (destinationKind == DestinationImageReferenceKind.LocalRegistry) + { + var manifestV2 = JsonSerializer.Deserialize(manifest); + if (manifestV2 == null) + { + Log.LogError(Strings.InvalidImageManifest); + break; + } + + imageDigest = manifestV2.Config.digest; + imageSha = DigestUtils.GetShaFromDigest(imageDigest); + layers = manifestV2.Layers; + } + images[i] = new BuiltImage() { Config = config, diff --git a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs index 6b87a502d6a6..1acc846dcdcd 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageIndexGeneratorTests.cs @@ -31,8 +31,6 @@ public void UnsupportedMediaTypeThrows() new BuiltImage { Config = "", - ImageDigest = "", - ImageSha = "", Manifest = "", ManifestDigest = "", ManifestMediaType = "unsupported" @@ -53,8 +51,6 @@ public void ImagesWithMixedMediaTypes(string supportedMediaType) new BuiltImage { Config = "", - ImageDigest = "", - ImageSha = "", Manifest = "", ManifestDigest = "", ManifestMediaType = supportedMediaType, @@ -62,8 +58,6 @@ public void ImagesWithMixedMediaTypes(string supportedMediaType) new BuiltImage { Config = "", - ImageDigest = "", - ImageSha = "", Manifest = "", ManifestDigest = "", ManifestMediaType = "anotherMediaType" @@ -82,8 +76,6 @@ public void GenerateDockerManifestList() new BuiltImage { Config = "", - ImageDigest = "", - ImageSha = "", Manifest = "123", ManifestDigest = "sha256:digest1", ManifestMediaType = SchemaTypes.DockerManifestV2, @@ -93,8 +85,6 @@ public void GenerateDockerManifestList() new BuiltImage { Config = "", - ImageDigest = "", - ImageSha = "", Manifest = "123", ManifestDigest = "sha256:digest2", ManifestMediaType = SchemaTypes.DockerManifestV2, @@ -116,8 +106,6 @@ public void GenerateOciImageIndex() new BuiltImage { Config = "", - ImageDigest = "", - ImageSha = "", Manifest = "123", ManifestDigest = "sha256:digest1", ManifestMediaType = SchemaTypes.OciManifestV1, @@ -127,8 +115,6 @@ public void GenerateOciImageIndex() new BuiltImage { Config = "", - ImageDigest = "", - ImageSha = "", Manifest = "123", ManifestDigest = "sha256:digest2", ManifestMediaType = SchemaTypes.OciManifestV1, From 0f5e6c80c92e78d028be4a8a172f84d9a4559d2f Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 10 Feb 2025 13:07:00 +0100 Subject: [PATCH 49/50] fix setting annotation image name prefix: set to 'docker.io/' when repostory contains /, and 'docker.io/library/' when it doesn't; add tests --- .../ImageIndexGenerator.cs | 4 +++- .../DockerSupportsArchFact.cs | 19 ++++++++++++++++ .../EndToEndTests.cs | 22 ++++++++++--------- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index 86e8d0a5c0c5..64a00b4236cb 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -92,6 +92,8 @@ internal static string GenerateImageIndex(BuiltImage[] images, string manifestMe 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++) { @@ -103,7 +105,7 @@ internal static string GenerateImageIndexWithAnnotations(string manifestMediaTyp digest = manifestDigest, annotations = new Dictionary { - { "io.containerd.image.name", $"docker.io/library/{repository}:{tag}" }, + { "io.containerd.image.name", $"{containerdImageNamePrefix}{repository}:{tag}" }, { "org.opencontainers.image.ref.name", tag } } }; diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs index ae0e76f3c153..e7744d8882d4 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs @@ -21,3 +21,22 @@ public DockerIsAvailableAndSupportsArchFactAttribute(string arch, bool checkCont } } } + +public class DockerIsAvailableAndSupportsArchTheoryAttribute : TheoryAttribute +{ + public DockerIsAvailableAndSupportsArchTheoryAttribute(string arch, bool checkContainerdStoreAvailability = false) + { + if (!DockerSupportsArchHelper.DaemonIsAvailable) + { + base.Skip = "Skipping test because Docker is not available on this host."; + } + else if (checkContainerdStoreAvailability && !DockerSupportsArchHelper.IsContainerdStoreEnabledForDocker) + { + base.Skip = "Skipping test because Docker daemon is not using containerd as the storage driver."; + } + else if (!DockerSupportsArchHelper.DaemonSupportsArch(arch)) + { + base.Skip = $"Skipping test because Docker daemon does not support {arch}."; + } + } +} diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index ccef1a3b41c4..836022f30f3b 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -721,10 +721,11 @@ public void EndToEnd_SingleArch_NoRid() processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); } - [DockerIsAvailableAndSupportsArchFact("linux/arm64", checkContainerdStoreAvailability: true)] - public void EndToEndMultiArch_LocalRegistry() + [InlineData("endtoendmultiarch-localregisty")] + [InlineData("myteam/endtoendmultiarch-localregisty")] + [DockerIsAvailableAndSupportsArchTheory("linux/arm64", checkContainerdStoreAvailability: true)] + public void EndToEndMultiArch_LocalRegistry(string imageName) { - string imageName = NewImageName(); string tag = "1.0"; string image = $"{imageName}:{tag}"; @@ -759,7 +760,7 @@ public void EndToEndMultiArch_LocalRegistry() "--platform", "linux/amd64", "--name", - $"test-container-{imageName}-x64", + $"test-container-{imageName.Replace('/', '-')}-x64", image) .Execute(); processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -770,7 +771,7 @@ public void EndToEndMultiArch_LocalRegistry() "--platform", "linux/arm64", "--name", - $"test-container-{imageName}-arm64", + $"test-container-{imageName.Replace('/', '-')}-arm64", image) .Execute(); processResultArm64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -904,10 +905,11 @@ private DirectoryInfo CreateNewProject(string template, [CallerMemberName] strin private string GetPublishArtifactsPath(string projectDir, string rid, string configuration = "Debug") => Path.Combine(projectDir, "bin", configuration, ToolsetInfo.CurrentTargetFramework, rid, "publish"); - [DockerIsAvailableAndSupportsArchFact("linux/arm64", checkContainerdStoreAvailability: true)] - public void EndToEndMultiArch_ArchivePublishing() + [InlineData("endtoendmultiarch-archivepublishing")] + [InlineData("myteam/endtoendmultiarch-archivepublishing")] + [DockerIsAvailableAndSupportsArchTheory("linux/arm64", checkContainerdStoreAvailability: true)] + public void EndToEndMultiArch_ArchivePublishing(string imageName) { - string imageName = NewImageName(); string tag = "1.0"; string image = $"{imageName}:{tag}"; string archiveOutput = TestSettings.TestArtifactsDirectory; @@ -953,7 +955,7 @@ public void EndToEndMultiArch_ArchivePublishing() "--platform", "linux/amd64", "--name", - $"test-container-{imageName}-x64", + $"test-container-{imageName.Replace('/', '-')}-x64", image) .Execute(); processResultX64.Should().Pass().And.HaveStdOut("Hello, World!"); @@ -964,7 +966,7 @@ public void EndToEndMultiArch_ArchivePublishing() "--platform", "linux/arm64", "--name", - $"test-container-{imageName}-arm64", + $"test-container-{imageName.Replace('/', '-')}-arm64", image) .Execute(); processResultArm64.Should().Pass().And.HaveStdOut("Hello, World!"); From f09c5c41b50cff4cadb36b73912687ab0fa44bb7 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 10 Feb 2025 13:44:33 +0100 Subject: [PATCH 50/50] move DockerIsAvailableAndSupportsArchTheoryAttribute to its own file; rename DockerIsAvailableAndSupportsArchFactAttribute file --- ...> DockerIsAvailableAndSupportsArchFact.cs} | 19 --------------- .../DockerIsAvailableAndSupportsArchTheory.cs | 23 +++++++++++++++++++ 2 files changed, 23 insertions(+), 19 deletions(-) rename src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/{DockerSupportsArchFact.cs => DockerIsAvailableAndSupportsArchFact.cs} (55%) create mode 100644 src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerIsAvailableAndSupportsArchTheory.cs diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerIsAvailableAndSupportsArchFact.cs similarity index 55% rename from src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs rename to src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerIsAvailableAndSupportsArchFact.cs index e7744d8882d4..ae0e76f3c153 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerSupportsArchFact.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerIsAvailableAndSupportsArchFact.cs @@ -21,22 +21,3 @@ public DockerIsAvailableAndSupportsArchFactAttribute(string arch, bool checkCont } } } - -public class DockerIsAvailableAndSupportsArchTheoryAttribute : TheoryAttribute -{ - public DockerIsAvailableAndSupportsArchTheoryAttribute(string arch, bool checkContainerdStoreAvailability = false) - { - if (!DockerSupportsArchHelper.DaemonIsAvailable) - { - base.Skip = "Skipping test because Docker is not available on this host."; - } - else if (checkContainerdStoreAvailability && !DockerSupportsArchHelper.IsContainerdStoreEnabledForDocker) - { - base.Skip = "Skipping test because Docker daemon is not using containerd as the storage driver."; - } - else if (!DockerSupportsArchHelper.DaemonSupportsArch(arch)) - { - base.Skip = $"Skipping test because Docker daemon does not support {arch}."; - } - } -} diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerIsAvailableAndSupportsArchTheory.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerIsAvailableAndSupportsArchTheory.cs new file mode 100644 index 000000000000..382e57604fab --- /dev/null +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerIsAvailableAndSupportsArchTheory.cs @@ -0,0 +1,23 @@ +// 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 DockerIsAvailableAndSupportsArchTheoryAttribute : TheoryAttribute +{ + public DockerIsAvailableAndSupportsArchTheoryAttribute(string arch, bool checkContainerdStoreAvailability = false) + { + if (!DockerSupportsArchHelper.DaemonIsAvailable) + { + base.Skip = "Skipping test because Docker is not available on this host."; + } + else if (checkContainerdStoreAvailability && !DockerSupportsArchHelper.IsContainerdStoreEnabledForDocker) + { + base.Skip = "Skipping test because Docker daemon is not using containerd as the storage driver."; + } + else if (!DockerSupportsArchHelper.DaemonSupportsArch(arch)) + { + base.Skip = $"Skipping test because Docker daemon does not support {arch}."; + } + } +} \ No newline at end of file