Skip to content

Commit 861789d

Browse files
authored
Skip redundant container uploads for existing manifests (#55690)
1 parent 14d35cd commit 861789d

25 files changed

Lines changed: 282 additions & 35 deletions

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public static async Task PublishImageAsync(
1212
BuiltImage singleArchImage,
1313
SourceImageReference sourceImageReference,
1414
DestinationImageReference destinationImageReference,
15+
bool noCache,
1516
Microsoft.Build.Utilities.TaskLoggingHelper Log,
1617
Telemetry telemetry,
1718
CancellationToken cancellationToken)
@@ -37,7 +38,12 @@ await PushToRemoteRegistryAsync(
3738
destinationImageReference,
3839
Log,
3940
cancellationToken,
40-
destinationImageReference.RemoteRegistry!.PushAsync,
41+
(image, source, destination, token) => destinationImageReference.RemoteRegistry!.PushAsync(
42+
image,
43+
source,
44+
destination,
45+
noCache,
46+
token),
4147
Strings.ContainerBuilder_ImageUploadedToRegistry).ConfigureAwait(false);
4248
break;
4349
default:

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateDigestLabel.get -> b
257257
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateDigestLabel.set -> void
258258
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.SkipPublishing.get -> bool
259259
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.SkipPublishing.set -> void
260+
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.NoCache.get -> bool
261+
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.NoCache.set -> void
260262
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerNames.get -> Microsoft.Build.Framework.ITaskItem![]!
261263
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GeneratedContainerNames.set -> void
262264
Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ImageFormat.get -> string?

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ internal DefaultManifestOperations(Uri baseUri, string registryName, HttpClient
2424
_registryName = registryName;
2525
}
2626

27+
public async Task<bool> ExistsAsync(string repositoryName, string reference, CancellationToken cancellationToken)
28+
{
29+
cancellationToken.ThrowIfCancellationRequested();
30+
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Head, new Uri(_baseUri, $"/v2/{repositoryName}/manifests/{reference}")).AcceptManifestFormats();
31+
using HttpResponseMessage response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
32+
return response.StatusCode switch
33+
{
34+
HttpStatusCode.OK => true,
35+
_ when (int)response.StatusCode >= 500 => await LogAndThrowContainerHttpException<bool>(response, cancellationToken).ConfigureAwait(false),
36+
_ => false,
37+
};
38+
}
39+
2740
public async Task<HttpResponseMessage> GetAsync(string repositoryName, string reference, CancellationToken cancellationToken)
2841
{
2942
cancellationToken.ThrowIfCancellationRequested();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace Microsoft.NET.Build.Containers;
1111
/// </remarks>
1212
internal interface IManifestOperations
1313
{
14+
public Task<bool> ExistsAsync(string repositoryName, string reference, CancellationToken cancellationToken);
15+
1416
public Task<HttpResponseMessage> GetAsync(string repositoryName, string reference, CancellationToken cancellationToken);
1517

1618
public Task PutAsync(string repositoryName, string reference, string manifestListJson, string mediaType, CancellationToken cancellationToken);

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

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -595,13 +595,24 @@ public async Task PushManifestListAsync(
595595
}
596596

597597
public Task PushAsync(BuiltImage builtImage, SourceImageReference source, DestinationImageReference destination, CancellationToken cancellationToken)
598-
=> PushAsync(builtImage, source, destination, pushTags: true, cancellationToken);
598+
=> PushAsync(builtImage, source, destination, noCache: false, cancellationToken);
599599

600-
private async Task PushAsync(BuiltImage builtImage, SourceImageReference source, DestinationImageReference destination, bool pushTags, CancellationToken cancellationToken)
600+
public Task PushAsync(BuiltImage builtImage, SourceImageReference source, DestinationImageReference destination, bool noCache, CancellationToken cancellationToken)
601+
=> PushAsync(builtImage, source, destination, pushTags: true, noCache, cancellationToken);
602+
603+
private async Task PushAsync(BuiltImage builtImage, SourceImageReference source, DestinationImageReference destination, bool pushTags, bool noCache, CancellationToken cancellationToken)
601604
{
602605
cancellationToken.ThrowIfCancellationRequested();
603606
Registry destinationRegistry = destination.RemoteRegistry!;
604607

608+
bool manifestExists = !noCache &&
609+
await _registryAPI.Manifest.ExistsAsync(destination.Repository, builtImage.ManifestDigest, cancellationToken).ConfigureAwait(false);
610+
611+
if (manifestExists)
612+
{
613+
_logger.LogInformation(Strings.Registry_ManifestExists, builtImage.ManifestDigest, destination.Repository);
614+
}
615+
605616
Func<Descriptor, Task> uploadLayerFunc = async (descriptor) =>
606617
{
607618
cancellationToken.ThrowIfCancellationRequested();
@@ -634,25 +645,28 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source,
634645
}
635646
};
636647

637-
if (SupportsParallelUploads)
648+
if (!manifestExists)
638649
{
639-
await Task.WhenAll(builtImage.LayerDescriptors.Select(descriptor => uploadLayerFunc(descriptor))).ConfigureAwait(false);
640-
}
641-
else
642-
{
643-
foreach (var descriptor in builtImage.LayerDescriptors)
650+
if (SupportsParallelUploads)
644651
{
645-
await uploadLayerFunc(descriptor).ConfigureAwait(false);
652+
await Task.WhenAll(builtImage.LayerDescriptors.Select(descriptor => uploadLayerFunc(descriptor))).ConfigureAwait(false);
653+
}
654+
else
655+
{
656+
foreach (var descriptor in builtImage.LayerDescriptors)
657+
{
658+
await uploadLayerFunc(descriptor).ConfigureAwait(false);
659+
}
646660
}
647-
}
648661

649-
cancellationToken.ThrowIfCancellationRequested();
650-
using (MemoryStream stringStream = new(Encoding.UTF8.GetBytes(builtImage.Config)))
651-
{
652-
var configDigest = builtImage.ImageDigest!;
653-
_logger.LogInformation(Strings.Registry_ConfigUploadStarted, configDigest);
654-
await UploadBlobAsync(destination.Repository, configDigest, stringStream, cancellationToken).ConfigureAwait(false);
655-
_logger.LogInformation(Strings.Registry_ConfigUploaded);
662+
cancellationToken.ThrowIfCancellationRequested();
663+
using (MemoryStream stringStream = new(Encoding.UTF8.GetBytes(builtImage.Config)))
664+
{
665+
var configDigest = builtImage.ImageDigest!;
666+
_logger.LogInformation(Strings.Registry_ConfigUploadStarted, configDigest);
667+
await UploadBlobAsync(destination.Repository, configDigest, stringStream, cancellationToken).ConfigureAwait(false);
668+
_logger.LogInformation(Strings.Registry_ConfigUploaded);
669+
}
656670
}
657671

658672
// Tags can refer to an image manifest or an image manifest list.
@@ -668,7 +682,7 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source,
668682
_logger.LogInformation(Strings.Registry_TagUploaded, tag, RegistryName);
669683
}
670684
}
671-
else
685+
else if (!manifestExists)
672686
{
673687
_logger.LogInformation(Strings.Registry_ManifestUploadStarted, RegistryName, builtImage.ManifestDigest);
674688
await _registryAPI.Manifest.PutAsync(destination.Repository, builtImage.ManifestDigest, builtImage.Manifest, builtImage.ManifestMediaType, cancellationToken).ConfigureAwait(false);

src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Containers/Microsoft.NET.Build.Containers/Resources/Strings.resx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,10 @@
453453
<value>Uploaded manifest to '{0}'.</value>
454454
<comment>{0} is the registry name</comment>
455455
</data>
456+
<data name="Registry_ManifestExists" xml:space="preserve">
457+
<value>Manifest '{0}' already exists in repository '{1}'. Skipping layer and configuration uploads.</value>
458+
<comment>{0} is the manifest digest, {1} is the repository name</comment>
459+
</data>
456460
<data name="Registry_ManifestUploadStarted" xml:space="preserve">
457461
<value>Uploading manifest to registry '{0}' as blob '{1}'.</value>
458462
<comment>{0} is the registry name</comment>

src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.cs.xlf

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.de.xlf

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Containers/Microsoft.NET.Build.Containers/Resources/xlf/Strings.es.xlf

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)