6
6
using NuGet . RuntimeModel ;
7
7
using System . Diagnostics ;
8
8
using System . Net . Http . Json ;
9
+ using System . Text . Json ;
9
10
using System . Text . Json . Nodes ;
10
11
using System . Text . RegularExpressions ;
11
12
12
13
namespace Microsoft . NET . Build . Containers ;
13
14
14
- internal interface IManifestPicker {
15
+ internal interface IManifestPicker
16
+ {
15
17
public PlatformSpecificManifest ? PickBestManifestForRid ( IReadOnlyDictionary < string , PlatformSpecificManifest > manifestList , string runtimeIdentifier ) ;
16
18
}
17
19
@@ -26,7 +28,8 @@ public RidGraphManifestPicker(string runtimeIdentifierGraphPath)
26
28
public PlatformSpecificManifest ? PickBestManifestForRid ( IReadOnlyDictionary < string , PlatformSpecificManifest > ridManifestDict , string runtimeIdentifier )
27
29
{
28
30
var bestManifestRid = GetBestMatchingRid ( _runtimeGraph , runtimeIdentifier , ridManifestDict . Keys ) ;
29
- if ( bestManifestRid is null ) {
31
+ if ( bestManifestRid is null )
32
+ {
30
33
return null ;
31
34
}
32
35
return ridManifestDict [ bestManifestRid ] ;
@@ -132,7 +135,8 @@ private static string DeriveRegistryName(Uri baseUri)
132
135
/// <remarks>
133
136
/// Google Artifact Registry locations (one for each availability zone) are of the form "ZONE-docker.pkg.dev".
134
137
/// </remarks>
135
- public bool IsGoogleArtifactRegistry {
138
+ public bool IsGoogleArtifactRegistry
139
+ {
136
140
get => RegistryName . EndsWith ( "-docker.pkg.dev" , StringComparison . Ordinal ) ;
137
141
}
138
142
@@ -151,7 +155,7 @@ public async Task<ImageBuilder> GetImageManifestAsync(string repositoryName, str
151
155
{
152
156
SchemaTypes . DockerManifestV2 or SchemaTypes . OciManifestV1 => await ReadSingleImageAsync (
153
157
repositoryName ,
154
- await initialManifestResponse . Content . ReadFromJsonAsync < ManifestV2 > ( cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ,
158
+ await ReadManifest ( ) . ConfigureAwait ( false ) ,
155
159
cancellationToken ) . ConfigureAwait ( false ) ,
156
160
SchemaTypes . DockerManifestListV2 => await PickBestImageFromManifestListAsync (
157
161
repositoryName ,
@@ -167,6 +171,17 @@ await initialManifestResponse.Content.ReadFromJsonAsync<ManifestListV2>(cancella
167
171
BaseUri ,
168
172
unknownMediaType ) )
169
173
} ;
174
+
175
+ async Task < ManifestV2 > ReadManifest ( )
176
+ {
177
+ initialManifestResponse . Headers . TryGetValues ( "Docker-Content-Digest" , out var knownDigest ) ;
178
+ var manifest = ( await initialManifestResponse . Content . ReadFromJsonAsync < ManifestV2 > ( cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ) ! ;
179
+ if ( knownDigest ? . FirstOrDefault ( ) is string knownDigestValue )
180
+ {
181
+ manifest . KnownDigest = knownDigestValue ;
182
+ }
183
+ return manifest ;
184
+ }
170
185
}
171
186
172
187
internal async Task < ManifestListV2 ? > GetManifestListAsync ( string repositoryName , string reference , CancellationToken cancellationToken )
@@ -193,11 +208,12 @@ private async Task<ImageBuilder> ReadSingleImageAsync(string repositoryName, Man
193
208
return new ImageBuilder ( manifest , new ImageConfig ( configDoc ) , _logger ) ;
194
209
}
195
210
196
-
211
+
197
212
private static IReadOnlyDictionary < string , PlatformSpecificManifest > GetManifestsByRid ( ManifestListV2 manifestList )
198
213
{
199
214
var ridDict = new Dictionary < string , PlatformSpecificManifest > ( ) ;
200
- foreach ( var manifest in manifestList . manifests ) {
215
+ foreach ( var manifest in manifestList . manifests )
216
+ {
201
217
if ( CreateRidForPlatform ( manifest . platform ) is { } rid )
202
218
{
203
219
ridDict . TryAdd ( rid , manifest ) ;
@@ -206,7 +222,7 @@ private static IReadOnlyDictionary<string, PlatformSpecificManifest> GetManifest
206
222
207
223
return ridDict ;
208
224
}
209
-
225
+
210
226
private static string ? CreateRidForPlatform ( PlatformInformation platform )
211
227
{
212
228
// we only support linux and windows containers explicitly, so anything else we should skip past.
@@ -220,7 +236,7 @@ private static IReadOnlyDictionary<string, PlatformSpecificManifest> GetManifest
220
236
// TODO: we _may_ need OS-specific version parsing. Need to do more research on what the field looks like across more manifest lists.
221
237
var versionPart = platform . version ? . Split ( '.' ) switch
222
238
{
223
- [ var major , .. ] => major ,
239
+ [ var major , ..] => major ,
224
240
_ => null
225
241
} ;
226
242
var platformPart = platform . architecture switch
@@ -254,12 +270,15 @@ private async Task<ImageBuilder> PickBestImageFromManifestListAsync(
254
270
using HttpResponseMessage manifestResponse = await _registryAPI . Manifest . GetAsync ( repositoryName , matchingManifest . digest , cancellationToken ) . ConfigureAwait ( false ) ;
255
271
256
272
cancellationToken . ThrowIfCancellationRequested ( ) ;
257
-
273
+ var manifest = await manifestResponse . Content . ReadFromJsonAsync < ManifestV2 > ( cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ;
274
+ if ( manifest is null ) throw new BaseImageNotFoundException ( runtimeIdentifier , repositoryName , reference , ridManifestDict . Keys ) ;
275
+ manifest . KnownDigest = matchingManifest . digest ;
258
276
return await ReadSingleImageAsync (
259
277
repositoryName ,
260
- await manifestResponse . Content . ReadFromJsonAsync < ManifestV2 > ( cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ,
278
+ manifest ,
261
279
cancellationToken ) . ConfigureAwait ( false ) ;
262
- } else
280
+ }
281
+ else
263
282
{
264
283
throw new BaseImageNotFoundException ( runtimeIdentifier , repositoryName , reference , ridManifestDict . Keys ) ;
265
284
}
@@ -332,13 +351,13 @@ internal async Task<FinalizeUploadInformation> UploadBlobChunkedAsync(Stream con
332
351
333
352
int bytesRead = await contents . ReadAsync ( chunkBackingStore , cancellationToken ) . ConfigureAwait ( false ) ;
334
353
335
- ByteArrayContent content = new ( chunkBackingStore , offset : 0 , count : bytesRead ) ;
354
+ ByteArrayContent content = new ( chunkBackingStore , offset : 0 , count : bytesRead ) ;
336
355
content . Headers . ContentLength = bytesRead ;
337
356
338
357
// manual because ACR throws an error with the .NET type {"Range":"bytes 0-84521/*","Reason":"the Content-Range header format is invalid"}
339
358
// content.Headers.Add("Content-Range", $"0-{contents.Length - 1}");
340
359
Debug . Assert ( content . Headers . TryAddWithoutValidation ( "Content-Range" , $ "{ chunkStart } -{ chunkStart + bytesRead - 1 } ") ) ;
341
-
360
+
342
361
NextChunkUploadInformation nextChunk = await _registryAPI . Blob . Upload . UploadChunkAsync ( patchUri , content , cancellationToken ) . ConfigureAwait ( false ) ;
343
362
patchUri = nextChunk . UploadUri ;
344
363
@@ -420,7 +439,7 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source,
420
439
}
421
440
422
441
// Blob wasn't there; can we tell the server to get it from the base image?
423
- if ( ! await _registryAPI . Blob . Upload . TryMountAsync ( destination . Repository , source . Repository , digest , cancellationToken ) . ConfigureAwait ( false ) )
442
+ if ( ! await _registryAPI . Blob . Upload . TryMountAsync ( destination . Repository , source . Repository , digest , cancellationToken ) . ConfigureAwait ( false ) )
424
443
{
425
444
// The blob wasn't already available in another namespace, so fall back to explicitly uploading it
426
445
@@ -432,7 +451,8 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source,
432
451
await destinationRegistry . PushLayerAsync ( Layer . FromDescriptor ( descriptor ) , destination . Repository , cancellationToken ) . ConfigureAwait ( false ) ;
433
452
_logger . LogInformation ( Strings . Registry_LayerUploaded , digest , destinationRegistry . RegistryName ) ;
434
453
}
435
- else {
454
+ else
455
+ {
436
456
throw new NotImplementedException ( Resource . GetString ( nameof ( Strings . MissingLinkToRegistry ) ) ) ;
437
457
}
438
458
}
@@ -444,7 +464,7 @@ private async Task PushAsync(BuiltImage builtImage, SourceImageReference source,
444
464
}
445
465
else
446
466
{
447
- foreach ( var descriptor in builtImage . LayerDescriptors )
467
+ foreach ( var descriptor in builtImage . LayerDescriptors )
448
468
{
449
469
await uploadLayerFunc ( descriptor ) . ConfigureAwait ( false ) ;
450
470
}
0 commit comments