// Complete completes the required options for build-chain func (o *BuildChainOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string, out io.Writer) error { if len(args) != 1 { return cmdutil.UsageError(cmd, "Must pass an image stream tag. If only an image stream name is specified, 'latest' will be used for the tag.") } // Setup client oc, _, _, err := f.Clients() if err != nil { return err } o.c, o.t = oc, oc resource := unversioned.GroupResource{} mapper, _ := f.Object(false) resource, o.name, err = osutil.ResolveResource(imageapi.Resource("imagestreamtags"), args[0], mapper) if err != nil { return err } switch resource { case imageapi.Resource("imagestreamtags"): o.name = imageapi.NormalizeImageStreamTag(o.name) glog.V(4).Infof("Using %q as the image stream tag to look dependencies for", o.name) default: return fmt.Errorf("invalid resource provided: %v", resource) } // Setup namespace if o.allNamespaces { // TODO: Handle different uses of build-chain; user and admin projectList, err := oc.Projects().List(kapi.ListOptions{}) if err != nil { return err } for _, project := range projectList.Items { glog.V(4).Infof("Found namespace %q", project.Name) o.namespaces.Insert(project.Name) } } namespace, _, err := f.DefaultNamespace() if err != nil { return err } o.defaultNamespace = namespace glog.V(4).Infof("Using %q as the namespace for %q", o.defaultNamespace, o.name) o.namespaces.Insert(namespace) glog.V(4).Infof("Will look for deps in %s", strings.Join(o.namespaces.List(), ",")) return nil }
func TestRunTag_AddAccrossNamespaces(t *testing.T) { streams := testData() client := testclient.NewSimpleFake(streams[2], streams[0]) client.PrependReactor("create", "imagestreamtags", func(action ktc.Action) (handled bool, ret runtime.Object, err error) { return true, nil, kapierrors.NewMethodNotSupported(imageapi.Resource("imagestreamtags"), "create") }) client.PrependReactor("update", "imagestreamtags", func(action ktc.Action) (handled bool, ret runtime.Object, err error) { return true, nil, kapierrors.NewMethodNotSupported(imageapi.Resource("imagestreamtags"), "update") }) test := struct { opts *TagOptions expectedActions []testAction expectedErr error }{ opts: &TagOptions{ out: os.Stdout, osClient: client, ref: imageapi.DockerImageReference{ Namespace: "openshift", Name: "ruby", Tag: "latest", }, namespace: "myproject2", sourceKind: "ImageStreamTag", destNamespace: []string{"yourproject"}, destNameAndTag: []string{"rails:tip"}, }, expectedActions: []testAction{ {verb: "update", resource: "imagestreamtags"}, {verb: "create", resource: "imagestreamtags"}, {verb: "get", resource: "imagestreams"}, {verb: "update", resource: "imagestreams"}, }, expectedErr: nil, } if err := test.opts.RunTag(); err != test.expectedErr { t.Fatalf("error mismatch: expected %v, got %v", test.expectedErr, err) } got := client.Actions() if len(test.expectedActions) != len(got) { t.Fatalf("action length mismatch: expectedc %d, got %d", len(test.expectedActions), len(got)) } for i, action := range test.expectedActions { if !got[i].Matches(action.verb, action.resource) { t.Errorf("action mismatch: expected %s %s, got %s %s", action.verb, action.resource, got[i].GetVerb(), got[i].GetResource()) } } }
// Delete removes a tag from a stream. `id` is of the format <stream name>:<tag>. // The associated image that the tag points to is *not* deleted. // The tag history remains intact and is not deleted. func (r *REST) Delete(ctx kapi.Context, id string) (runtime.Object, error) { name, tag, err := nameAndTag(id) if err != nil { return nil, err } stream, err := r.imageStreamRegistry.GetImageStream(ctx, name) if err != nil { return nil, err } notFound := true // Try to delete the status tag if _, ok := stream.Status.Tags[tag]; ok { delete(stream.Status.Tags, tag) notFound = false } // Try to delete the spec tag if _, ok := stream.Spec.Tags[tag]; ok { delete(stream.Spec.Tags, tag) notFound = false } if notFound { return nil, kapierrors.NewNotFound(api.Resource("imagestreamtags"), tag) } if _, err = r.imageStreamRegistry.UpdateImageStream(ctx, stream); err != nil { return nil, fmt.Errorf("cannot remove tag from image stream: %v", err) } return &unversioned.Status{Status: unversioned.StatusSuccess}, nil }
// NewREST returns a new REST. func NewREST(optsGetter restoptions.Getter) (*REST, error) { store := ®istry.Store{ NewFunc: func() runtime.Object { return &api.Image{} }, // NewListFunc returns an object capable of storing results of an etcd list. NewListFunc: func() runtime.Object { return &api.ImageList{} }, // Retrieve the name field of an image ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Image).Name, nil }, // Used to match objects based on labels/fields for list and watch PredicateFunc: func(label labels.Selector, field fields.Selector) *generic.SelectionPredicate { return image.Matcher(label, field) }, QualifiedResource: api.Resource("images"), // Used to validate image creation CreateStrategy: image.Strategy, // Used to validate image updates UpdateStrategy: image.Strategy, ReturnDeletedObject: false, } if err := restoptions.ApplyOptions(optsGetter, store, false, storage.NoTriggerPublisher); err != nil { return nil, err } return &REST{store}, nil }
func (r *REST) Delete(ctx kapi.Context, name string) (runtime.Object, error) { imageName, _, err := imageapi.SplitImageSignatureName(name) if err != nil { return nil, kapierrors.NewBadRequest("ImageSignatures must be accessed with <imageName>@<signatureName>") } image, err := r.imageClient.Get(imageName) if err != nil { return nil, err } index := imageapi.IndexOfImageSignatureByName(image.Signatures, name) if index < 0 { return nil, kapierrors.NewNotFound(imageapi.Resource("imageSignatures"), name) } size := len(image.Signatures) copy(image.Signatures[index:size-1], image.Signatures[index+1:size]) image.Signatures = image.Signatures[0 : size-1] if _, err := r.imageClient.Update(image); err != nil { return nil, err } return &unversioned.Status{Status: unversioned.StatusSuccess}, nil }
// imageFor retrieves the most recent image for a tag in a given imageStreem. func (r *REST) imageFor(ctx kapi.Context, tag string, imageStream *api.ImageStream) (*api.Image, error) { event := api.LatestTaggedImage(imageStream, tag) if event == nil || len(event.Image) == 0 { return nil, kapierrors.NewNotFound(api.Resource("imagestreamtags"), api.JoinImageStreamTag(imageStream.Name, tag)) } return r.imageRegistry.GetImage(ctx, event.Image) }
func TestAdmitImageStreamMapping(t *testing.T) { tests := map[string]struct { imageStreamMapping *imageapi.ImageStreamMapping limitRange *kapi.LimitRange shouldAdmit bool operation kadmission.Operation }{ "new ism, no limit range": { imageStreamMapping: getImageStreamMapping(), operation: kadmission.Create, shouldAdmit: true, }, "new ism, under limit range": { imageStreamMapping: getImageStreamMapping(), limitRange: getLimitRange("1Ki"), operation: kadmission.Create, shouldAdmit: true, }, "new ism, over limit range": { imageStreamMapping: getImageStreamMapping(), limitRange: getLimitRange("0Ki"), operation: kadmission.Create, shouldAdmit: false, }, } for k, v := range tests { var fakeKubeClient clientset.Interface if v.limitRange != nil { fakeKubeClient = clientsetfake.NewSimpleClientset(v.limitRange) } else { fakeKubeClient = clientsetfake.NewSimpleClientset() } plugin, err := NewImageLimitRangerPlugin(fakeKubeClient, nil) if err != nil { t.Errorf("%s failed creating plugin %v", k, err) continue } attrs := kadmission.NewAttributesRecord(v.imageStreamMapping, imageapi.Kind("ImageStreamMapping").WithVersion("version"), v.imageStreamMapping.Namespace, v.imageStreamMapping.Name, imageapi.Resource("imagestreammappings").WithVersion("version"), "", v.operation, nil) err = plugin.Admit(attrs) if v.shouldAdmit && err != nil { t.Errorf("%s expected to be admitted but received error %v", k, err) } if !v.shouldAdmit && err == nil { t.Errorf("%s expected to be rejected but received no error", k) } } }
func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { istag, ok := obj.(*imageapi.ImageStreamTag) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamTag: %#v", obj)) } if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { return nil, err } namespace, ok := kapi.NamespaceFrom(ctx) if !ok { return nil, kapierrors.NewBadRequest("a namespace must be specified to import images") } imageStreamName, imageTag, ok := imageapi.SplitImageStreamTag(istag.Name) if !ok { return nil, fmt.Errorf("%q must be of the form <stream_name>:<tag>", istag.Name) } target, err := r.imageStreamRegistry.GetImageStream(ctx, imageStreamName) if err != nil { if !kapierrors.IsNotFound(err) { return nil, err } // try to create the target if it doesn't exist target = &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: imageStreamName, Namespace: namespace, }, } } if target.Spec.Tags == nil { target.Spec.Tags = make(map[string]imageapi.TagReference) } // The user wants to symlink a tag. _, exists := target.Spec.Tags[imageTag] if exists { return nil, kapierrors.NewAlreadyExists(imageapi.Resource("imagestreamtag"), istag.Name) } target.Spec.Tags[imageTag] = *istag.Tag // Check the stream creation timestamp and make sure we will not // create a new image stream while deleting. if target.CreationTimestamp.IsZero() { _, err = r.imageStreamRegistry.CreateImageStream(ctx, target) } else { _, err = r.imageStreamRegistry.UpdateImageStream(ctx, target) } if err != nil { return nil, err } return istag, nil }
// NewREST returns a new REST. func NewREST(optsGetter restoptions.Getter, defaultRegistry api.DefaultRegistry, subjectAccessReviewRegistry subjectaccessreview.Registry, limitVerifier imageadmission.LimitVerifier) (*REST, *StatusREST, *InternalREST, error) { prefix := "/imagestreams" store := registry.Store{ NewFunc: func() runtime.Object { return &api.ImageStream{} }, // NewListFunc returns an object capable of storing results of an etcd list. NewListFunc: func() runtime.Object { return &api.ImageStreamList{} }, // Produces a path that etcd understands, to the root of the resource // by combining the namespace in the context with the given prefix. KeyRootFunc: func(ctx kapi.Context) string { return registry.NamespaceKeyRootFunc(ctx, prefix) }, // Produces a path that etcd understands, to the resource by combining // the namespace in the context with the given prefix KeyFunc: func(ctx kapi.Context, name string) (string, error) { return registry.NamespaceKeyFunc(ctx, prefix, name) }, // Retrieve the name field of an image ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.ImageStream).Name, nil }, // Used to match objects based on labels/fields for list and watch PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return imagestream.MatchImageStream(label, field) }, QualifiedResource: api.Resource("imagestreams"), ReturnDeletedObject: false, } strategy := imagestream.NewStrategy(defaultRegistry, subjectAccessReviewRegistry, limitVerifier) rest := &REST{Store: &store, subjectAccessReviewRegistry: subjectAccessReviewRegistry} strategy.ImageStreamGetter = rest store.CreateStrategy = strategy store.UpdateStrategy = strategy store.Decorator = strategy.Decorate if err := restoptions.ApplyOptions(optsGetter, &store, prefix); err != nil { return nil, nil, nil, err } statusStore := store statusStore.Decorator = nil statusStore.CreateStrategy = nil statusStore.UpdateStrategy = imagestream.NewStatusStrategy(strategy) internalStore := store internalStrategy := imagestream.NewInternalStrategy(strategy) internalStore.Decorator = nil internalStore.CreateStrategy = internalStrategy internalStore.UpdateStrategy = internalStrategy return rest, &StatusREST{store: &statusStore}, &InternalREST{store: &internalStore}, nil }
// Get the image stream matching the name from the cache. func (s storeImageStreamsNamespacer) Get(name string) (*imageapi.ImageStream, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { return nil, kapierrors.NewNotFound(imageapi.Resource("imagestream"), name) } return obj.(*imageapi.ImageStream), nil }
func newISTag(tag string, imageStream *api.ImageStream, image *api.Image) (*api.ImageStreamTag, error) { istagName := api.JoinImageStreamTag(imageStream.Name, tag) event := api.LatestTaggedImage(imageStream, tag) if event == nil || len(event.Image) == 0 { return nil, kapierrors.NewNotFound(api.Resource("imagestreamtag"), istagName) } ist := &api.ImageStreamTag{ ObjectMeta: kapi.ObjectMeta{ Namespace: imageStream.Namespace, Name: istagName, CreationTimestamp: event.Created, Annotations: map[string]string{}, ResourceVersion: imageStream.ResourceVersion, }, } // if the imageStream has Spec.Tags[tag].Annotations[k] = v, copy it to the image's annotations // and add them to the istag's annotations if imageStream.Spec.Tags != nil { if tagRef, ok := imageStream.Spec.Tags[tag]; ok { if image != nil && image.Annotations == nil { image.Annotations = make(map[string]string) } for k, v := range tagRef.Annotations { ist.Annotations[k] = v if image != nil { image.Annotations[k] = v } } } } if image != nil { if err := api.ImageWithMetadata(image); err != nil { return nil, err } image.DockerImageManifest = "" ist.Image = *image } else { ist.Image = api.Image{} ist.Image.Name = event.Image } // Replace the DockerImageReference with the value from event, which contains // real value from status. This should fix the problem for v1 registries, // where mutliple tags point to a single id and only the first image's metadata // is saved. This in turn will always return the pull spec from the first // imported image, which might be different than the requested tag. ist.Image.DockerImageReference = event.DockerImageReference return ist, nil }
// TestCreateRetryConflictTagDiff ensures that attempts to create a mapping // that result in resource conflicts that DO contain tag diffs causes the // conflict error to be returned. func TestCreateRetryConflictTagDiff(t *testing.T) { firstGet := true firstUpdate := true rest := &REST{ strategy: NewStrategy(testDefaultRegistry), imageRegistry: &fakeImageRegistry{ createImage: func(ctx kapi.Context, image *api.Image) error { return nil }, }, imageStreamRegistry: &fakeImageStreamRegistry{ getImageStream: func(ctx kapi.Context, id string) (*api.ImageStream, error) { // For the first get, return a stream with a latest tag pointing to "original" if firstGet { firstGet = false stream := validImageStream() stream.Status = api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": {Items: []api.TagEvent{{DockerImageReference: "localhost:5000/someproject/somerepo:original"}}}, }, } return stream, nil } // For subsequent gets, return a stream with the latest tag changed to "newer" stream := validImageStream() stream.Status = api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": {Items: []api.TagEvent{{DockerImageReference: "localhost:5000/someproject/somerepo:newer"}}}, }, } return stream, nil }, updateImageStreamStatus: func(ctx kapi.Context, repo *api.ImageStream) (*api.ImageStream, error) { // For the first update, return a conflict so that the stream // get/compare is retried. if firstUpdate { firstUpdate = false return nil, errors.NewConflict(api.Resource("imagestreams"), repo.Name, fmt.Errorf("resource modified")) } return repo, nil }, }, } obj, err := rest.Create(kapi.NewDefaultContext(), validNewMappingWithName()) if err == nil { t.Fatalf("expected an error") } if !errors.IsConflict(err) { t.Errorf("expected a conflict error, got %v", err) } if obj != nil { t.Fatalf("expected a nil result") } }
func (v *limitVerifier) VerifyLimits(namespace string, is *imageapi.ImageStream) error { limits, err := v.limiter.LimitsForNamespace(namespace) if err != nil || len(limits) == 0 { return err } usage := GetImageStreamUsage(is) if err := verifyImageStreamUsage(usage, limits); err != nil { return kapierrors.NewForbidden(imageapi.Resource("ImageStream"), is.Name, err) } return nil }
// NewREST returns a new REST. func NewREST(optsGetter restoptions.Getter, defaultRegistry api.DefaultRegistry, subjectAccessReviewRegistry subjectaccessreview.Registry, limitVerifier imageadmission.LimitVerifier) (*REST, *StatusREST, *InternalREST, error) { store := registry.Store{ NewFunc: func() runtime.Object { return &api.ImageStream{} }, // NewListFunc returns an object capable of storing results of an etcd list. NewListFunc: func() runtime.Object { return &api.ImageStreamList{} }, // Retrieve the name field of an image ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.ImageStream).Name, nil }, // Used to match objects based on labels/fields for list and watch PredicateFunc: func(label labels.Selector, field fields.Selector) *generic.SelectionPredicate { return imagestream.Matcher(label, field) }, QualifiedResource: api.Resource("imagestreams"), ReturnDeletedObject: false, } rest := &REST{ Store: &store, subjectAccessReviewRegistry: subjectAccessReviewRegistry, } // strategy must be able to load image streams across namespaces during tag verification strategy := imagestream.NewStrategy(defaultRegistry, subjectAccessReviewRegistry, limitVerifier, rest) store.CreateStrategy = strategy store.UpdateStrategy = strategy store.Decorator = strategy.Decorate if err := restoptions.ApplyOptions(optsGetter, &store, true, storage.NoTriggerPublisher); err != nil { return nil, nil, nil, err } statusStrategy := imagestream.NewStatusStrategy(strategy) statusStore := store statusStore.Decorator = nil statusStore.CreateStrategy = nil statusStore.UpdateStrategy = statusStrategy statusREST := &StatusREST{store: &statusStore} internalStore := store internalStrategy := imagestream.NewInternalStrategy(strategy) internalStore.Decorator = nil internalStore.CreateStrategy = internalStrategy internalStore.UpdateStrategy = internalStrategy internalREST := &InternalREST{store: &internalStore} return rest, statusREST, internalREST, nil }
func formatRepositoryError(repository *importRepository, refName string, refID string, defErr error) (err error) { err = defErr switch { case isDockerError(err, v2.ErrorCodeManifestUnknown): ref := repository.Ref ref.Tag, ref.ID = refName, refID err = kapierrors.NewNotFound(api.Resource("dockerimage"), ref.Exact()) case isDockerError(err, errcode.ErrorCodeUnauthorized): err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact())) case strings.HasSuffix(err.Error(), "no basic auth credentials"): err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact())) } return }
func TestImageStreamImportUnsupported(t *testing.T) { testCases := []struct { status unversioned.Status errFn func(err error) bool }{ { status: errors.NewNotFound(api.Resource(""), "").(errors.APIStatus).Status(), errFn: func(err error) bool { return err == ErrImageStreamImportUnsupported }, }, { status: errors.NewNotFound(api.Resource("ImageStreamImport"), "").(errors.APIStatus).Status(), errFn: func(err error) bool { return err != ErrImageStreamImportUnsupported && errors.IsNotFound(err) }, }, { status: errors.NewConflict(api.Resource("ImageStreamImport"), "", nil).(errors.APIStatus).Status(), errFn: func(err error) bool { return err != ErrImageStreamImportUnsupported && errors.IsConflict(err) }, }, { status: errors.NewForbidden(api.Resource("ImageStreamImport"), "", nil).(errors.APIStatus).Status(), errFn: func(err error) bool { return err == ErrImageStreamImportUnsupported }, }, } for i, test := range testCases { c, err := New(&kclient.Config{ Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { buf := bytes.NewBuffer([]byte(runtime.EncodeOrDie(kapi.Codecs.LegacyCodec(api.SchemeGroupVersion), &test.status))) return &http.Response{StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(buf)}, nil }), }) if err != nil { t.Fatal(err) } if _, err := c.ImageStreams("test").Import(&api.ImageStreamImport{}); !test.errFn(err) { t.Errorf("%d: error: %v", i, err) } } }
func TestRunTag_DeleteOld(t *testing.T) { streams := testData() client := testclient.NewSimpleFake(streams[1]) client.PrependReactor("delete", "imagestreamtags", func(action ktc.Action) (handled bool, ret runtime.Object, err error) { return true, nil, kapierrors.NewForbidden(imageapi.Resource("imagestreamtags"), "rails:tip", fmt.Errorf("dne")) }) client.PrependReactor("get", "imagestreams", func(action ktc.Action) (handled bool, ret runtime.Object, err error) { return true, testData()[1], nil }) client.PrependReactor("update", "imagestreams", func(action ktc.Action) (handled bool, ret runtime.Object, err error) { return true, nil, nil }) test := struct { opts *TagOptions expectedActions []testAction expectedErr error }{ opts: &TagOptions{ out: os.Stdout, osClient: client, deleteTag: true, destNamespace: []string{"yourproject"}, destNameAndTag: []string{"rails:tip"}, }, expectedActions: []testAction{ {verb: "delete", resource: "imagestreamtags"}, {verb: "get", resource: "imagestreams"}, {verb: "update", resource: "imagestreams"}, }, expectedErr: nil, } if err := test.opts.RunTag(); err != test.expectedErr { t.Fatalf("error mismatch: expected %v, got %v", test.expectedErr, err) } got := client.Actions() if len(test.expectedActions) != len(got) { t.Fatalf("action length mismatch: expectedc %d, got %d", len(test.expectedActions), len(got)) } for i, action := range test.expectedActions { if !got[i].Matches(action.verb, action.resource) { t.Errorf("action mismatch: expected %s %s, got %s %s", action.verb, action.resource, got[i].GetVerb(), got[i].GetResource()) } } }
// Get retrieves an image by ID that has previously been tagged into an image stream. // `id` is of the form <repo name>@<image id>. func (r *REST) Get(ctx kapi.Context, id string) (runtime.Object, error) { name, imageID, err := ParseNameAndID(id) if err != nil { return nil, err } repo, err := r.imageStreamRegistry.GetImageStream(ctx, name) if err != nil { return nil, err } if repo.Status.Tags == nil { return nil, errors.NewNotFound(api.Resource("imagestreamimage"), imageID) } event, err := api.ResolveImageID(repo, imageID) if err != nil { return nil, err } imageName := event.Image image, err := r.imageRegistry.GetImage(ctx, imageName) if err != nil { return nil, err } if err := api.ImageWithMetadata(image); err != nil { return nil, err } image.DockerImageManifest = "" if d, err := digest.ParseDigest(imageName); err == nil { imageName = d.Hex() } if len(imageName) > 7 { imageName = imageName[:7] } isi := api.ImageStreamImage{ ObjectMeta: kapi.ObjectMeta{ Namespace: kapi.NamespaceValue(ctx), Name: fmt.Sprintf("%s@%s", name, imageName), }, Image: *image, } return &isi, nil }
// NewREST returns a new REST. func NewREST(optsGetter restoptions.Getter) (*REST, error) { prefix := "/images" store := ®istry.Store{ NewFunc: func() runtime.Object { return &api.Image{} }, // NewListFunc returns an object capable of storing results of an etcd list. NewListFunc: func() runtime.Object { return &api.ImageList{} }, // Produces a path that etcd understands, to the root of the resource // by combining the namespace in the context with the given prefix. // Yet images are not namespace scoped, so we're returning just prefix here. KeyRootFunc: func(ctx kapi.Context) string { return prefix }, // Produces a path that etcd understands, to the resource by combining // the namespace in the context with the given prefix // Yet images are not namespace scoped, so we're returning just prefix here. KeyFunc: func(ctx kapi.Context, name string) (string, error) { return registry.NoNamespaceKeyFunc(ctx, prefix, name) }, // Retrieve the name field of an image ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Image).Name, nil }, // Used to match objects based on labels/fields for list and watch PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return image.MatchImage(label, field) }, QualifiedResource: api.Resource("images"), // Used to validate image creation CreateStrategy: image.Strategy, // Used to validate image updates UpdateStrategy: image.Strategy, ReturnDeletedObject: false, } if err := restoptions.ApplyOptions(optsGetter, store, prefix); err != nil { return nil, err } return &REST{store}, nil }
func TestGenerate_reportsNotFoundErrorWhenMissingDeploymentConfig(t *testing.T) { generator := &DeploymentConfigGenerator{ Client: Client{ DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) { return nil, kerrors.NewNotFound(deployapi.Resource("DeploymentConfig"), name) }, ISFn: func(ctx kapi.Context, name string) (*imageapi.ImageStream, error) { return nil, kerrors.NewNotFound(imageapi.Resource("ImageStream"), name) }, }, } _, err := generator.Generate(kapi.NewDefaultContext(), "deploy1") if err == nil || !kerrors.IsNotFound(err) { t.Fatalf("Unexpected error type: %v", err) } if !strings.Contains(err.Error(), "DeploymentConfig \"deploy1\" not found") { t.Errorf("unexpected error message: %v", err) } }
// findStreamForMapping retrieves an ImageStream whose DockerImageRepository matches dockerRepo. func (s *REST) findStreamForMapping(ctx kapi.Context, mapping *api.ImageStreamMapping) (*api.ImageStream, error) { if len(mapping.Name) > 0 { return s.imageStreamRegistry.GetImageStream(ctx, mapping.Name) } if len(mapping.DockerImageRepository) != 0 { list, err := s.imageStreamRegistry.ListImageStreams(ctx, &kapi.ListOptions{}) if err != nil { return nil, err } for i := range list.Items { if mapping.DockerImageRepository == list.Items[i].Spec.DockerImageRepository { return &list.Items[i], nil } } return nil, errors.NewInvalid(api.Kind("ImageStreamMapping"), "", field.ErrorList{ field.NotFound(field.NewPath("dockerImageStream"), mapping.DockerImageRepository), }) } return nil, errors.NewNotFound(api.Resource("imagestream"), "") }
func (v *limitVerifier) VerifyLimits(namespace string, is *imageapi.ImageStream) error { items, err := v.indexer.Index("namespace", &kapi.LimitRange{ObjectMeta: kapi.ObjectMeta{Namespace: namespace}}) if err != nil { return fmt.Errorf("error resolving limit ranges: %v", err) } if len(items) == 0 { return nil } limits := getMaxLimits(items) if len(limits) == 0 { return nil } usage := GetImageStreamUsage(is) if err := verifyImageStreamUsage(usage, limits); err != nil { return kapierrors.NewForbidden(imageapi.Resource("ImageStream"), is.Name, err) } return nil }
// Get retrieves an image by ID that has previously been tagged into an image stream. // `id` is of the form <repo name>@<image id>. func (r *REST) Get(ctx kapi.Context, id string) (runtime.Object, error) { name, imageID, err := parseNameAndID(id) if err != nil { return nil, err } repo, err := r.imageStreamRegistry.GetImageStream(ctx, name) if err != nil { return nil, err } if repo.Status.Tags == nil { return nil, errors.NewNotFound(api.Resource("imagestreamimage"), id) } event, err := api.ResolveImageID(repo, imageID) if err != nil { return nil, err } imageName := event.Image image, err := r.imageRegistry.GetImage(ctx, imageName) if err != nil { return nil, err } if err := api.ImageWithMetadata(image); err != nil { return nil, err } image.DockerImageManifest = "" isi := api.ImageStreamImage{ ObjectMeta: kapi.ObjectMeta{ Namespace: kapi.NamespaceValue(ctx), Name: api.MakeImageStreamImageName(name, imageID), CreationTimestamp: image.ObjectMeta.CreationTimestamp, }, Image: *image, } return &isi, nil }
// TestCreateRetryConflictNoTagDiff ensures that attempts to create a mapping // that result in resource conflicts that do NOT include tag diffs causes the // create to be retried successfully. func TestCreateRetryConflictNoTagDiff(t *testing.T) { firstUpdate := true rest := &REST{ strategy: NewStrategy(testDefaultRegistry), imageRegistry: &fakeImageRegistry{ createImage: func(ctx kapi.Context, image *api.Image) error { return nil }, }, imageStreamRegistry: &fakeImageStreamRegistry{ getImageStream: func(ctx kapi.Context, id string) (*api.ImageStream, error) { stream := validImageStream() stream.Status = api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": {Items: []api.TagEvent{{DockerImageReference: "localhost:5000/someproject/somerepo:original"}}}, }, } return stream, nil }, updateImageStreamStatus: func(ctx kapi.Context, repo *api.ImageStream) (*api.ImageStream, error) { // For the first update call, return a conflict to cause a retry of an // image stream whose tags haven't changed. if firstUpdate { firstUpdate = false return nil, errors.NewConflict(api.Resource("imagestreams"), repo.Name, fmt.Errorf("resource modified")) } return repo, nil }, }, } obj, err := rest.Create(kapi.NewDefaultContext(), validNewMappingWithName()) if err != nil { t.Errorf("unexpected error: %v", err) } if obj == nil { t.Fatalf("expected a result") } }
func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { signature := obj.(*imageapi.ImageSignature) if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { return nil, err } imageName, _, err := imageapi.SplitImageSignatureName(signature.Name) if err != nil { return nil, kapierrors.NewBadRequest(err.Error()) } image, err := r.imageClient.Get(imageName) if err != nil { return nil, err } // ensure that given signature already doesn't exist - either by its name or type:content if byName, byContent := imageapi.IndexOfImageSignatureByName(image.Signatures, signature.Name), imageapi.IndexOfImageSignature(image.Signatures, signature.Type, signature.Content); byName >= 0 || byContent >= 0 { return nil, kapierrors.NewAlreadyExists(imageapi.Resource("imageSignatures"), signature.Name) } image.Signatures = append(image.Signatures, *signature) image, err = r.imageClient.Update(image) if err != nil { return nil, err } byName := imageapi.IndexOfImageSignatureByName(image.Signatures, signature.Name) if byName < 0 { return nil, kapierrors.NewInternalError(errors.New("failed to store given signature")) } return &image.Signatures[byName], nil }
func TestLimitVerifier(t *testing.T) { makeISForbiddenError := func(isName string, exceeded []kapi.ResourceName) error { if len(exceeded) == 0 { return nil } exceededStrings := []string{} for _, r := range exceeded { exceededStrings = append(exceededStrings, string(r)) } sort.Strings(exceededStrings) err := fmt.Errorf("exceeded %s", strings.Join(exceededStrings, ",")) return kapierrors.NewForbidden(api.Resource("ImageStream"), isName, err) } makeISEvaluator := func(maxImages, maxImageTags int64) func(string, *api.ImageStream) error { return func(ns string, is *api.ImageStream) error { limit := kapi.ResourceList{ api.ResourceImageStreamImages: *resource.NewQuantity(maxImages, resource.DecimalSI), api.ResourceImageStreamTags: *resource.NewQuantity(maxImageTags, resource.DecimalSI), } usage := admission.GetImageStreamUsage(is) if less, exceeded := kquota.LessThanOrEqual(usage, limit); !less { return makeISForbiddenError(is.Name, exceeded) } return nil } } tests := []struct { name string isEvaluator func(string, *api.ImageStream) error is api.ImageStream expected field.ErrorList }{ { name: "no limit", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Status: api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "is", testutil.BaseImageWith1LayerDigest), Image: testutil.BaseImageWith1LayerDigest, }, }, }, }, }, }, }, { name: "below limit", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Status: api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "is", testutil.BaseImageWith1LayerDigest), Image: testutil.BaseImageWith1LayerDigest, }, }, }, }, }, }, isEvaluator: makeISEvaluator(1, 0), }, { name: "exceed images", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Status: api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "is", testutil.BaseImageWith1LayerDigest), Image: testutil.BaseImageWith1LayerDigest, }, }, }, "oldest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "is", testutil.BaseImageWith2LayersDigest), Image: testutil.BaseImageWith2LayersDigest, }, }, }, }, }, }, isEvaluator: makeISEvaluator(1, 0), expected: field.ErrorList{ field.Forbidden(field.NewPath("imageStream"), makeISForbiddenError("is", []kapi.ResourceName{api.ResourceImageStreamImages}).Error()), }, }, { name: "exceed tags", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Spec: api.ImageStreamSpec{ Tags: map[string]api.TagReference{ "new": { Name: "new", From: &kapi.ObjectReference{ Kind: "DockerImage", Name: testutil.MakeDockerImageReference("test", "is", testutil.ChildImageWith2LayersDigest), }, }, }, }, }, isEvaluator: makeISEvaluator(0, 0), expected: field.ErrorList{ field.Forbidden(field.NewPath("imageStream"), makeISForbiddenError("is", []kapi.ResourceName{api.ResourceImageStreamTags}).Error()), }, }, { name: "exceed images and tags", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Spec: api.ImageStreamSpec{ Tags: map[string]api.TagReference{ "new": { Name: "new", From: &kapi.ObjectReference{ Kind: "DockerImage", Name: testutil.MakeDockerImageReference("test", "other", testutil.BaseImageWith1LayerDigest), }, }, }, }, Status: api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "other", testutil.BaseImageWith1LayerDigest), Image: testutil.BaseImageWith1LayerDigest, }, }, }, }, }, }, isEvaluator: makeISEvaluator(0, 0), expected: field.ErrorList{ field.Forbidden(field.NewPath("imageStream"), makeISForbiddenError("is", []kapi.ResourceName{api.ResourceImageStreamImages, api.ResourceImageStreamTags}).Error()), }, }, } for _, tc := range tests { sar := &fakeSubjectAccessReviewRegistry{ allow: true, } tagVerifier := &TagVerifier{sar} s := &Strategy{ tagVerifier: tagVerifier, limitVerifier: &testutil.FakeImageStreamLimitVerifier{ ImageStreamEvaluator: tc.isEvaluator, }, defaultRegistry: &fakeDefaultRegistry{}, } ctx := kapi.WithUser(kapi.NewDefaultContext(), &fakeUser{}) errList := s.Validate(ctx, &tc.is) if e, a := tc.expected, errList; !reflect.DeepEqual(e, a) { t.Errorf("%s: unexpected validation errors: %s", tc.name, diff.ObjectDiff(e, a)) } } }
func TestClusterReaderCoverage(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } discoveryClient := client.NewDiscoveryClient(clusterAdminClient.RESTClient) // (map[string]*unversioned.APIResourceList, error) allResourceList, err := discoveryClient.ServerResources() if err != nil { t.Fatalf("unexpected error: %v", err) } allResources := map[unversioned.GroupResource]bool{} for _, resources := range allResourceList { version, err := unversioned.ParseGroupVersion(resources.GroupVersion) if err != nil { t.Fatalf("unexpected error: %v", err) } for _, resource := range resources.APIResources { allResources[version.WithResource(resource.Name).GroupResource()] = true } } escalatingResources := map[unversioned.GroupResource]bool{ oauthapi.Resource("oauthauthorizetokens"): true, oauthapi.Resource("oauthaccesstokens"): true, oauthapi.Resource("oauthclients"): true, imageapi.Resource("imagestreams/secrets"): true, kapi.Resource("secrets"): true, kapi.Resource("pods/exec"): true, kapi.Resource("pods/proxy"): true, kapi.Resource("pods/portforward"): true, kapi.Resource("nodes/proxy"): true, kapi.Resource("services/proxy"): true, } readerRole, err := clusterAdminClient.ClusterRoles().Get(bootstrappolicy.ClusterReaderRoleName) if err != nil { t.Fatalf("unexpected error: %v", err) } for _, rule := range readerRole.Rules { for _, group := range rule.APIGroups { for resource := range rule.Resources { gr := unversioned.GroupResource{Group: group, Resource: resource} if escalatingResources[gr] { t.Errorf("cluster-reader role has escalating resource %v. Check pkg/cmd/server/bootstrappolicy/policy.go.", gr) } delete(allResources, gr) } } } // remove escalating resources that cluster-reader should not have access to for resource := range escalatingResources { delete(allResources, resource) } // remove resources without read APIs nonreadingResources := []unversioned.GroupResource{ buildapi.Resource("buildconfigs/instantiatebinary"), buildapi.Resource("buildconfigs/instantiate"), buildapi.Resource("builds/clone"), deployapi.Resource("deploymentconfigrollbacks"), deployapi.Resource("generatedeploymentconfigs"), deployapi.Resource("deploymentconfigs/rollback"), imageapi.Resource("imagestreamimports"), imageapi.Resource("imagestreammappings"), extensionsapi.Resource("deployments/rollback"), kapi.Resource("pods/attach"), kapi.Resource("namespaces/finalize"), } for _, resource := range nonreadingResources { delete(allResources, resource) } // anything left in the map is missing from the permissions if len(allResources) > 0 { t.Errorf("cluster-reader role is missing %v. Check pkg/cmd/server/bootstrappolicy/policy.go.", allResources) } }
func TestSupports(t *testing.T) { resources := []string{"imagestreammappings"} plugin, err := NewImageLimitRangerPlugin(clientsetfake.NewSimpleClientset(), nil) if err != nil { t.Fatalf("error creating plugin: %v", err) } ilr := plugin.(*imageLimitRangerPlugin) for _, r := range resources { attr := kadmission.NewAttributesRecord(nil, unversioned.Kind("ImageStreamMapping").WithVersion("version"), "ns", "name", imageapi.Resource(r).WithVersion("version"), "", kadmission.Create, nil) if !ilr.SupportsAttributes(attr) { t.Errorf("plugin is expected to support %s", r) } } badKinds := []string{"ImageStream", "Image", "Pod", "foo"} for _, k := range badKinds { attr := kadmission.NewAttributesRecord(nil, unversioned.Kind(k).WithVersion("version"), "ns", "name", imageapi.Resource("bar").WithVersion("version"), "", kadmission.Create, nil) if ilr.SupportsAttributes(attr) { t.Errorf("plugin is not expected to support %s", k) } } }
func importRepositoryFromDockerV1(ctx gocontext.Context, repository *importRepository, limiter flowcontrol.RateLimiter) { value := ctx.Value(ContextKeyV1RegistryClient) if value == nil { err := kapierrors.NewForbidden(api.Resource(""), "", fmt.Errorf("registry %q does not support the v2 Registry API", repository.Registry.Host)) err.ErrStatus.Reason = "NotV2Registry" applyErrorToRepository(repository, err) return } client, ok := value.(dockerregistry.Client) if !ok { err := kapierrors.NewForbidden(api.Resource(""), "", fmt.Errorf("registry %q does not support the v2 Registry API", repository.Registry.Host)) err.ErrStatus.Reason = "NotV2Registry" return } conn, err := client.Connect(repository.Registry.Host, repository.Insecure) if err != nil { applyErrorToRepository(repository, err) return } // if repository import is requested (MaximumTags), attempt to load the tags, sort them, and request the first N if count := repository.MaximumTags; count > 0 { tagMap, err := conn.ImageTags(repository.Ref.Namespace, repository.Ref.Name) if err != nil { repository.Err = err return } tags := make([]string, 0, len(tagMap)) for tag := range tagMap { tags = append(tags, tag) } // some images on the Hub have empty tags - treat those as "latest" set := sets.NewString(tags...) if set.Has("") { set.Delete("") set.Insert(api.DefaultImageTag) } tags = set.List() // include only the top N tags in the result, put the rest in AdditionalTags api.PrioritizeTags(tags) for _, s := range tags { if count <= 0 { repository.AdditionalTags = append(repository.AdditionalTags, s) continue } count-- repository.Tags = append(repository.Tags, importTag{ Name: s, }) } } // load digests for i := range repository.Digests { importDigest := &repository.Digests[i] if importDigest.Err != nil || importDigest.Image != nil { continue } limiter.Accept() image, err := conn.ImageByID(repository.Ref.Namespace, repository.Ref.Name, importDigest.Name) if err != nil { importDigest.Err = err continue } // we do not preserve manifests of legacy images importDigest.Image, err = schema0ToImage(image, importDigest.Name) if err != nil { importDigest.Err = err continue } } for i := range repository.Tags { importTag := &repository.Tags[i] if importTag.Err != nil || importTag.Image != nil { continue } limiter.Accept() image, err := conn.ImageByTag(repository.Ref.Namespace, repository.Ref.Name, importTag.Name) if err != nil { importTag.Err = err continue } // we do not preserve manifests of legacy images importTag.Image, err = schema0ToImage(image, "") if err != nil { importTag.Err = err continue } } }
// importRepositoryFromDocker loads the tags and images requested in the passed importRepository, obeying the // optional rate limiter. Errors are set onto the individual tags and digest objects. func (isi *ImageStreamImporter) importRepositoryFromDocker(ctx gocontext.Context, retriever RepositoryRetriever, repository *importRepository, limiter flowcontrol.RateLimiter) { glog.V(5).Infof("importing remote Docker repository registry=%s repository=%s insecure=%t", repository.Registry, repository.Name, repository.Insecure) // retrieve the repository repo, err := retriever.Repository(ctx, repository.Registry, repository.Name, repository.Insecure) if err != nil { glog.V(5).Infof("unable to access repository %#v: %#v", repository, err) switch { case err == reference.ErrReferenceInvalidFormat: err = field.Invalid(field.NewPath("from", "name"), repository.Name, "the provided repository name is not valid") case isDockerError(err, v2.ErrorCodeNameUnknown): err = kapierrors.NewNotFound(api.Resource("dockerimage"), repository.Ref.Exact()) case isDockerError(err, errcode.ErrorCodeUnauthorized): err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact())) case strings.Contains(err.Error(), "tls: oversized record received with length") && !repository.Insecure: err = kapierrors.NewBadRequest("this repository is HTTP only and requires the insecure flag to import") case strings.HasSuffix(err.Error(), "no basic auth credentials"): err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q and did not have credentials to the repository", repository.Ref.Exact())) case strings.HasSuffix(err.Error(), "does not support v2 API"): importRepositoryFromDockerV1(ctx, repository, limiter) return } applyErrorToRepository(repository, err) return } // get a manifest context s, err := repo.Manifests(ctx) if err != nil { glog.V(5).Infof("unable to access manifests for repository %#v: %#v", repository, err) switch { case isDockerError(err, v2.ErrorCodeNameUnknown): err = kapierrors.NewNotFound(api.Resource("dockerimage"), repository.Ref.Exact()) case isDockerError(err, errcode.ErrorCodeUnauthorized): err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact())) case strings.HasSuffix(err.Error(), "no basic auth credentials"): err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q and did not have credentials to the repository", repository.Ref.Exact())) } applyErrorToRepository(repository, err) return } // get a blob context b := repo.Blobs(ctx) // if repository import is requested (MaximumTags), attempt to load the tags, sort them, and request the first N if count := repository.MaximumTags; count > 0 || count == -1 { tags, err := repo.Tags(ctx).All(ctx) if err != nil { glog.V(5).Infof("unable to access tags for repository %#v: %#v", repository, err) switch { case isDockerError(err, v2.ErrorCodeNameUnknown): err = kapierrors.NewNotFound(api.Resource("dockerimage"), repository.Ref.Exact()) case isDockerError(err, errcode.ErrorCodeUnauthorized): err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact())) } repository.Err = err return } // some images on the Hub have empty tags - treat those as "latest" set := sets.NewString(tags...) if set.Has("") { set.Delete("") set.Insert(api.DefaultImageTag) } tags = set.List() // include only the top N tags in the result, put the rest in AdditionalTags api.PrioritizeTags(tags) for _, s := range tags { if count <= 0 && repository.MaximumTags != -1 { repository.AdditionalTags = append(repository.AdditionalTags, s) continue } count-- repository.Tags = append(repository.Tags, importTag{ Name: s, }) } } // load digests for i := range repository.Digests { importDigest := &repository.Digests[i] if importDigest.Err != nil || importDigest.Image != nil { continue } d, err := digest.ParseDigest(importDigest.Name) if err != nil { importDigest.Err = err continue } limiter.Accept() manifest, err := s.Get(ctx, d) if err != nil { glog.V(5).Infof("unable to access digest %q for repository %#v: %#v", d, repository, err) importDigest.Err = formatRepositoryError(repository, "", importDigest.Name, err) continue } if signedManifest, isSchema1 := manifest.(*schema1.SignedManifest); isSchema1 { importDigest.Image, err = schema1ToImage(signedManifest, d) } else if deserializedManifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 { imageConfig, err := b.Get(ctx, deserializedManifest.Config.Digest) if err != nil { glog.V(5).Infof("unable to access the image config using digest %q for repository %#v: %#v", d, repository, err) if isDockerError(err, v2.ErrorCodeManifestUnknown) { ref := repository.Ref ref.ID = deserializedManifest.Config.Digest.String() importDigest.Err = kapierrors.NewNotFound(api.Resource("dockerimage"), ref.Exact()) } else { importDigest.Err = formatRepositoryError(repository, "", importDigest.Name, err) } continue } importDigest.Image, err = schema2ToImage(deserializedManifest, imageConfig, d) } else { glog.V(5).Infof("unsupported manifest type: %T", manifest) continue } if err != nil { importDigest.Err = err continue } if err := api.ImageWithMetadata(importDigest.Image); err != nil { importDigest.Err = err continue } if importDigest.Image.DockerImageMetadata.Size == 0 { if err := isi.calculateImageSize(ctx, repo, importDigest.Image); err != nil { importDigest.Err = err continue } } } for i := range repository.Tags { importTag := &repository.Tags[i] if importTag.Err != nil || importTag.Image != nil { continue } limiter.Accept() desc, err := repo.Tags(ctx).Get(ctx, importTag.Name) if err != nil { glog.V(5).Infof("unable to get tag %q for repository %#v: %#v", importTag.Name, repository, err) importTag.Err = formatRepositoryError(repository, importTag.Name, "", err) continue } manifest, err := s.Get(ctx, desc.Digest) if err != nil { glog.V(5).Infof("unable to access digest %q for tag %q for repository %#v: %#v", desc.Digest, importTag.Name, repository, err) importTag.Err = formatRepositoryError(repository, importTag.Name, "", err) continue } if signedManifest, isSchema1 := manifest.(*schema1.SignedManifest); isSchema1 { importTag.Image, err = schema1ToImage(signedManifest, "") } else if deserializedManifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 { imageConfig, err := b.Get(ctx, deserializedManifest.Config.Digest) if err != nil { glog.V(5).Infof("unable to access image config using digest %q for tag %q for repository %#v: %#v", desc.Digest, importTag.Name, repository, err) importTag.Err = formatRepositoryError(repository, importTag.Name, "", err) continue } importTag.Image, err = schema2ToImage(deserializedManifest, imageConfig, "") } else { glog.V(5).Infof("unsupported manifest type: %T", manifest) continue } if err != nil { importTag.Err = err continue } if err := api.ImageWithMetadata(importTag.Image); err != nil { importTag.Err = err continue } if importTag.Image.DockerImageMetadata.Size == 0 { if err := isi.calculateImageSize(ctx, repo, importTag.Image); err != nil { importTag.Err = err continue } } } }