func (o *ImportImageOptions) importTag(stream *imageapi.ImageStream) (*imageapi.ImageStreamImport, error) { from := o.From tag := o.Tag // follow any referential tags to the destination finalTag, existing, ok, multiple := imageapi.FollowTagReference(stream, tag) if !ok && multiple { return nil, fmt.Errorf("tag %q on the image stream is a reference to %q, which does not exist", tag, finalTag) } // update ImageStream appropriately if ok { // disallow re-importing anything other than DockerImage if existing.From != nil && existing.From.Kind != "DockerImage" { return nil, fmt.Errorf("tag %q points to existing %s %q, it cannot be re-imported", tag, existing.From.Kind, existing.From.Name) } // disallow changing an existing tag if existing.From == nil { return nil, fmt.Errorf("tag %q already exists - you must use the 'tag' command if you want to change the source to %q", tag, from) } if len(from) != 0 && from != existing.From.Name { if multiple { return nil, fmt.Errorf("the tag %q points to the tag %q which points to %q - use the 'tag' command if you want to change the source to %q", tag, finalTag, existing.From.Name, from) } return nil, fmt.Errorf("the tag %q points to %q - use the 'tag' command if you want to change the source to %q", tag, existing.From.Name, from) } // set the target item to import from = existing.From.Name if multiple { tag = finalTag } // clear the legacy annotation delete(existing.Annotations, imageapi.DockerImageRepositoryCheckAnnotation) // reset the generation zero := int64(0) existing.Generation = &zero } else { // create a new tag if len(from) == 0 { from = stream.Spec.DockerImageRepository } // if the from is still empty this means there's no such tag defined // nor we can't create any from .spec.dockerImageRepository if len(from) == 0 { return nil, fmt.Errorf("the tag %q does not exist on the image stream - choose an existing tag to import or use the 'tag' command to create a new tag", tag) } existing = &imageapi.TagReference{ From: &kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, } } stream.Spec.Tags[tag] = *existing // and create accompanying ImageStreamImport return o.newImageStreamImportTags(stream, map[string]string{tag: from}), nil }
// RunImportImage contains all the necessary functionality for the OpenShift cli import-image command. func RunImportImage(f *clientcmd.Factory, out io.Writer, cmd *cobra.Command, args []string) error { if len(args) == 0 || len(args[0]) == 0 { return cmdutil.UsageError(cmd, "you must specify the name of an image stream") } target := args[0] namespace, _, err := f.DefaultNamespace() if err != nil { return err } osClient, _, err := f.Clients() if err != nil { return err } insecure := cmdutil.GetFlagBool(cmd, "insecure") from := cmdutil.GetFlagString(cmd, "from") confirm := cmdutil.GetFlagBool(cmd, "confirm") all := cmdutil.GetFlagBool(cmd, "all") targetRef, err := imageapi.ParseDockerImageReference(target) switch { case err != nil: return fmt.Errorf("the image name must be a valid Docker image pull spec or reference to an image stream (e.g. myregistry/myteam/image:tag)") case len(targetRef.ID) > 0: return fmt.Errorf("to import images by ID, use the 'tag' command") case len(targetRef.Tag) != 0 && all: // error out return fmt.Errorf("cannot specify a tag %q as well as --all", target) case len(targetRef.Tag) == 0 && !all: // apply the default tag targetRef.Tag = imageapi.DefaultImageTag } name := targetRef.Name tag := targetRef.Tag imageStreamClient := osClient.ImageStreams(namespace) stream, err := imageStreamClient.Get(name) if err != nil { if !errors.IsNotFound(err) { return err } // the stream is new if !confirm { return fmt.Errorf("no image stream named %q exists, pass --confirm to create and import", name) } if len(from) == 0 { from = target } if all { stream = &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{Name: name}, Spec: imageapi.ImageStreamSpec{DockerImageRepository: from}, } } else { stream = &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{Name: name}, Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ tag: { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, }, }, }, } } } else { // the stream already exists if len(stream.Spec.DockerImageRepository) == 0 && len(stream.Spec.Tags) == 0 { return fmt.Errorf("image stream has not defined anything to import") } if all { // importing a whole repository if len(from) == 0 { from = target } if from != stream.Spec.DockerImageRepository { if !confirm { if len(stream.Spec.DockerImageRepository) == 0 { return fmt.Errorf("the image stream does not currently import an entire Docker repository, pass --confirm to update") } return fmt.Errorf("the image stream has a different import spec %q, pass --confirm to update", stream.Spec.DockerImageRepository) } stream.Spec.DockerImageRepository = from } } else { // importing a single tag // follow any referential tags to the destination finalTag, existing, ok, multiple := imageapi.FollowTagReference(stream, tag) if !ok && multiple { return fmt.Errorf("tag %q on the image stream is a reference to %q, which does not exist", tag, finalTag) } if ok { // disallow changing an existing tag if existing.From == nil || existing.From.Kind != "DockerImage" { return fmt.Errorf("tag %q already exists - you must use the 'tag' command if you want to change the source to %q", tag, from) } if len(from) != 0 && from != existing.From.Name { if multiple { return fmt.Errorf("the tag %q points to the tag %q which points to %q - use the 'tag' command if you want to change the source to %q", tag, finalTag, existing.From.Name, from) } return fmt.Errorf("the tag %q points to %q - use the 'tag' command if you want to change the source to %q", tag, existing.From.Name, from) } // set the target item to import from = existing.From.Name if multiple { tag = finalTag } // clear the legacy annotation delete(existing.Annotations, imageapi.DockerImageRepositoryCheckAnnotation) // reset the generation zero := int64(0) existing.Generation = &zero } else { // create a new tag if len(from) == 0 { from = target } existing = &imageapi.TagReference{ From: &kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, } } stream.Spec.Tags[tag] = *existing } } if len(from) == 0 { // catch programmer error return fmt.Errorf("unexpected error, from is empty") } // Attempt the new, direct import path isi := &imageapi.ImageStreamImport{ ObjectMeta: kapi.ObjectMeta{ Name: stream.Name, Namespace: namespace, ResourceVersion: stream.ResourceVersion, }, Spec: imageapi.ImageStreamImportSpec{Import: true}, } if all { isi.Spec.Repository = &imageapi.RepositoryImportSpec{ From: kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, ImportPolicy: imageapi.TagImportPolicy{Insecure: insecure}, } } else { isi.Spec.Images = append(isi.Spec.Images, imageapi.ImageImportSpec{ From: kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, To: &kapi.LocalObjectReference{Name: tag}, ImportPolicy: imageapi.TagImportPolicy{Insecure: insecure}, }) } // TODO: add dry-run _, err = imageStreamClient.Import(isi) switch { case err == client.ErrImageStreamImportUnsupported: case err != nil: return err default: fmt.Fprint(cmd.Out(), "The import completed successfully.\n\n") // optimization, use the image stream returned by the call d := describe.ImageStreamDescriber{Interface: osClient} info, err := d.Describe(namespace, stream.Name) if err != nil { return err } fmt.Fprintln(out, info) return nil } // Legacy path, remove when support for older importers is removed delete(stream.Annotations, imageapi.DockerImageRepositoryCheckAnnotation) if insecure { if stream.Annotations == nil { stream.Annotations = make(map[string]string) } stream.Annotations[imageapi.InsecureRepositoryAnnotation] = "true" } if stream.CreationTimestamp.IsZero() { stream, err = imageStreamClient.Create(stream) } else { stream, err = imageStreamClient.Update(stream) } if err != nil { return err } resourceVersion := stream.ResourceVersion fmt.Fprintln(cmd.Out(), "Importing (ctrl+c to stop waiting) ...") updatedStream, err := waitForImport(imageStreamClient, stream.Name, resourceVersion) if err != nil { if _, ok := err.(importError); ok { return err } return fmt.Errorf("unable to determine if the import completed successfully - please run 'oc describe -n %s imagestream/%s' to see if the tags were updated as expected: %v", stream.Namespace, stream.Name, err) } fmt.Fprint(cmd.Out(), "The import completed successfully.\n\n") d := describe.ImageStreamDescriber{Interface: osClient} info, err := d.Describe(updatedStream.Namespace, updatedStream.Name) if err != nil { return err } fmt.Fprintln(out, info) return nil }
func formatImageStreamTags(out *tabwriter.Writer, stream *imageapi.ImageStream) { if len(stream.Status.Tags) == 0 && len(stream.Spec.Tags) == 0 { fmt.Fprintf(out, "Tags:\t<none>\n") return } now := timeNowFn() images := make(map[string]string) for tag, tags := range stream.Status.Tags { for _, item := range tags.Items { switch { case len(item.Image) > 0: if _, ok := images[item.Image]; !ok { images[item.Image] = tag } case len(item.DockerImageReference) > 0: if _, ok := images[item.DockerImageReference]; !ok { images[item.Image] = item.DockerImageReference } } } } sortedTags := []string{} for k := range stream.Status.Tags { sortedTags = append(sortedTags, k) } var localReferences sets.String var referentialTags map[string]sets.String for k := range stream.Spec.Tags { if target, _, ok, multiple := imageapi.FollowTagReference(stream, k); ok && multiple { if referentialTags == nil { referentialTags = make(map[string]sets.String) } if localReferences == nil { localReferences = sets.NewString() } localReferences.Insert(k) v := referentialTags[target] if v == nil { v = sets.NewString() referentialTags[target] = v } v.Insert(k) } if _, ok := stream.Status.Tags[k]; !ok { sortedTags = append(sortedTags, k) } } fmt.Fprintf(out, "Unique Images:\t%d\nTags:\t%d\n\n", len(images), len(sortedTags)) first := true imageapi.PrioritizeTags(sortedTags) for _, tag := range sortedTags { if localReferences.Has(tag) { continue } if first { first = false } else { fmt.Fprintf(out, "\n") } taglist, _ := stream.Status.Tags[tag] tagRef, hasSpecTag := stream.Spec.Tags[tag] scheduled := false insecure := false importing := false var name string if hasSpecTag && tagRef.From != nil { if len(tagRef.From.Namespace) > 0 && tagRef.From.Namespace != stream.Namespace { name = fmt.Sprintf("%s/%s", tagRef.From.Namespace, tagRef.From.Name) } else { name = tagRef.From.Name } scheduled, insecure = tagRef.ImportPolicy.Scheduled, tagRef.ImportPolicy.Insecure gen := imageapi.LatestObservedTagGeneration(stream, tag) importing = !tagRef.Reference && tagRef.Generation != nil && *tagRef.Generation != gen } // updates whenever tag :5.2 is changed // :latest (30 minutes ago) -> 102.205.358.453/foo/bar@sha256:abcde734 // error: last import failed 20 minutes ago // updates automatically from index.docker.io/mysql/bar // will use insecure HTTPS connections or HTTP // // MySQL 5.5 // --------- // Describes a system for updating based on practical changes to a database system // with some other data involved // // 20 minutes ago <import failed> // Failed to locate the server in time // 30 minutes ago 102.205.358.453/foo/bar@sha256:abcdef // 1 hour ago 102.205.358.453/foo/bar@sha256:bfedfc //var shortErrors []string /* var internalReference *imageapi.DockerImageReference if value := stream.Status.DockerImageRepository; len(value) > 0 { ref, err := imageapi.ParseDockerImageReference(value) if err != nil { internalReference = &ref } } */ if referentialTags[tag].Len() > 0 { references := referentialTags[tag].List() imageapi.PrioritizeTags(references) fmt.Fprintf(out, "%s (%s)\n", tag, strings.Join(references, ", ")) } else { fmt.Fprintf(out, "%s\n", tag) } switch { case !hasSpecTag || tagRef.From == nil: fmt.Fprintf(out, " pushed image\n") case tagRef.From.Kind == "ImageStreamTag": switch { case tagRef.Reference: fmt.Fprintf(out, " reference to %s\n", name) case scheduled: fmt.Fprintf(out, " updates automatically from %s\n", name) default: fmt.Fprintf(out, " tagged from %s\n", name) } case tagRef.From.Kind == "DockerImage": switch { case tagRef.Reference: fmt.Fprintf(out, " reference to registry %s\n", name) case scheduled: fmt.Fprintf(out, " updates automatically from registry %s\n", name) default: fmt.Fprintf(out, " tagged from %s\n", name) } case tagRef.From.Kind == "ImageStreamImage": switch { case tagRef.Reference: fmt.Fprintf(out, " reference to image %s\n", name) default: fmt.Fprintf(out, " tagged from %s\n", name) } default: switch { case tagRef.Reference: fmt.Fprintf(out, " reference to %s %s\n", tagRef.From.Kind, name) default: fmt.Fprintf(out, " updates from %s %s\n", tagRef.From.Kind, name) } } if insecure { fmt.Fprintf(out, " will use insecure HTTPS or HTTP connections\n") } fmt.Fprintln(out) extraOutput := false if d := tagRef.Annotations["description"]; len(d) > 0 { fmt.Fprintf(out, " %s\n", d) extraOutput = true } if t := tagRef.Annotations["tags"]; len(t) > 0 { fmt.Fprintf(out, " Tags: %s\n", strings.Join(strings.Split(t, ","), ", ")) extraOutput = true } if t := tagRef.Annotations["supports"]; len(t) > 0 { fmt.Fprintf(out, " Supports: %s\n", strings.Join(strings.Split(t, ","), ", ")) extraOutput = true } if t := tagRef.Annotations["sampleRepo"]; len(t) > 0 { fmt.Fprintf(out, " Example Repo: %s\n", t) extraOutput = true } if extraOutput { fmt.Fprintln(out) } if importing { fmt.Fprintf(out, " ~ importing latest image ...\n") } for i := range taglist.Conditions { condition := &taglist.Conditions[i] switch condition.Type { case imageapi.ImportSuccess: if condition.Status == api.ConditionFalse { d := now.Sub(condition.LastTransitionTime.Time) fmt.Fprintf(out, " ! error: Import failed (%s): %s\n %s ago\n", condition.Reason, condition.Message, units.HumanDuration(d)) } } } if len(taglist.Items) == 0 { continue } for i, event := range taglist.Items { d := now.Sub(event.Created.Time) if i == 0 { fmt.Fprintf(out, " * %s\n", event.DockerImageReference) } else { fmt.Fprintf(out, " %s\n", event.DockerImageReference) } ref, err := imageapi.ParseDockerImageReference(event.DockerImageReference) id := event.Image if len(id) > 0 && err == nil && ref.ID != id { fmt.Fprintf(out, " %s ago\t%s\n", units.HumanDuration(d), id) } else { fmt.Fprintf(out, " %s ago\n", units.HumanDuration(d)) } } } }
func (o *ImportImageOptions) createImageImport() (*imageapi.ImageStream, *imageapi.ImageStreamImport, error) { stream, err := o.isClient.Get(o.Name) from := o.From tag := o.Tag if err != nil { if !errors.IsNotFound(err) { return nil, nil, err } // the stream is new if !o.Confirm { return nil, nil, fmt.Errorf("no image stream named %q exists, pass --confirm to create and import", o.Name) } if len(from) == 0 { from = o.Target } if o.All { stream = &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{Name: o.Name}, Spec: imageapi.ImageStreamSpec{DockerImageRepository: from}, } } else { stream = &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{Name: o.Name}, Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ tag: { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, }, }, }, } } } else { // the stream already exists if len(stream.Spec.DockerImageRepository) == 0 && len(stream.Spec.Tags) == 0 { return nil, nil, fmt.Errorf("image stream has not defined anything to import") } if o.All { // importing a whole repository // TODO soltysh: write tests to cover all the possible usecases!!! if len(from) == 0 { if len(stream.Spec.DockerImageRepository) == 0 { // FIXME soltysh: return nil, nil, fmt.Errorf("flag --all is applicable only to images with spec.dockerImageRepository defined") } from = stream.Spec.DockerImageRepository } if from != stream.Spec.DockerImageRepository { if !o.Confirm { if len(stream.Spec.DockerImageRepository) == 0 { return nil, nil, fmt.Errorf("the image stream does not currently import an entire Docker repository, pass --confirm to update") } return nil, nil, fmt.Errorf("the image stream has a different import spec %q, pass --confirm to update", stream.Spec.DockerImageRepository) } stream.Spec.DockerImageRepository = from } } else { // importing a single tag // follow any referential tags to the destination finalTag, existing, ok, multiple := imageapi.FollowTagReference(stream, tag) if !ok && multiple { return nil, nil, fmt.Errorf("tag %q on the image stream is a reference to %q, which does not exist", tag, finalTag) } if ok { // disallow changing an existing tag if existing.From == nil || existing.From.Kind != "DockerImage" { return nil, nil, fmt.Errorf("tag %q already exists - you must use the 'tag' command if you want to change the source to %q", tag, from) } if len(from) != 0 && from != existing.From.Name { if multiple { return nil, nil, fmt.Errorf("the tag %q points to the tag %q which points to %q - use the 'tag' command if you want to change the source to %q", tag, finalTag, existing.From.Name, from) } return nil, nil, fmt.Errorf("the tag %q points to %q - use the 'tag' command if you want to change the source to %q", tag, existing.From.Name, from) } // set the target item to import from = existing.From.Name if multiple { tag = finalTag } // clear the legacy annotation delete(existing.Annotations, imageapi.DockerImageRepositoryCheckAnnotation) // reset the generation zero := int64(0) existing.Generation = &zero } else { // create a new tag if len(from) == 0 { from = stream.Spec.DockerImageRepository } existing = &imageapi.TagReference{ From: &kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, } } stream.Spec.Tags[tag] = *existing } } if len(from) == 0 { // catch programmer error return nil, nil, fmt.Errorf("unexpected error, from is empty") } // Attempt the new, direct import path isi := &imageapi.ImageStreamImport{ ObjectMeta: kapi.ObjectMeta{ Name: stream.Name, Namespace: o.Namespace, ResourceVersion: stream.ResourceVersion, }, Spec: imageapi.ImageStreamImportSpec{Import: true}, } if o.All { isi.Spec.Repository = &imageapi.RepositoryImportSpec{ From: kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, ImportPolicy: imageapi.TagImportPolicy{Insecure: o.Insecure}, } } else { isi.Spec.Images = append(isi.Spec.Images, imageapi.ImageImportSpec{ From: kapi.ObjectReference{ Kind: "DockerImage", Name: from, }, To: &kapi.LocalObjectReference{Name: tag}, ImportPolicy: imageapi.TagImportPolicy{Insecure: o.Insecure}, }) } return stream, isi, nil }