Skip to content

Commit 2b3e41f

Browse files
author
Oleg Bulatov
committed
Accept manifest lists with linux/amd64 platform
1 parent d133023 commit 2b3e41f

File tree

2 files changed

+105
-108
lines changed

2 files changed

+105
-108
lines changed

pkg/image/importer/importer.go

Lines changed: 105 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package importer
22

33
import (
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.
377432
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
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
}

test/integration/imageimporter_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ import (
3232
)
3333

3434
func TestImageStreamImport(t *testing.T) {
35-
t.Skip("This test was disabled until https://github.com/openshift/origin/issues/16323 is fixed!")
36-
3735
masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMaster()
3836
if err != nil {
3937
t.Fatalf("unexpected error: %v", err)
@@ -799,8 +797,6 @@ func TestImageStreamImportScheduled(t *testing.T) {
799797
}
800798

801799
func TestImageStreamImportDockerHub(t *testing.T) {
802-
t.Skip("This test was disabled until https://github.com/openshift/origin/issues/16323 is fixed!")
803-
804800
rt, _ := restclient.TransportFor(&restclient.Config{})
805801
importCtx := importer.NewContext(rt, nil).WithCredentials(importer.NoCredentials)
806802

0 commit comments

Comments
 (0)