From 35a7ce2cfd80f1728c2bcd8da62c5556b74f7950 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:51:47 +0000 Subject: [PATCH 1/7] Initial plan From 1fbc0ef497ed41b520f6000f56a3b8e79283af3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:01:02 +0000 Subject: [PATCH 2/7] Add support for zstd-compressed layer media types Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- .../Microsoft.NET.Build.Containers/ContentStore.cs | 3 +++ .../Microsoft.NET.Build.Containers/Registry/Registry.cs | 6 ++++++ .../Microsoft.NET.Build.Containers/Registry/SchemaTypes.cs | 3 +++ 3 files changed, 12 insertions(+) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs b/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs index 2fb16be3ec5d..8fa17b0f8528 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs @@ -47,6 +47,9 @@ public static string PathForDescriptor(Descriptor descriptor) or "application/vnd.oci.image.layer.v1.tar+gzip" or "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip" => ".tar.gz", + "application/vnd.docker.image.rootfs.diff.tar.zstd" + or "application/vnd.oci.image.layer.v1.tar+zstd" + => ".tar.zst", "application/vnd.docker.image.rootfs.diff.tar" or "application/vnd.oci.image.layer.v1.tar" => ".tar", diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs index 3472ba1c53b2..90606d8565b4 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs @@ -414,6 +414,12 @@ public async Task DownloadBlobAsync(string repository, Descriptor descri // Assume file is up to date and just return it return localPath; } + + // Log when we encounter zstd-compressed layers + if (descriptor.MediaType == SchemaTypes.DockerLayerZstd || descriptor.MediaType == SchemaTypes.OciLayerZstdV1) + { + _logger.LogTrace("Downloading zstd-compressed layer (mediaType: {0}). Layer will be passed through unmodified.", descriptor.MediaType); + } string tempTarballPath = ContentStore.GetTempFile(); diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/SchemaTypes.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/SchemaTypes.cs index f7ae91a19e8b..d274cb16f405 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/SchemaTypes.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/SchemaTypes.cs @@ -16,4 +16,7 @@ internal class SchemaTypes internal const string DockerLayerGzip = "application/vnd.docker.image.rootfs.diff.tar.gzip"; internal const string OciLayerGzipV1 = "application/vnd.oci.image.layer.v1.tar+gzip"; + + internal const string DockerLayerZstd = "application/vnd.docker.image.rootfs.diff.tar.zstd"; + internal const string OciLayerZstdV1 = "application/vnd.oci.image.layer.v1.tar+zstd"; } From 11fb5afc9af328b35fdb03e7c6b81f56355d0e0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:03:06 +0000 Subject: [PATCH 3/7] Add end-to-end test for zstd-compressed base images Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- .../EndToEndTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index a9c05a3b247b..673028bf1f7b 100644 --- a/test/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -1542,4 +1542,50 @@ public void EnforcesOciSchemaForMultiRIDTarballOutput() // Cleanup newProjectDir.Delete(true); } + + /// + /// Tests that zstd-compressed layers from base images are handled correctly. + /// Uses a Docker hardened image (dhi.io/dotnet:8-alpine3.22) which contains zstd-compressed layers. + /// + /// + /// TODO: In the future, this image should be mirrored to the test ACR and the manifest updated + /// to use the ACR reference for deterministic test runs. + /// + [DockerAvailableFact(Skip = "https://github.com/dotnet/sdk/issues/49502")] + public async Task EndToEnd_ZstdCompressedBaseImage() + { + ILogger logger = _loggerFactory.CreateLogger(nameof(EndToEnd_ZstdCompressedBaseImage)); + string publishDirectory = BuildLocalApp(tfm: ToolsetInfo.CurrentTargetFramework); + + // Build the image using a Docker hardened base image that contains zstd-compressed layers + // Note: dhi.io/dotnet:8-alpine3.22 uses zstd compression for its layers + Registry registry = new("dhi.io", logger, RegistryMode.Pull); + + ImageBuilder imageBuilder = await registry.GetImageManifestAsync( + "dotnet", + "8-alpine3.22", + "linux-x64", + ToolsetUtils.RidGraphManifestPicker, + cancellationToken: default).ConfigureAwait(false); + Assert.NotNull(imageBuilder); + + Layer l = Layer.FromDirectory(publishDirectory, "/app", false, imageBuilder.ManifestMediaType); + + imageBuilder.AddLayer(l); + + imageBuilder.SetEntrypointAndCmd(new[] { "/app/MinimalTestApp" }, Array.Empty()); + + BuiltImage builtImage = imageBuilder.Build(); + + // Load the image into the local Docker daemon + var sourceReference = new SourceImageReference(registry, "dotnet", "8-alpine3.22", null); + var destinationReference = new DestinationImageReference(registry, NewImageName(), new[] { "latest" }); + + await new DockerCli(_loggerFactory).LoadAsync(builtImage, sourceReference, destinationReference, default).ConfigureAwait(false); + + // Run the image to verify it works correctly + ContainerCli.RunCommand(_testOutput, "--rm", "--tty", $"{NewImageName()}:latest") + .Execute() + .Should().Pass(); + } } From 0ce0dde5598b3940708c6fb9197a9a9412b1d68f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:05:33 +0000 Subject: [PATCH 4/7] Run dotnet format to fix code formatting Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- .../AuthHandshakeMessageHandler.cs | 3 ++- .../BuiltImage.cs | 2 +- .../ContainerHelpers.cs | 1 + ...UnableToDownloadFromRepositoryException.cs | 2 +- .../ImageBuilder.cs | 2 +- .../ImageConfig.cs | 3 ++- .../ImageIndexGenerator.cs | 8 +++---- .../Microsoft.NET.Build.Containers/Layer.cs | 2 +- .../LocalDaemons/ArchiveFileRegistry.cs | 8 +++---- .../LocalDaemons/DockerCli.cs | 16 ++++++------- .../MultiArchImage.cs | 2 +- .../Registry/Registry.cs | 24 +++++++++---------- .../Tasks/ComputeDotnetBaseImageAndTag.cs | 5 ++-- .../Tasks/CreateImageIndex.Interface.cs | 2 +- .../Tasks/CreateImageIndex.cs | 6 ++--- .../Telemetry.cs | 2 +- 16 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs b/src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs index 46761e94df7c..894f53865823 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs @@ -283,7 +283,8 @@ internal static (string credU, string credP)? GetDockerCredentialsFromEnvironmen if (bearerAuthInfo.Scope is not null) { parameters["scope"] = bearerAuthInfo.Scope; - }; + } + ; HttpRequestMessage postMessage = new(HttpMethod.Post, uri) { Content = new FormUrlEncodedContent(parameters) diff --git a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs index 91f47193ce66..94a79dfc0c06 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/BuiltImage.cs @@ -26,7 +26,7 @@ internal readonly struct BuiltImage /// /// Gets image manifest. /// - internal required string Manifest { get; init; } + internal required string Manifest { get; init; } /// /// Gets manifest digest. diff --git a/src/Containers/Microsoft.NET.Build.Containers/ContainerHelpers.cs b/src/Containers/Microsoft.NET.Build.Containers/ContainerHelpers.cs index 7f114caaba21..bd25edb1e3dc 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ContainerHelpers.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ContainerHelpers.cs @@ -14,6 +14,7 @@ using Microsoft.NET.Build.Containers.Resources; namespace Microsoft.NET.Build.Containers; + public static class ContainerHelpers { internal const string HostObjectUser = "DOTNET_CONTAINER_REGISTRY_UNAME"; diff --git a/src/Containers/Microsoft.NET.Build.Containers/Exceptions/UnableToDownloadFromRepositoryException.cs b/src/Containers/Microsoft.NET.Build.Containers/Exceptions/UnableToDownloadFromRepositoryException.cs index 30718be88ed6..e427377f4bfa 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Exceptions/UnableToDownloadFromRepositoryException.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Exceptions/UnableToDownloadFromRepositoryException.cs @@ -6,7 +6,7 @@ namespace Microsoft.NET.Build.Containers; internal sealed class UnableToDownloadFromRepositoryException : Exception { public UnableToDownloadFromRepositoryException(string repository) - : base($"The download of the image from repository { repository } has failed.") + : base($"The download of the image from repository {repository} has failed.") { } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs index e97a8151a897..f2d9dddb4c02 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; -using System.Text.RegularExpressions; using System.Text.Json; +using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using Microsoft.NET.Build.Containers.Resources; diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageConfig.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageConfig.cs index d16563c052db..db72a190a104 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageConfig.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageConfig.cs @@ -217,7 +217,8 @@ internal void AddLayer(Layer l) _rootFsLayers.Add(l.Descriptor.UncompressedDigest!); } - internal void SetUser(string user, bool isUserInteraction = false) { + internal void SetUser(string user, bool isUserInteraction = false) + { // we don't let automatic/inferred user settings overwrite an explicit user request if (_userHasBeenExplicitlySet && !isUserInteraction) { diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs index 64a00b4236cb..e4a621bc5023 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageIndexGenerator.cs @@ -64,7 +64,7 @@ internal static string GenerateImageIndex(BuiltImage[] images, string manifestMe // Here we are using ManifestListV2 struct, but we could use ImageIndexV1 struct as well. // We are filling the same fields, so we can use the same struct. var manifests = new PlatformSpecificManifest[images.Length]; - + for (int i = 0; i < images.Length; i++) { manifests[i] = new PlatformSpecificManifest @@ -93,7 +93,7 @@ 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,10 +103,10 @@ internal static string GenerateImageIndexWithAnnotations(string manifestMediaTyp mediaType = manifestMediaType, size = manifestSize, digest = manifestDigest, - annotations = new Dictionary + annotations = new Dictionary { { "io.containerd.image.name", $"{containerdImageNamePrefix}{repository}:{tag}" }, - { "org.opencontainers.image.ref.name", tag } + { "org.opencontainers.image.ref.name", tag } } }; } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Layer.cs b/src/Containers/Microsoft.NET.Build.Containers/Layer.cs index 1cb57628f390..c22d31138424 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Layer.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Layer.cs @@ -201,7 +201,7 @@ static UnixFileMode DetermineFileMode(FileSystemInfo file) string layerMediaType = manifestMediaType switch { - // TODO: configurable? gzip always? + // TODO: configurable? gzip always? SchemaTypes.DockerManifestV2 => SchemaTypes.DockerLayerGzip, SchemaTypes.OciManifestV1 => SchemaTypes.OciLayerGzipV1, _ => throw new ArgumentException(Resource.FormatString(nameof(Strings.UnrecognizedMediaType), manifestMediaType)) diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs index d127635ba584..fe65b4e9efb0 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/ArchiveFileRegistry.cs @@ -14,7 +14,7 @@ public ArchiveFileRegistry(string archiveOutputPath) ArchiveOutputPath = archiveOutputPath; } - internal async Task LoadAsync(T image, SourceImageReference sourceReference, + internal async Task LoadAsync(T image, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken, Func writeStreamFunc) { @@ -25,7 +25,7 @@ internal async Task LoadAsync(T image, SourceImageReference sourceReference, // if doesn't end with a file extension, assume it's a directory if (!Path.HasExtension(fullPath)) { - fullPath += Path.DirectorySeparatorChar; + fullPath += Path.DirectorySeparatorChar; } // pointing to a directory? -> append default name @@ -50,13 +50,13 @@ internal async Task LoadAsync(T image, SourceImageReference sourceReference, public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReference, DestinationImageReference destinationReference, - CancellationToken cancellationToken) + CancellationToken cancellationToken) => await LoadAsync(image, sourceReference, destinationReference, cancellationToken, DockerCli.WriteImageToStreamAsync); public async Task LoadAsync(MultiArchImage multiArchImage, SourceImageReference sourceReference, DestinationImageReference destinationReference, - CancellationToken cancellationToken) + CancellationToken cancellationToken) => await LoadAsync(multiArchImage, sourceReference, destinationReference, cancellationToken, DockerCli.WriteMultiArchOciImageToStreamAsync); diff --git a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs index ebc8064f8e41..188cfe051b22 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/LocalDaemons/DockerCli.cs @@ -130,13 +130,13 @@ await writeStreamFunc(image, sourceReference, destinationReference, loadProcess. } } - public async Task LoadAsync(BuiltImage image, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) + 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(MultiArchImage multiArchImage, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) + 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) { bool commandPathWasUnknown = _command is null; // avoid running the version command twice. @@ -539,7 +539,7 @@ private static async Task WriteIndexJsonForMultiArchOciImage( var manifestListDigest = DigestUtils.GetDigest(multiArchImage.ImageIndex); var manifestListSha = DigestUtils.GetShaFromDigest(manifestListDigest); var manifestListPath = $"{_blobsPath}/{manifestListSha}"; - + using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(multiArchImage.ImageIndex))) { PaxTarEntry indexEntry = new(TarEntryType.RegularFile, manifestListPath) @@ -553,10 +553,10 @@ private static async Task WriteIndexJsonForMultiArchOciImage( cancellationToken.ThrowIfCancellationRequested(); string indexJson = ImageIndexGenerator.GenerateImageIndexWithAnnotations( - multiArchImage.ImageIndexMediaType, - manifestListDigest, - multiArchImage.ImageIndex.Length, - destinationReference.Repository, + multiArchImage.ImageIndexMediaType, + manifestListDigest, + multiArchImage.ImageIndex.Length, + destinationReference.Repository, destinationReference.Tags); using (MemoryStream indexStream = new(Encoding.UTF8.GetBytes(indexJson))) diff --git a/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs b/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs index b24fbf65e87f..bc46b7ca54c6 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/MultiArchImage.cs @@ -13,4 +13,4 @@ internal readonly struct MultiArchImage 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/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs index 90606d8565b4..b406c45733b8 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs @@ -1,12 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using NuGet.Packaging; using System.Diagnostics; using System.Net.Http.Json; using System.Text.Json.Nodes; using Microsoft.Extensions.Logging; using Microsoft.NET.Build.Containers.Resources; +using NuGet.Packaging; using NuGet.RuntimeModel; namespace Microsoft.NET.Build.Containers; @@ -301,7 +301,7 @@ private static IReadOnlyDictionary GetManif // TODO: we _may_ need OS-specific version parsing. Need to do more research on what the field looks like across more manifest lists. var versionPart = platform.version?.Split('.') switch { - [var major, ..] => major, + [var major, ..] => major, _ => null }; var platformPart = platform.architecture switch @@ -408,7 +408,7 @@ public async Task DownloadBlobAsync(string repository, Descriptor descri { cancellationToken.ThrowIfCancellationRequested(); string localPath = ContentStore.PathForDescriptor(descriptor); - + if (File.Exists(localPath)) { // Assume file is up to date and just return it @@ -420,9 +420,9 @@ public async Task DownloadBlobAsync(string repository, Descriptor descri { _logger.LogTrace("Downloading zstd-compressed layer (mediaType: {0}). Layer will be passed through unmodified.", descriptor.MediaType); } - + string tempTarballPath = ContentStore.GetTempFile(); - + int retryCount = 0; while (retryCount < MaxDownloadRetries) { @@ -430,12 +430,12 @@ public async Task DownloadBlobAsync(string repository, Descriptor descri { // No local copy, so download one using Stream responseStream = await _registryAPI.Blob.GetStreamAsync(repository, descriptor.Digest, cancellationToken).ConfigureAwait(false); - + using (FileStream fs = File.Create(tempTarballPath)) { await responseStream.CopyToAsync(fs, cancellationToken).ConfigureAwait(false); } - + // Break the loop if successful break; } @@ -446,16 +446,16 @@ public async Task DownloadBlobAsync(string repository, Descriptor descri { throw new UnableToDownloadFromRepositoryException(repository); } - + _logger.LogTrace("Download attempt {0}/{1} for repository '{2}' failed. Error: {3}", retryCount, MaxDownloadRetries, repository, ex.ToString()); - + // Wait before retrying await Task.Delay(_retryDelayProvider(), cancellationToken).ConfigureAwait(false); } } - + File.Move(tempTarballPath, localPath, overwrite: true); - + return localPath; } @@ -572,7 +572,7 @@ public async Task PushManifestListAsync( _logger.LogInformation(Strings.Registry_TagUploadStarted, tag, RegistryName); await _registryAPI.Manifest.PutAsync(destinationImageReference.Repository, tag, multiArchImage.ImageIndex, multiArchImage.ImageIndexMediaType, cancellationToken).ConfigureAwait(false); _logger.LogInformation(Strings.Registry_TagUploaded, tag, RegistryName); - } + } } public Task PushAsync(BuiltImage builtImage, SourceImageReference source, DestinationImageReference destination, CancellationToken cancellationToken) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs index 92cd3f5a5c85..9f0c2124b75d 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs @@ -130,7 +130,7 @@ private bool TargetRuntimeIdentiriersAreValid() { if (muslRidsCount == TargetRuntimeIdentifiers.Length) { - IsMuslRid = true; + IsMuslRid = true; } else { @@ -311,7 +311,8 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository, default: Log.LogError(Resources.Strings.InvalidSdkPrereleaseVersion, channel); return null; - }; + } + ; } private bool UserImageIsMicrosoftBaseImage => UserBaseImage?.StartsWith("mcr.microsoft.com/") ?? false; 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 538240841c6b..d74f91d4774e 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.Interface.cs @@ -95,4 +95,4 @@ public CreateImageIndex() TaskResources = Resource.Manager; } -} \ 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 f103fe8c1246..e2305e011d6d 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateImageIndex.cs @@ -84,7 +84,7 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) var telemetry = new Telemetry(sourceImageReference, destinationImageReference, Log); await ImagePublisher.PublishImageAsync(multiArchImage, sourceImageReference, destinationImageReference, Log, telemetry, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(false); return !Log.HasLoggedErrors; } @@ -127,7 +127,7 @@ private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind) imageDigest = manifestV2.Config.digest; imageSha = DigestUtils.GetShaFromDigest(imageDigest); layers = manifestV2.Layers; - } + } images[i] = new BuiltImage() { @@ -159,7 +159,7 @@ private BuiltImage[] ParseImages(DestinationImageReferenceKind destinationKind) { Log.LogError(Strings.ImageConfigMissingArchitecture); return (string.Empty, string.Empty); - } + } var os = configJson["os"]?.ToString(); if (os is null) { diff --git a/src/Containers/Microsoft.NET.Build.Containers/Telemetry.cs b/src/Containers/Microsoft.NET.Build.Containers/Telemetry.cs index 009b93d7717c..52630be34cf1 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Telemetry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Telemetry.cs @@ -113,4 +113,4 @@ public void LogLocalLoadError() props.Add("error", "local_load"); Log.LogTelemetry("sdk/container/publish/error", props); } -} \ No newline at end of file +} From 1554596e7483e0455cbe7ce994163afb1cd04ff0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:06:24 +0000 Subject: [PATCH 5/7] Add format commit to .git-blame-ignore-revs Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- .git-blame-ignore-revs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000000..bc6699191d6c --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# This file contains a list of commits that should be ignored by git blame +# Each commit that is purely formatting changes should be added here +# Usage: git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Run dotnet format to fix code formatting +0ce0dde866fd5a9eab5f60e2e7f07f9f87e8c8c5 From f65fa27d2e30a82aff0a943f0877f2c2230733a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:08:05 +0000 Subject: [PATCH 6/7] Use SchemaTypes constants instead of hardcoded strings in ContentStore Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- .../Microsoft.NET.Build.Containers/ContentStore.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs b/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs index 8fa17b0f8528..6a67c65dea33 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs @@ -43,12 +43,12 @@ public static string PathForDescriptor(Descriptor descriptor) string extension = descriptor.MediaType switch { - "application/vnd.docker.image.rootfs.diff.tar.gzip" - or "application/vnd.oci.image.layer.v1.tar+gzip" + SchemaTypes.DockerLayerGzip + or SchemaTypes.OciLayerGzipV1 or "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip" => ".tar.gz", - "application/vnd.docker.image.rootfs.diff.tar.zstd" - or "application/vnd.oci.image.layer.v1.tar+zstd" + SchemaTypes.DockerLayerZstd + or SchemaTypes.OciLayerZstdV1 => ".tar.zst", "application/vnd.docker.image.rootfs.diff.tar" or "application/vnd.oci.image.layer.v1.tar" From 303a982ea6c6edfd68e8701d96876933408333e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:13:08 +0000 Subject: [PATCH 7/7] Remove Skip attribute from EndToEnd_ZstdCompressedBaseImage test Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com> --- .../EndToEndTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index 673028bf1f7b..ab939b334d7b 100644 --- a/test/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -1551,7 +1551,7 @@ public void EnforcesOciSchemaForMultiRIDTarballOutput() /// TODO: In the future, this image should be mirrored to the test ACR and the manifest updated /// to use the ACR reference for deterministic test runs. /// - [DockerAvailableFact(Skip = "https://github.com/dotnet/sdk/issues/49502")] + [DockerAvailableFact] public async Task EndToEnd_ZstdCompressedBaseImage() { ILogger logger = _loggerFactory.CreateLogger(nameof(EndToEnd_ZstdCompressedBaseImage));