func TestRegistryClientImage(t *testing.T) { for _, v2 := range []bool{true, false} { host := "index.docker.io" if !v2 { host = "registry.hub.docker.com" } conn, err := dockerregistry.NewClient().Connect(host, false) if err != nil { t.Fatal(err) } if _, err := conn.ImageByTag("openshift", "origin-not-found", "latest"); !dockerregistry.IsRepositoryNotFound(err) && !dockerregistry.IsTagNotFound(err) { t.Errorf("V2=%t: unexpected error: %v", v2, err) } image, err := conn.ImageByTag("openshift", "origin", "latest") if err != nil { t.Fatalf("V2=%t: %v", v2, err) } if len(image.ContainerConfig.Entrypoint) == 0 { t.Errorf("V2=%t: unexpected image: %#v", v2, image) } if v2 && !image.PullByID { t.Errorf("V2=%t: should be able to pull by ID %s", v2, image.ID) } other, err := conn.ImageByID("openshift", "origin", image.ID) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(other.ContainerConfig.Entrypoint, image.ContainerConfig.Entrypoint) { t.Errorf("V2=%t: unexpected image: %#v", v2, other) } } }
// importTag import single tag from given ImageStream. Returns retrieved image (for later reuse), // a flag saying if we should retry imports and an error if one occurs. func (c *ImportController) importTag(stream *api.ImageStream, tag string, ref api.DockerImageReference, dockerImage *dockerregistry.Image, client dockerregistry.Client, insecure bool) (*dockerregistry.Image, bool, error) { glog.V(5).Infof("Importing tag %s from %s/%s...", tag, stream.Namespace, stream.Name) if dockerImage == nil { // TODO insecure applies to the stream's spec.dockerImageRepository, not necessarily to an external one! conn, err := client.Connect(ref.Registry, insecure) if err != nil { // retry-able error no. 3 return nil, true, err } if len(ref.ID) > 0 { dockerImage, err = conn.ImageByID(ref.Namespace, ref.Name, ref.ID) } else { dockerImage, err = conn.ImageByTag(ref.Namespace, ref.Name, ref.Tag) } switch { case dockerregistry.IsRepositoryNotFound(err), dockerregistry.IsRegistryNotFound(err), dockerregistry.IsImageNotFound(err), dockerregistry.IsTagNotFound(err): return nil, false, err case err != nil: // retry-able error no. 4 return nil, true, err } } var image api.DockerImage if err := kapi.Scheme.Convert(&dockerImage.Image, &image); err != nil { return nil, false, fmt.Errorf("could not convert image: %#v", err) } // prefer to pull by ID always if dockerImage.PullByID { // if the registry indicates the image is pullable by ID, clear the tag ref.Tag = "" ref.ID = dockerImage.ID } mapping := &api.ImageStreamMapping{ ObjectMeta: kapi.ObjectMeta{ Name: stream.Name, Namespace: stream.Namespace, }, Tag: tag, Image: api.Image{ ObjectMeta: kapi.ObjectMeta{ Name: dockerImage.ID, }, DockerImageReference: ref.String(), DockerImageMetadata: image, }, } if err := c.mappings.ImageStreamMappings(stream.Namespace).Create(mapping); err != nil { // retry-able no. 5 return nil, true, err } return dockerImage, false, nil }
func doTestRegistryClientImage(t *testing.T, registry, reponame, version string) { conn, err := dockerregistry.NewClient(10*time.Second, version == "v2").Connect(registry, false) if err != nil { t.Fatal(err) } err = retryWhenUnreachable(t, func() error { _, err := conn.ImageByTag("openshift", "origin-not-found", "latest") return err }) if err == nil || (!dockerregistry.IsRepositoryNotFound(err) && !dockerregistry.IsTagNotFound(err)) { t.Errorf("%s: unexpected error: %v", version, err) } var image *dockerregistry.Image err = retryWhenUnreachable(t, func() error { image, err = conn.ImageByTag("openshift", reponame, "latest") return err }) if err != nil { t.Fatal(err) } if image.Comment != "Imported from -" { t.Errorf("%s: unexpected image comment", version) } if image.Architecture != "amd64" { t.Errorf("%s: unexpected image architecture", version) } if version == "v2" && !image.PullByID { t.Errorf("%s: should be able to pull by ID %s", version, image.ID) } var other *dockerregistry.Image err = retryWhenUnreachable(t, func() error { other, err = conn.ImageByID("openshift", reponame, image.ID) return err }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(other.ContainerConfig.Entrypoint, image.ContainerConfig.Entrypoint) { t.Errorf("%s: unexpected image: %#v", version, other) } }
// Next processes the given image stream, looking for streams that have DockerImageRepository // set but have not yet been marked as "ready". If transient errors occur, err is returned but // the image stream is not modified (so it will be tried again later). If a permanent // failure occurs the image is marked with an annotation. The tags of the original spec image // are left as is (those are updated through status). func (c *ImportController) Next(stream *api.ImageStream) error { if !needsImport(stream) { return nil } name := stream.Spec.DockerImageRepository ref, err := api.ParseDockerImageReference(name) if err != nil { err = fmt.Errorf("invalid docker image repository, cannot import data: %v", err) util.HandleError(err) return c.done(stream, err.Error(), retryCount) } insecure := stream.Annotations != nil && stream.Annotations[api.InsecureRepositoryAnnotation] == "true" client := c.client if client == nil { client = dockerregistry.NewClient() } conn, err := client.Connect(ref.Registry, insecure, false) if err != nil { return err } tags, err := conn.ImageTags(ref.Namespace, ref.Name) switch { case dockerregistry.IsRepositoryNotFound(err), dockerregistry.IsRegistryNotFound(err): return c.done(stream, err.Error(), retryCount) case err != nil: return err } imageToTag := make(map[string][]string) for tag, image := range tags { if specTag, ok := stream.Spec.Tags[tag]; ok && specTag.From != nil { // spec tag is set to track another tag - do not import continue } imageToTag[image] = append(imageToTag[image], tag) } // no tags to import if len(imageToTag) == 0 { return c.done(stream, "", retryCount) } for id, tags := range imageToTag { dockerImage, err := conn.ImageByID(ref.Namespace, ref.Name, id) switch { case dockerregistry.IsRepositoryNotFound(err), dockerregistry.IsRegistryNotFound(err): return c.done(stream, err.Error(), retryCount) case dockerregistry.IsImageNotFound(err): continue case err != nil: return err } var image api.DockerImage if err := kapi.Scheme.Convert(&dockerImage.Image, &image); err != nil { err = fmt.Errorf("could not convert image: %#v", err) util.HandleError(err) return c.done(stream, err.Error(), retryCount) } idTagPresent := false if len(tags) > 1 && hasTag(tags, id) { // only set to true if we have at least 1 tag that isn't the image id idTagPresent = true } for _, tag := range tags { if idTagPresent && id == tag { continue } pullRef := api.DockerImageReference{ Registry: ref.Registry, Namespace: ref.Namespace, Name: ref.Name, Tag: tag, } // prefer to pull by ID always if dockerImage.PullByID { // if the registry indicates the image is pullable by ID, clear the tag pullRef.Tag = "" pullRef.ID = dockerImage.ID } else if idTagPresent { // if there is a tag for the image by its id (tag=tag), we can pull by id pullRef.Tag = id } mapping := &api.ImageStreamMapping{ ObjectMeta: kapi.ObjectMeta{ Name: stream.Name, Namespace: stream.Namespace, }, Tag: tag, Image: api.Image{ ObjectMeta: kapi.ObjectMeta{ Name: dockerImage.ID, }, DockerImageReference: pullRef.String(), DockerImageMetadata: image, }, } if err := c.mappings.ImageStreamMappings(stream.Namespace).Create(mapping); err != nil { if errors.IsNotFound(err) { return c.done(stream, err.Error(), retryCount) } return err } } } // we've completed our updates return c.done(stream, "", retryCount) }
// getTags returns tags from default upstream image repository and explicitly defined. // Returns a map of tags to be imported and an error if one occurs. // Tags explicitly defined will overwrite those from default upstream image repository. func getTags(stream *api.ImageStream, client dockerregistry.Client, insecure bool) (map[string]api.DockerImageReference, error) { imports := make(map[string]api.DockerImageReference) references := sets.NewString() // read explicitly defined tags for tagName, specTag := range stream.Spec.Tags { if specTag.From == nil { continue } if specTag.From.Kind != "DockerImage" || specTag.Reference { references.Insert(tagName) continue } ref, err := api.ParseDockerImageReference(specTag.From.Name) if err != nil { glog.V(2).Infof("error parsing DockerImage %s: %v", specTag.From.Name, err) continue } imports[tagName] = ref.DockerClientDefaults() } if len(stream.Spec.DockerImageRepository) == 0 { return imports, nil } // read tags from default upstream image repository streamRef, err := api.ParseDockerImageReference(stream.Spec.DockerImageRepository) if err != nil { util.HandleError(fmt.Errorf("invalid docker image repository, cannot import data: %v", err)) return imports, nil } conn, err := client.Connect(streamRef.Registry, insecure) if err != nil { // retry-able error no. 1 return imports, err } tags, err := conn.ImageTags(streamRef.Namespace, streamRef.Name) switch { case dockerregistry.IsRepositoryNotFound(err), dockerregistry.IsRegistryNotFound(err): return imports, nil case err != nil: // retry-able error no. 2 return imports, err } for tag, image := range tags { if _, ok := imports[tag]; ok || references.Has(tag) { continue } idTagPresent := false // this for loop is for backwards compatibility with v1 repo, where // there was no image id returned with tags, like v2 does right now. for t2, i2 := range tags { if i2 == image && t2 == image { idTagPresent = true break } } ref := streamRef if idTagPresent { ref.Tag = image } else { ref.Tag = tag } ref.ID = image imports[tag] = ref } return imports, nil }