func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { isi, ok := obj.(*api.ImageStreamImport) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamImport: %#v", obj)) } inputMeta := isi.ObjectMeta 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") } secrets, err := r.secrets.ImageStreamSecrets(namespace).Secrets(isi.Name, kapi.ListOptions{}) if err != nil { util.HandleError(fmt.Errorf("unable to load secrets for namespace %q: %v", namespace, err)) secrets = &kapi.SecretList{} } if r.clientFn != nil { if client := r.clientFn(); client != nil { ctx = kapi.WithValue(ctx, importer.ContextKeyV1RegistryClient, client) } } credentials := importer.NewCredentialsForSecrets(secrets.Items) importCtx := importer.NewContext(r.transport).WithCredentials(credentials) imports := r.importFn(importCtx) if err := imports.Import(ctx.(gocontext.Context), isi); err != nil { return nil, kapierrors.NewInternalError(err) } // TODO: perform the transformation of the image stream and return it with the ISI if import is false // so that clients can see what the resulting object would look like. if !isi.Spec.Import { clearManifests(isi) return isi, nil } create := false stream, err := r.streams.GetImageStream(ctx, isi.Name) if err != nil { if !kapierrors.IsNotFound(err) { return nil, err } // consistency check, stream must exist if len(inputMeta.ResourceVersion) > 0 || len(inputMeta.UID) > 0 { return nil, err } create = true stream = &api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: isi.Name, Namespace: namespace, Generation: 0, }, } } else { if len(inputMeta.ResourceVersion) > 0 && inputMeta.ResourceVersion != stream.ResourceVersion { glog.V(4).Infof("DEBUG: mismatch between requested UID %s and located UID %s", inputMeta.UID, stream.UID) return nil, kapierrors.NewConflict("imageStream", inputMeta.Name, fmt.Errorf("the image stream was updated from %q to %q", inputMeta.ResourceVersion, stream.ResourceVersion)) } if len(inputMeta.UID) > 0 && inputMeta.UID != stream.UID { glog.V(4).Infof("DEBUG: mismatch between requested UID %s and located UID %s", inputMeta.UID, stream.UID) return nil, kapierrors.NewNotFound("imageStream", inputMeta.Name) } } if stream.Annotations == nil { stream.Annotations = make(map[string]string) } now := unversioned.Now() stream.Annotations[api.DockerImageRepositoryCheckAnnotation] = now.UTC().Format(time.RFC3339) gen := stream.Generation + 1 zero := int64(0) importedImages := make(map[string]error) updatedImages := make(map[string]*api.Image) if spec := isi.Spec.Repository; spec != nil { for i, imageStatus := range isi.Status.Repository.Images { image := imageStatus.Image if image == nil { continue } // update the spec tag ref, err := api.ParseDockerImageReference(image.DockerImageReference) if err != nil { // ??? continue } tag := ref.Tag if len(imageStatus.Tag) > 0 { tag = imageStatus.Tag } if _, ok := stream.Spec.Tags[tag]; !ok { if stream.Spec.Tags == nil { stream.Spec.Tags = make(map[string]api.TagReference) } stream.Spec.Tags[tag] = api.TagReference{ From: &kapi.ObjectReference{ Kind: "DockerImage", Name: image.DockerImageReference, }, Generation: &gen, ImportPolicy: api.TagImportPolicy{Insecure: spec.ImportPolicy.Insecure}, } } // import or reuse the image importErr, imported := importedImages[image.Name] if importErr != nil { api.SetTagConditions(stream, tag, newImportFailedCondition(err, gen, now)) } pullSpec, _ := api.MostAccuratePullSpec(image.DockerImageReference, image.Name, "") api.AddTagEventToImageStream(stream, tag, api.TagEvent{ Created: now, DockerImageReference: pullSpec, Image: image.Name, Generation: gen, }) if imported { if updatedImage, ok := updatedImages[image.Name]; ok { isi.Status.Repository.Images[i].Image = updatedImage } continue } // establish the image into the store updated, err := r.images.Create(ctx, image) switch { case kapierrors.IsAlreadyExists(err): if err := api.ImageWithMetadata(image); err != nil { glog.V(4).Infof("Unable to update image metadata during image import when image already exists %q: err", image.Name, err) } updated = image fallthrough case err == nil: updatedImage := updated.(*api.Image) updatedImages[image.Name] = updatedImage isi.Status.Repository.Images[i].Image = updatedImage importedImages[image.Name] = nil default: importedImages[image.Name] = err } } } for i, spec := range isi.Spec.Images { if spec.To == nil { continue } tag := spec.To.Name if stream.Spec.Tags == nil { stream.Spec.Tags = make(map[string]api.TagReference) } specTag := stream.Spec.Tags[tag] from := spec.From specTag.From = &from specTag.Generation = &zero specTag.ImportPolicy.Insecure = spec.ImportPolicy.Insecure stream.Spec.Tags[tag] = specTag status := isi.Status.Images[i] if status.Image == nil || status.Status.Status == unversioned.StatusFailure { message := status.Status.Message if len(message) == 0 { message = "unknown error prevented import" } api.SetTagConditions(stream, tag, api.TagEventCondition{ Type: api.ImportSuccess, Status: kapi.ConditionFalse, Message: message, Reason: string(status.Status.Reason), Generation: gen, LastTransitionTime: now, }) continue } image := status.Image importErr, imported := importedImages[image.Name] if importErr != nil { api.SetTagConditions(stream, tag, newImportFailedCondition(err, gen, now)) } pullSpec, _ := api.MostAccuratePullSpec(image.DockerImageReference, image.Name, "") api.AddTagEventToImageStream(stream, tag, api.TagEvent{ Created: now, DockerImageReference: pullSpec, Image: image.Name, Generation: gen, }) if imported { continue } _, err = r.images.Create(ctx, image) if kapierrors.IsAlreadyExists(err) { err = nil } importedImages[image.Name] = err } // TODO: should we allow partial failure? for _, err := range importedImages { if err != nil { return nil, err } } clearManifests(isi) if create { obj, err = r.internalStreams.Create(ctx, stream) } else { obj, _, err = r.internalStreams.Update(ctx, stream) } if err != nil { return nil, err } isi.Status.Import = obj.(*api.ImageStream) return isi, nil }
func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { isi, ok := obj.(*api.ImageStreamImport) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("obj is not an ImageStreamImport: %#v", obj)) } inputMeta := isi.ObjectMeta 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") } if r.clientFn != nil { if client := r.clientFn(); client != nil { ctx = kapi.WithValue(ctx, importer.ContextKeyV1RegistryClient, client) } } // only load secrets if we need them credentials := importer.NewLazyCredentialsForSecrets(func() ([]kapi.Secret, error) { secrets, err := r.secrets.ImageStreamSecrets(namespace).Secrets(isi.Name, kapi.ListOptions{}) if err != nil { return nil, err } return secrets.Items, nil }) importCtx := importer.NewContext(r.transport, r.insecureTransport).WithCredentials(credentials) imports := r.importFn(importCtx) if err := imports.Import(ctx.(gocontext.Context), isi); err != nil { return nil, kapierrors.NewInternalError(err) } // if we encountered an error loading credentials and any images could not be retrieved with an access // related error, modify the message. // TODO: set a status cause if err := credentials.Err(); err != nil { for i, image := range isi.Status.Images { switch image.Status.Reason { case unversioned.StatusReasonUnauthorized, unversioned.StatusReasonForbidden: isi.Status.Images[i].Status.Message = fmt.Sprintf("Unable to load secrets for this image: %v; (%s)", err, image.Status.Message) } } if r := isi.Status.Repository; r != nil { switch r.Status.Reason { case unversioned.StatusReasonUnauthorized, unversioned.StatusReasonForbidden: r.Status.Message = fmt.Sprintf("Unable to load secrets for this repository: %v; (%s)", err, r.Status.Message) } } } // TODO: perform the transformation of the image stream and return it with the ISI if import is false // so that clients can see what the resulting object would look like. if !isi.Spec.Import { clearManifests(isi) return isi, nil } create := false stream, err := r.streams.GetImageStream(ctx, isi.Name) if err != nil { if !kapierrors.IsNotFound(err) { return nil, err } // consistency check, stream must exist if len(inputMeta.ResourceVersion) > 0 || len(inputMeta.UID) > 0 { return nil, err } create = true stream = &api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: isi.Name, Namespace: namespace, Generation: 0, }, } } else { if len(inputMeta.ResourceVersion) > 0 && inputMeta.ResourceVersion != stream.ResourceVersion { glog.V(4).Infof("DEBUG: mismatch between requested ResourceVersion %s and located ResourceVersion %s", inputMeta.ResourceVersion, stream.ResourceVersion) return nil, kapierrors.NewConflict(api.Resource("imagestream"), inputMeta.Name, fmt.Errorf("the image stream was updated from %q to %q", inputMeta.ResourceVersion, stream.ResourceVersion)) } if len(inputMeta.UID) > 0 && inputMeta.UID != stream.UID { glog.V(4).Infof("DEBUG: mismatch between requested UID %s and located UID %s", inputMeta.UID, stream.UID) return nil, kapierrors.NewNotFound(api.Resource("imagestream"), inputMeta.Name) } } if stream.Annotations == nil { stream.Annotations = make(map[string]string) } now := unversioned.Now() _, hasAnnotation := stream.Annotations[api.DockerImageRepositoryCheckAnnotation] nextGeneration := stream.Generation + 1 original, err := kapi.Scheme.DeepCopy(stream) if err != nil { return nil, err } // walk the retrieved images, ensuring each one exists in etcd importedImages := make(map[string]error) updatedImages := make(map[string]*api.Image) if spec := isi.Spec.Repository; spec != nil { for i, status := range isi.Status.Repository.Images { if checkImportFailure(status, stream, status.Tag, nextGeneration, now) { continue } image := status.Image ref, err := api.ParseDockerImageReference(image.DockerImageReference) if err != nil { utilruntime.HandleError(fmt.Errorf("unable to parse image reference during import: %v", err)) continue } from, err := api.ParseDockerImageReference(spec.From.Name) if err != nil { utilruntime.HandleError(fmt.Errorf("unable to parse from reference during import: %v", err)) continue } tag := ref.Tag if len(status.Tag) > 0 { tag = status.Tag } // we've imported a set of tags, ensure spec tag will point to this for later imports from.ID, from.Tag = "", tag if updated, ok := r.importSuccessful(ctx, image, stream, tag, from.Exact(), nextGeneration, now, spec.ImportPolicy, importedImages, updatedImages); ok { isi.Status.Repository.Images[i].Image = updated } } } for i, spec := range isi.Spec.Images { if spec.To == nil { continue } tag := spec.To.Name // record a failure condition status := isi.Status.Images[i] if checkImportFailure(status, stream, tag, nextGeneration, now) { // ensure that we have a spec tag set ensureSpecTag(stream, tag, spec.From.Name, spec.ImportPolicy, false) continue } // record success image := status.Image if updated, ok := r.importSuccessful(ctx, image, stream, tag, spec.From.Name, nextGeneration, now, spec.ImportPolicy, importedImages, updatedImages); ok { isi.Status.Images[i].Image = updated } } // TODO: should we allow partial failure? for _, err := range importedImages { if err != nil { return nil, err } } clearManifests(isi) hasChanges := !kapi.Semantic.DeepEqual(original, stream) if create { stream.Annotations[api.DockerImageRepositoryCheckAnnotation] = now.UTC().Format(time.RFC3339) glog.V(4).Infof("create new stream: %#v", stream) obj, err = r.internalStreams.Create(ctx, stream) } else { if hasAnnotation && !hasChanges { glog.V(4).Infof("stream did not change: %#v", stream) obj, err = original.(*api.ImageStream), nil } else { if glog.V(4) { glog.V(4).Infof("updated stream %s", diff.ObjectDiff(original, stream)) } stream.Annotations[api.DockerImageRepositoryCheckAnnotation] = now.UTC().Format(time.RFC3339) obj, _, err = r.internalStreams.Update(ctx, stream.Name, rest.DefaultUpdatedObjectInfo(stream, kapi.Scheme)) } } if err != nil { return nil, err } isi.Status.Import = obj.(*api.ImageStream) return isi, nil }
// WithRequestInfo returns a copy of parent in which the request info value is set func WithRequestInfo(parent api.Context, info *RequestInfo) api.Context { return api.WithValue(parent, requestInfoKey, info) }