func getTags(stream *imageapi.ImageStream, image *imageapi.Image) []string { tags := []string{} for tag, history := range stream.Status.Tags { if history.Items[0].Image == image.Name { tags = append(tags, tag) } } imageapi.PrioritizeTags(tags) return tags }
func Convert_api_TagEventListArray_to_v1_NamedTagEventListArray(in *map[string]newer.TagEventList, out *[]NamedTagEventList, s conversion.Scope) error { allKeys := make([]string, 0, len(*in)) for key := range *in { allKeys = append(allKeys, key) } newer.PrioritizeTags(allKeys) for _, key := range allKeys { newTagEventList := (*in)[key] oldTagEventList := &NamedTagEventList{Tag: key} if err := s.Convert(&newTagEventList.Conditions, &oldTagEventList.Conditions, 0); err != nil { return err } if err := s.Convert(&newTagEventList.Items, &oldTagEventList.Items, 0); err != nil { return err } *out = append(*out, *oldTagEventList) } return nil }
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 } } } }
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 } fmt.Fprint(out, "\nTag\tSpec\tCreated\tPullSpec\tImage\n") sortedTags := []string{} for k := range stream.Status.Tags { sortedTags = append(sortedTags, k) } for k := range stream.Spec.Tags { if _, ok := stream.Status.Tags[k]; !ok { sortedTags = append(sortedTags, k) } } hasScheduled, hasInsecure := false, false imageapi.PrioritizeTags(sortedTags) for _, tag := range sortedTags { tagRef, ok := stream.Spec.Tags[tag] specTag := "" scheduled := false insecure := false if ok { if tagRef.From != nil { namePair := "" if len(tagRef.From.Namespace) > 0 && tagRef.From.Namespace != stream.Namespace { namePair = fmt.Sprintf("%s/%s", tagRef.From.Namespace, tagRef.From.Name) } else { namePair = tagRef.From.Name } switch tagRef.From.Kind { case "ImageStreamTag", "ImageStreamImage": specTag = namePair case "DockerImage": specTag = tagRef.From.Name default: specTag = fmt.Sprintf("<unknown %s> %s", tagRef.From.Kind, namePair) } } scheduled, insecure = tagRef.ImportPolicy.Scheduled, tagRef.ImportPolicy.Insecure hasScheduled = hasScheduled || scheduled hasInsecure = hasScheduled || insecure } else { specTag = "<pushed>" } if taglist, ok := stream.Status.Tags[tag]; ok { if len(taglist.Conditions) > 0 { var lastTime time.Time summary := []string{} for _, condition := range taglist.Conditions { if condition.LastTransitionTime.After(lastTime) { lastTime = condition.LastTransitionTime.Time } switch condition.Type { case imageapi.ImportSuccess: if condition.Status == api.ConditionFalse { summary = append(summary, fmt.Sprintf("import failed: %s", condition.Message)) } default: summary = append(summary, string(condition.Type)) } } if len(summary) > 0 { description := strings.Join(summary, ", ") if len(description) > 70 { description = strings.TrimSpace(description[:70-3]) + "..." } d := timeNowFn().Sub(lastTime) fmt.Fprintf(out, "%s\t%s\t%s ago\t%s\t%v\n", tag, shortenImagePullSpec(specTag), units.HumanDuration(d), "", description) } } for i, event := range taglist.Items { d := timeNowFn().Sub(event.Created.Time) image := event.Image ref, err := imageapi.ParseDockerImageReference(event.DockerImageReference) if err == nil { if ref.ID == image { image = "<same>" } } pullSpec := event.DockerImageReference if pullSpec == specTag { pullSpec = "<same>" } else { pullSpec = shortenImagePullSpec(pullSpec) } specTag = shortenImagePullSpec(specTag) if i != 0 { tag, specTag = "", "" } else { extra := "" if scheduled { extra += "*" } if insecure { extra += "!" } if len(extra) > 0 { specTag += " " + extra } } fmt.Fprintf(out, "%s\t%s\t%s ago\t%s\t%v\n", tag, specTag, units.HumanDuration(d), pullSpec, image) } } else { fmt.Fprintf(out, "%s\t%s\t\t<not available>\t<not available>\n", tag, specTag) } } if hasInsecure || hasScheduled { fmt.Fprintln(out) if hasScheduled { fmt.Fprintf(out, " * tag is scheduled for periodic import\n") } if hasInsecure { fmt.Fprintf(out, " ! tag is insecure and can be imported over HTTP or self-signed HTTPS\n") } } }
// 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 importRepositoryFromDocker(ctx gocontext.Context, retriever RepositoryRetriever, repository *importRepository, limiter util.RateLimiter) { // 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 isDockerError(err, v2.ErrorCodeNameUnknown): err = kapierrors.NewNotFound("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("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 } // 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 := s.Tags() 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("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() m, err := s.Get(d) if err != nil { glog.V(5).Infof("unable to access digest %q for repository %#v: %#v", d, repository, err) switch { case isDockerError(err, v2.ErrorCodeManifestUnknown): ref := repository.Ref ref.Tag, ref.ID = "", importDigest.Name err = kapierrors.NewNotFound("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())) } importDigest.Err = err continue } importDigest.Image, err = schema1ToImage(m, d) if err != nil { importDigest.Err = err continue } if err := api.ImageWithMetadata(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() m, err := s.GetByTag(importTag.Name) if err != nil { glog.V(5).Infof("unable to access tag %q for repository %#v: %#v", importTag.Name, repository, err) switch { case isDockerError(err, v2.ErrorCodeManifestUnknown): ref := repository.Ref ref.Tag = importTag.Name err = kapierrors.NewNotFound("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())) } importTag.Err = err continue } importTag.Image, err = schema1ToImage(m, "") if err != nil { importTag.Err = err continue } if err := api.ImageWithMetadata(importTag.Image); err != nil { importTag.Err = err continue } } }
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)) } } } }