1
1
package importer
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
5
6
"net/url"
6
7
"strings"
@@ -10,6 +11,7 @@ import (
10
11
11
12
"github.com/docker/distribution"
12
13
"github.com/docker/distribution/digest"
14
+ "github.com/docker/distribution/manifest/manifestlist"
13
15
"github.com/docker/distribution/manifest/schema1"
14
16
"github.com/docker/distribution/manifest/schema2"
15
17
"github.com/docker/distribution/reference"
@@ -316,26 +318,21 @@ func applyErrorToRepository(repository *importRepository, err error) {
316
318
}
317
319
}
318
320
319
- func formatRepositoryError (repository * importRepository , refName string , refID string , defErr error ) (err error ) {
320
- err = defErr
321
+ func formatRepositoryError (ref imageapi.DockerImageReference , err error ) error {
321
322
switch {
322
323
case isDockerError (err , v2 .ErrorCodeManifestUnknown ):
323
- ref := repository .Ref
324
- ref .Tag , ref .ID = refName , refID
325
324
err = kapierrors .NewNotFound (imageapi .Resource ("dockerimage" ), ref .Exact ())
326
325
case isDockerError (err , errcode .ErrorCodeUnauthorized ):
327
- err = kapierrors .NewUnauthorized (fmt .Sprintf ("you may not have access to the Docker image %q" , repository . Ref .Exact ()))
326
+ err = kapierrors .NewUnauthorized (fmt .Sprintf ("you may not have access to the Docker image %q" , ref .Exact ()))
328
327
case strings .HasSuffix (err .Error (), "no basic auth credentials" ):
329
- err = kapierrors .NewUnauthorized (fmt .Sprintf ("you may not have access to the Docker image %q" , repository . Ref .Exact ()))
328
+ err = kapierrors .NewUnauthorized (fmt .Sprintf ("you may not have access to the Docker image %q" , ref .Exact ()))
330
329
}
331
- return
330
+ return err
332
331
}
333
332
334
333
// calculateImageSize gets and updates size of each image layer. If manifest v2 is converted to v1,
335
334
// then it loses information about layers size. We have to get this information from server again.
336
- func (isi * ImageStreamImporter ) calculateImageSize (ctx gocontext.Context , repo distribution.Repository , image * imageapi.Image ) error {
337
- bs := repo .Blobs (ctx )
338
-
335
+ func (isi * ImageStreamImporter ) calculateImageSize (ctx gocontext.Context , bs distribution.BlobStore , image * imageapi.Image ) error {
339
336
blobSet := sets .NewString ()
340
337
size := int64 (0 )
341
338
for i := range image .DockerImageLayers {
@@ -372,6 +369,64 @@ func (isi *ImageStreamImporter) calculateImageSize(ctx gocontext.Context, repo d
372
369
return nil
373
370
}
374
371
372
+ // Defaults for converting manifest lists to schema1/schema2 manifests.
373
+ // See https://github.com/docker/distribution/blob/06fa77aa11a3913096efcb9b5bd25db8ef55a939/registry/handlers/manifests.go#L25
374
+ const (
375
+ defaultArch = "amd64"
376
+ defaultOS = "linux"
377
+ )
378
+
379
+ var errUnsupportedManifestList = errors .New ("importer: unsupported manifest list" )
380
+
381
+ func (isi * ImageStreamImporter ) importManifest (ctx gocontext.Context , manifest distribution.Manifest , ref imageapi.DockerImageReference , d digest.Digest , s distribution.ManifestService , b distribution.BlobStore ) (image * imageapi.Image , err error ) {
382
+ if manifestList , ok := manifest .(* manifestlist.DeserializedManifestList ); ok {
383
+ var manifestDigest digest.Digest
384
+ for _ , manifestDescriptor := range manifestList .Manifests {
385
+ if manifestDescriptor .Platform .Architecture == defaultArch && manifestDescriptor .Platform .OS == defaultOS {
386
+ manifestDigest = manifestDescriptor .Digest
387
+ break
388
+ }
389
+ }
390
+ if manifestDigest == "" {
391
+ return nil , errUnsupportedManifestList
392
+ }
393
+
394
+ manifest , err = s .Get (ctx , manifestDigest )
395
+ if err != nil {
396
+ glog .V (5 ).Infof ("unable to get %s/%s manifest by digest %q for image %s: %#v" , defaultOS , defaultArch , d , ref .Exact (), err )
397
+ return nil , formatRepositoryError (ref , err )
398
+ }
399
+ }
400
+
401
+ if signedManifest , isSchema1 := manifest .(* schema1.SignedManifest ); isSchema1 {
402
+ image , err = schema1ToImage (signedManifest , d )
403
+ } else if deserializedManifest , isSchema2 := manifest .(* schema2.DeserializedManifest ); isSchema2 {
404
+ imageConfig , getImportConfigErr := b .Get (ctx , deserializedManifest .Config .Digest )
405
+ if getImportConfigErr != nil {
406
+ glog .V (5 ).Infof ("unable to get image config by digest %q for image %s: %#v" , d , ref .Exact (), getImportConfigErr )
407
+ return image , formatRepositoryError (ref , getImportConfigErr )
408
+ }
409
+ image , err = schema2ToImage (deserializedManifest , imageConfig , d )
410
+ } else {
411
+ err = fmt .Errorf ("unsupported image manifest type: %T" , manifest )
412
+ glog .V (5 ).Info (err )
413
+ }
414
+ if err != nil {
415
+ return
416
+ }
417
+
418
+ if err := imageapi .ImageWithMetadata (image ); err != nil {
419
+ return image , err
420
+ }
421
+
422
+ if image .DockerImageMetadata .Size == 0 {
423
+ if err := isi .calculateImageSize (ctx , b , image ); err != nil {
424
+ return image , err
425
+ }
426
+ }
427
+ return
428
+ }
429
+
375
430
// importRepositoryFromDocker loads the tags and images requested in the passed importRepository, obeying the
376
431
// optional rate limiter. Errors are set onto the individual tags and digest objects.
377
432
func (isi * ImageStreamImporter ) importRepositoryFromDocker (ctx gocontext.Context , retriever RepositoryRetriever , repository * importRepository , limiter flowcontrol.RateLimiter ) {
@@ -418,9 +473,10 @@ func (isi *ImageStreamImporter) importRepositoryFromDocker(ctx gocontext.Context
418
473
// get a blob context
419
474
b := repo .Blobs (ctx )
420
475
421
- // if repository import is requested (MaximumTags), attempt to load the tags, sort them, and request the first N
476
+ // if repository import is requested (MaximumTags), attempt to load the tags, sort them, and request at most N tags
477
+ var tags []string
422
478
if count := repository .MaximumTags ; count > 0 || count == - 1 {
423
- tags , err : = repo .Tags (ctx ).All (ctx )
479
+ tags , err = repo .Tags (ctx ).All (ctx )
424
480
if err != nil {
425
481
glog .V (5 ).Infof ("unable to access tags for repository %#v: %#v" , repository , err )
426
482
switch {
@@ -441,16 +497,6 @@ func (isi *ImageStreamImporter) importRepositoryFromDocker(ctx gocontext.Context
441
497
tags = set .List ()
442
498
// include only the top N tags in the result, put the rest in AdditionalTags
443
499
imageapi .PrioritizeTags (tags )
444
- for _ , s := range tags {
445
- if count <= 0 && repository .MaximumTags != - 1 {
446
- repository .AdditionalTags = append (repository .AdditionalTags , s )
447
- continue
448
- }
449
- count --
450
- repository .Tags = append (repository .Tags , importTag {
451
- Name : s ,
452
- })
453
- }
454
500
}
455
501
456
502
// load digests
@@ -459,116 +505,71 @@ func (isi *ImageStreamImporter) importRepositoryFromDocker(ctx gocontext.Context
459
505
if importDigest .Err != nil || importDigest .Image != nil {
460
506
continue
461
507
}
508
+
462
509
d , err := digest .ParseDigest (importDigest .Name )
463
510
if err != nil {
464
511
importDigest .Err = err
465
512
continue
466
513
}
514
+
515
+ ref := repository .Ref
516
+ ref .Tag = ""
517
+ ref .ID = string (d )
518
+
467
519
limiter .Accept ()
520
+
468
521
manifest , err := s .Get (ctx , d )
469
522
if err != nil {
470
- glog .V (5 ).Infof ("unable to access digest %q for repository %#v : %#v" , d , repository , err )
471
- importDigest .Err = formatRepositoryError (repository , "" , importDigest . Name , err )
523
+ glog .V (5 ).Infof ("unable to get manifest by digest %q for image %s : %#v" , d , ref . Exact () , err )
524
+ importDigest .Err = formatRepositoryError (ref , err )
472
525
continue
473
526
}
474
527
475
- if signedManifest , isSchema1 := manifest .(* schema1.SignedManifest ); isSchema1 {
476
- importDigest .Image , err = schema1ToImage (signedManifest , d )
477
- } else if deserializedManifest , isSchema2 := manifest .(* schema2.DeserializedManifest ); isSchema2 {
478
- imageConfig , getImportConfigErr := b .Get (ctx , deserializedManifest .Config .Digest )
479
- if getImportConfigErr != nil {
480
- glog .V (5 ).Infof ("unable to access the image config using digest %q for repository %#v: %#v" , d , repository , getImportConfigErr )
481
- if isDockerError (getImportConfigErr , v2 .ErrorCodeManifestUnknown ) {
482
- ref := repository .Ref
483
- ref .ID = deserializedManifest .Config .Digest .String ()
484
- importDigest .Err = kapierrors .NewNotFound (imageapi .Resource ("dockerimage" ), ref .Exact ())
485
- } else {
486
- importDigest .Err = formatRepositoryError (repository , "" , importDigest .Name , getImportConfigErr )
487
- }
488
- continue
489
- }
528
+ importDigest .Image , importDigest .Err = isi .importManifest (ctx , manifest , ref , d , s , b )
529
+ }
490
530
491
- importDigest .Image , err = schema2ToImage (deserializedManifest , imageConfig , d )
492
- } else {
493
- // TODO: Current this error means the imported received the manifest list
494
- // which we don't support yet.
495
- err = fmt .Errorf ("unsupported image manifest schema: %T" , manifest )
496
- glog .V (5 ).Infof ("unsupported manifest type: %T" , manifest )
497
- }
531
+ doImportTag := func (importTag * importTag ) bool {
532
+ ref := repository .Ref
533
+ ref .Tag = importTag .Name
534
+ ref .ID = ""
535
+
536
+ limiter .Accept ()
498
537
538
+ manifest , err := s .Get (ctx , "" , distribution .WithTag (importTag .Name ))
499
539
if err != nil {
500
- importDigest .Err = err
501
- continue
540
+ glog .V (5 ).Infof ("unable to get manifest by tag %q for image %s: %#v" , importTag .Name , ref .Exact (), err )
541
+ importTag .Err = formatRepositoryError (ref , err )
542
+ return true
502
543
}
503
544
504
- if err := imageapi .ImageWithMetadata (importDigest .Image ); err != nil {
505
- importDigest .Err = err
506
- continue
507
- }
508
- if importDigest .Image .DockerImageMetadata .Size == 0 {
509
- if err := isi .calculateImageSize (ctx , repo , importDigest .Image ); err != nil {
510
- importDigest .Err = err
511
- continue
512
- }
513
- }
545
+ importTag .Image , importTag .Err = isi .importManifest (ctx , manifest , ref , "" , s , b )
546
+ return importTag .Err != errUnsupportedManifestList
514
547
}
515
548
516
549
for i := range repository .Tags {
517
550
importTag := & repository .Tags [i ]
518
551
if importTag .Err != nil || importTag .Image != nil {
519
552
continue
520
553
}
521
- limiter .Accept ()
522
-
523
- manifest , err := s .Get (ctx , "" , distribution .WithTag (importTag .Name ))
524
- if err != nil {
525
- glog .V (5 ).Infof ("unable to get manifest by tag %q for repository %#v: %#v" , importTag .Name , repository , err )
526
- // try to resolve the tag and fetch manifest by digest instead
527
- desc , getTagErr := repo .Tags (ctx ).Get (ctx , importTag .Name )
528
- if getTagErr != nil {
529
- glog .V (5 ).Infof ("unable to get tag %q for repository %#v: %#v" , importTag .Name , repository , getTagErr )
530
- importTag .Err = formatRepositoryError (repository , importTag .Name , "" , err )
531
- continue
532
- }
533
- m , getManifestErr := s .Get (ctx , desc .Digest )
534
- if getManifestErr != nil {
535
- glog .V (5 ).Infof ("unable to access digest %q for tag %q for repository %#v: %#v" , desc .Digest , importTag .Name , repository , getManifestErr )
536
- importTag .Err = formatRepositoryError (repository , importTag .Name , "" , err )
537
- continue
538
- }
539
- manifest = m
540
- }
541
554
542
- if signedManifest , isSchema1 := manifest .(* schema1.SignedManifest ); isSchema1 {
543
- importTag .Image , err = schema1ToImage (signedManifest , "" )
544
- } else if deserializedManifest , isSchema2 := manifest .(* schema2.DeserializedManifest ); isSchema2 {
545
- imageConfig , getImportConfigErr := b .Get (ctx , deserializedManifest .Config .Digest )
546
- if getImportConfigErr != nil {
547
- glog .V (5 ).Infof ("unable to access image config using digest %q for tag %q for repository %#v: %#v" , deserializedManifest .Config .Digest , importTag .Name , repository , getImportConfigErr )
548
- importTag .Err = formatRepositoryError (repository , importTag .Name , "" , getImportConfigErr )
549
- continue
550
- }
551
- importTag .Image , err = schema2ToImage (deserializedManifest , imageConfig , "" )
552
- } else {
553
- // TODO: Current this error means the imported received the manifest list
554
- // which we don't support yet.
555
- err = fmt .Errorf ("unsupported image manifest schema: %T" , manifest )
556
- glog .V (5 ).Infof ("unsupported manifest type: %T" , manifest )
557
- }
555
+ doImportTag (importTag )
556
+ }
558
557
559
- if err != nil {
560
- importTag .Err = err
558
+ imported := 0
559
+ for _ , tagName := range tags {
560
+ if repository .MaximumTags != - 1 && imported >= repository .MaximumTags {
561
+ repository .AdditionalTags = append (repository .AdditionalTags , tagName )
561
562
continue
562
563
}
563
- if err := imageapi . ImageWithMetadata ( importTag . Image ); err != nil {
564
- importTag . Err = err
565
- continue
564
+
565
+ it := importTag {
566
+ Name : tagName ,
566
567
}
567
- if importTag . Image . DockerImageMetadata . Size == 0 {
568
- if err := isi . calculateImageSize ( ctx , repo , importTag . Image ); err != nil {
569
- importTag . Err = err
570
- continue
571
- }
568
+ if doImportTag ( & it ) {
569
+ imported ++
570
+ repository . Tags = append ( repository . Tags , it )
571
+ } else {
572
+ repository . AdditionalTags = append ( repository . AdditionalTags , tagName )
572
573
}
573
574
}
574
575
}
0 commit comments