// Create registers a new image (if it doesn't exist) and updates the specified ImageStream's tags. func (s *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { return nil, err } mapping := obj.(*api.ImageStreamMapping) stream, err := s.findStreamForMapping(ctx, mapping) if err != nil { return nil, err } image := mapping.Image tag := mapping.Tag if len(tag) == 0 { tag = api.DefaultImageTag } if err := s.imageRegistry.CreateImage(ctx, &image); err != nil && !errors.IsAlreadyExists(err) { return nil, err } next := api.TagEvent{ Created: util.Now(), DockerImageReference: image.DockerImageReference, Image: image.Name, } if !api.AddTagEventToImageStream(stream, tag, next) { // nothing actually changed return &kapi.Status{Status: kapi.StatusSuccess}, nil } api.UpdateTrackingTags(stream, tag, next) if _, err := s.imageStreamRegistry.UpdateImageStreamStatus(ctx, stream); err != nil { return nil, err } return &kapi.Status{Status: kapi.StatusSuccess}, nil }
// importSuccessful records a successful import into an image stream, setting the spec tag, status tag or conditions, and ensuring // the image is created in etcd. Images are cached so they are not created multiple times in a row (when multiple tags point to the // same image), and a failure to persist the image will be summarized before we update the stream. If an image was imported by this // operation, it *replaces* the imported image (from the remote repository) with the updated image. func (r *REST) importSuccessful( ctx kapi.Context, image *api.Image, stream *api.ImageStream, tag string, from string, nextGeneration int64, now unversioned.Time, importPolicy api.TagImportPolicy, importedImages map[string]error, updatedImages map[string]*api.Image, ) (*api.Image, bool) { Strategy.PrepareImageForCreate(image) pullSpec, _ := api.MostAccuratePullSpec(image.DockerImageReference, image.Name, "") tagEvent := api.TagEvent{ Created: now, DockerImageReference: pullSpec, Image: image.Name, Generation: nextGeneration, } if stream.Spec.Tags == nil { stream.Spec.Tags = make(map[string]api.TagReference) } // ensure the spec and status tag match the imported image changed := api.DifferentTagEvent(stream, tag, tagEvent) specTag, ok := stream.Spec.Tags[tag] if changed || !ok { specTag = ensureSpecTag(stream, tag, from, importPolicy, true) api.AddTagEventToImageStream(stream, tag, tagEvent) } // always reset the import policy specTag.ImportPolicy = importPolicy stream.Spec.Tags[tag] = specTag // import or reuse the image, and ensure tag conditions are set importErr, alreadyImported := importedImages[image.Name] if importErr != nil { api.SetTagConditions(stream, tag, newImportFailedCondition(importErr, nextGeneration, now)) } else { api.SetTagConditions(stream, tag) } // create the image if it does not exist, otherwise cache the updated status from the store for use by other tags if alreadyImported { if updatedImage, ok := updatedImages[image.Name]; ok { return updatedImage, true } return nil, false } 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 return updatedImage, true default: importedImages[image.Name] = err } return nil, false }
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 }
// tagsChanged updates stream.Status.Tags based on the old and new image stream. // if the old stream is nil, all tags are considered additions. func (s Strategy) tagsChanged(old, stream *api.ImageStream) fielderrors.ValidationErrorList { var errs fielderrors.ValidationErrorList oldTags := map[string]api.TagReference{} if old != nil && old.Spec.Tags != nil { oldTags = old.Spec.Tags } for tag, tagRef := range stream.Spec.Tags { if oldRef, ok := oldTags[tag]; ok && !tagRefChanged(oldRef, tagRef, stream.Namespace) { continue } if tagRef.From == nil { continue } if tagRef.From.Kind == "DockerImage" && len(tagRef.From.Name) > 0 { if tagRef.Reference { event, err := tagReferenceToTagEvent(stream, tagRef, "") if err != nil { errs = append(errs, fielderrors.NewFieldInvalid(fmt.Sprintf("spec.tags[%s].from", tag), tagRef.From, err.Error())) continue } api.AddTagEventToImageStream(stream, tag, *event) } continue } tagRefStreamName, tagOrID, err := parseFromReference(stream, tagRef.From) if err != nil { errs = append(errs, fielderrors.NewFieldInvalid(fmt.Sprintf("spec.tags[%s].from.name", tag), tagRef.From.Name, "must be of the form <tag>, <repo>:<tag>, <id>, or <repo>@<id>")) continue } streamRef := stream streamRefNamespace := tagRef.From.Namespace if len(streamRefNamespace) == 0 { streamRefNamespace = stream.Namespace } if streamRefNamespace != stream.Namespace || tagRefStreamName != stream.Name { obj, err := s.ImageStreamGetter.Get(kapi.WithNamespace(kapi.NewContext(), streamRefNamespace), tagRefStreamName) if err != nil { if kerrors.IsNotFound(err) { errs = append(errs, fielderrors.NewFieldNotFound(fmt.Sprintf("spec.tags[%s].from.name", tag), tagRef.From.Name)) } else { errs = append(errs, fielderrors.NewFieldInvalid(fmt.Sprintf("spec.tags[%s].from.name", tag), tagRef.From.Name, fmt.Sprintf("unable to retrieve image stream: %v", err))) } continue } streamRef = obj.(*api.ImageStream) } event, err := tagReferenceToTagEvent(streamRef, tagRef, tagOrID) if err != nil { errs = append(errs, fielderrors.NewFieldInvalid(fmt.Sprintf("spec.tags[%s].from.name", tag), tagRef.From.Name, fmt.Sprintf("error generating tag event: %v", err))) continue } if event == nil { // referenced tag or ID doesn't exist, which is ok continue } api.AddTagEventToImageStream(stream, tag, *event) } if old != nil { api.UpdateChangedTrackingTags(stream, old) } // use a consistent timestamp on creation if old == nil && !stream.CreationTimestamp.IsZero() { for tag, list := range stream.Status.Tags { for _, event := range list.Items { event.Created = stream.CreationTimestamp } stream.Status.Tags[tag] = list } } return errs }
// Create registers a new image (if it doesn't exist) and updates the // specified ImageStream's tags. If attempts to update the ImageStream fail // with a resource conflict, the update will be retried if the newer // ImageStream has no tag diffs from the previous state. If tag diffs are // detected, the conflict error is returned. func (s *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { return nil, err } mapping := obj.(*api.ImageStreamMapping) stream, err := s.findStreamForMapping(ctx, mapping) if err != nil { return nil, err } image := mapping.Image tag := mapping.Tag if len(tag) == 0 { tag = api.DefaultImageTag } if err := s.imageRegistry.CreateImage(ctx, &image); err != nil && !errors.IsAlreadyExists(err) { return nil, err } next := api.TagEvent{ Created: unversioned.Now(), DockerImageReference: image.DockerImageReference, Image: image.Name, } err = wait.ExponentialBackoff(wait.Backoff{Steps: maxRetriesOnConflict}, func() (bool, error) { lastEvent := api.LatestTaggedImage(stream, tag) next.Generation = stream.Generation if !api.AddTagEventToImageStream(stream, tag, next) { // nothing actually changed return true, nil } api.UpdateTrackingTags(stream, tag, next) _, err := s.imageStreamRegistry.UpdateImageStreamStatus(ctx, stream) if err == nil { return true, nil } if !errors.IsConflict(err) { return false, err } // If the update conflicts, get the latest stream and check for tag // updates. If the latest tag hasn't changed, retry. latestStream, findLatestErr := s.findStreamForMapping(ctx, mapping) if findLatestErr != nil { return false, findLatestErr } // no previous tag if lastEvent == nil { // The tag hasn't changed, so try again with the updated stream. stream = latestStream return false, nil } // check for tag change newerEvent := api.LatestTaggedImage(latestStream, tag) // generation and creation time differences are ignored lastEvent.Generation = newerEvent.Generation lastEvent.Created = newerEvent.Created if kapi.Semantic.DeepEqual(lastEvent, newerEvent) { // The tag hasn't changed, so try again with the updated stream. stream = latestStream return false, nil } // The tag changed, so return the conflict error back to the client. return false, err }) if err != nil { return nil, err } return &unversioned.Status{Status: unversioned.StatusSuccess}, nil }
// tagsChanged updates stream.Status.Tags based on the old and new image stream. // if the old stream is nil, all tags are considered additions. func (s Strategy) tagsChanged(old, stream *api.ImageStream) field.ErrorList { internalRegistry, hasInternalRegistry := s.defaultRegistry.DefaultRegistry() var errs field.ErrorList oldTags := map[string]api.TagReference{} if old != nil && old.Spec.Tags != nil { oldTags = old.Spec.Tags } for tag, tagRef := range stream.Spec.Tags { if oldRef, ok := oldTags[tag]; ok && !tagRefChanged(oldRef, tagRef, stream.Namespace) { continue } if tagRef.From == nil { continue } glog.V(5).Infof("Detected changed tag %s in %s/%s", tag, stream.Namespace, stream.Name) generation := stream.Generation tagRef.Generation = &generation fromPath := field.NewPath("spec", "tags").Key(tag).Child("from") if tagRef.From.Kind == "DockerImage" && len(tagRef.From.Name) > 0 { if tagRef.Reference { event, err := tagReferenceToTagEvent(stream, tagRef, "") if err != nil { errs = append(errs, field.Invalid(fromPath, tagRef.From, err.Error())) continue } stream.Spec.Tags[tag] = tagRef api.AddTagEventToImageStream(stream, tag, *event) } continue } tagRefStreamName, tagOrID, err := parseFromReference(stream, tagRef.From) if err != nil { errs = append(errs, field.Invalid(fromPath.Child("name"), tagRef.From.Name, "must be of the form <tag>, <repo>:<tag>, <id>, or <repo>@<id>")) continue } streamRef := stream streamRefNamespace := tagRef.From.Namespace if len(streamRefNamespace) == 0 { streamRefNamespace = stream.Namespace } if streamRefNamespace != stream.Namespace || tagRefStreamName != stream.Name { obj, err := s.ImageStreamGetter.Get(kapi.WithNamespace(kapi.NewContext(), streamRefNamespace), tagRefStreamName) if err != nil { if kerrors.IsNotFound(err) { errs = append(errs, field.NotFound(fromPath.Child("name"), tagRef.From.Name)) } else { errs = append(errs, field.Invalid(fromPath.Child("name"), tagRef.From.Name, fmt.Sprintf("unable to retrieve image stream: %v", err))) } continue } streamRef = obj.(*api.ImageStream) } event, err := tagReferenceToTagEvent(streamRef, tagRef, tagOrID) if err != nil { errs = append(errs, field.Invalid(fromPath.Child("name"), tagRef.From.Name, fmt.Sprintf("error generating tag event: %v", err))) continue } if event == nil { // referenced tag or ID doesn't exist, which is ok continue } // if this is not a reference tag, and the tag points to the internal registry for the other namespace, alter it to // point to this stream so that pulls happen from this stream in the future. if !tagRef.Reference { if ref, err := api.ParseDockerImageReference(event.DockerImageReference); err == nil { if hasInternalRegistry && ref.Registry == internalRegistry && ref.Namespace == streamRef.Namespace && ref.Name == streamRef.Name { ref.Namespace = stream.Namespace ref.Name = stream.Name event.DockerImageReference = ref.Exact() } } } stream.Spec.Tags[tag] = tagRef api.AddTagEventToImageStream(stream, tag, *event) } api.UpdateChangedTrackingTags(stream, old) // use a consistent timestamp on creation if old == nil && !stream.CreationTimestamp.IsZero() { for tag, list := range stream.Status.Tags { for _, event := range list.Items { event.Created = stream.CreationTimestamp } stream.Status.Tags[tag] = list } } return errs }