11package importer
22
33import (
4+ "errors"
45 "fmt"
56 "net/url"
67 "strings"
@@ -10,6 +11,7 @@ import (
1011
1112 "github.com/docker/distribution"
1213 "github.com/docker/distribution/digest"
14+ "github.com/docker/distribution/manifest/manifestlist"
1315 "github.com/docker/distribution/manifest/schema1"
1416 "github.com/docker/distribution/manifest/schema2"
1517 "github.com/docker/distribution/reference"
@@ -316,26 +318,21 @@ func applyErrorToRepository(repository *importRepository, err error) {
316318 }
317319}
318320
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 {
321322 switch {
322323 case isDockerError (err , v2 .ErrorCodeManifestUnknown ):
323- ref := repository .Ref
324- ref .Tag , ref .ID = refName , refID
325324 err = kapierrors .NewNotFound (imageapi .Resource ("dockerimage" ), ref .Exact ())
326325 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 ()))
328327 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 ()))
330329 }
331- return
330+ return err
332331}
333332
334333// calculateImageSize gets and updates size of each image layer. If manifest v2 is converted to v1,
335334// 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 {
339336 blobSet := sets .NewString ()
340337 size := int64 (0 )
341338 for i := range image .DockerImageLayers {
@@ -372,6 +369,64 @@ func (isi *ImageStreamImporter) calculateImageSize(ctx gocontext.Context, repo d
372369 return nil
373370}
374371
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+
375430// importRepositoryFromDocker loads the tags and images requested in the passed importRepository, obeying the
376431// optional rate limiter. Errors are set onto the individual tags and digest objects.
377432func (isi * ImageStreamImporter ) importRepositoryFromDocker (ctx gocontext.Context , retriever RepositoryRetriever , repository * importRepository , limiter flowcontrol.RateLimiter ) {
@@ -418,9 +473,10 @@ func (isi *ImageStreamImporter) importRepositoryFromDocker(ctx gocontext.Context
418473 // get a blob context
419474 b := repo .Blobs (ctx )
420475
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
422478 if count := repository .MaximumTags ; count > 0 || count == - 1 {
423- tags , err : = repo .Tags (ctx ).All (ctx )
479+ tags , err = repo .Tags (ctx ).All (ctx )
424480 if err != nil {
425481 glog .V (5 ).Infof ("unable to access tags for repository %#v: %#v" , repository , err )
426482 switch {
@@ -441,16 +497,6 @@ func (isi *ImageStreamImporter) importRepositoryFromDocker(ctx gocontext.Context
441497 tags = set .List ()
442498 // include only the top N tags in the result, put the rest in AdditionalTags
443499 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- }
454500 }
455501
456502 // load digests
@@ -459,116 +505,71 @@ func (isi *ImageStreamImporter) importRepositoryFromDocker(ctx gocontext.Context
459505 if importDigest .Err != nil || importDigest .Image != nil {
460506 continue
461507 }
508+
462509 d , err := digest .ParseDigest (importDigest .Name )
463510 if err != nil {
464511 importDigest .Err = err
465512 continue
466513 }
514+
515+ ref := repository .Ref
516+ ref .Tag = ""
517+ ref .ID = string (d )
518+
467519 limiter .Accept ()
520+
468521 manifest , err := s .Get (ctx , d )
469522 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 )
472525 continue
473526 }
474527
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+ }
490530
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 ()
498537
538+ manifest , err := s .Get (ctx , "" , distribution .WithTag (importTag .Name ))
499539 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
502543 }
503544
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
514547 }
515548
516549 for i := range repository .Tags {
517550 importTag := & repository .Tags [i ]
518551 if importTag .Err != nil || importTag .Image != nil {
519552 continue
520553 }
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- }
541554
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+ }
558557
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 )
561562 continue
562563 }
563- if err := imageapi . ImageWithMetadata ( importTag . Image ); err != nil {
564- importTag . Err = err
565- continue
564+
565+ it := importTag {
566+ Name : tagName ,
566567 }
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 )
572573 }
573574 }
574575}
0 commit comments