// RegistryListTags returns the list of images instances obtained from all tags existing in the registry func RegistryListTags(image *imagename.ImageName, auth *docker.AuthConfigurations) (images []*imagename.ImageName, err error) { var ( name = image.Name registry = image.Registry ) regAuth, err := GetAuthForRegistry(auth, image) if err != nil { return nil, fmt.Errorf("Failed to get auth token for registry: %s, make sure you are properly logged in using `docker login` or have AWS credentials set in case of using ECR", image) } // XXX: AWS ECR Registry API v2 does not support listing tags // wo we just return a single image tag if it exists and no wildcards used if image.IsECR() { log.Debugf("ECR detected %s", registry) if !image.IsStrict() { return nil, fmt.Errorf("Amazon ECR does not support tags listing, therefore image wildcards are not supported, sorry: %s", image) } if exists, err := ecrImageExists(image, regAuth); err != nil { return nil, err } else if exists { log.Debugf("ECR image %s found in the registry", image) images = append(images, image) } return } if registry == "" { registry = "registry-1.docker.io" if !strings.Contains(name, "/") { name = "library/" + name } } var ( tg = tags{} url = fmt.Sprintf("https://%s/v2/%s/tags/list?page_size=9999&page=1", registry, name) ) log.Debugf("Listing image tags from the remote registry %s", url) if err := registryGet(url, regAuth, &tg); err != nil { return nil, err } log.Debugf("Got %d tags from the remote registry for image %s", len(tg.Tags), image) for _, t := range tg.Tags { candidate := imagename.New(image.NameWithRegistry(), t) if image.Contains(candidate) || image.Tag == candidate.Tag { images = append(images, candidate) } } return }
// GetAuthForRegistry extracts desired docker.AuthConfiguration object from the // list of docker.AuthConfigurations by registry hostname func GetAuthForRegistry(auth *docker.AuthConfigurations, image *imagename.ImageName) (result docker.AuthConfiguration, err error) { registry := image.Registry // The default registry is "index.docker.io" if registry == "" || registry == "registry-1.docker.io" { registry = "index.docker.io" } // Optionally override auth took via aws-sdk (through ENV vars) if image.IsECR() { if awsRegAuth, err := GetECRAuth(registry, image.GetECRRegion()); err != nil && err != credentials.ErrNoValidProvidersFoundInChain { return result, err } else if awsRegAuth.Username != "" { return awsRegAuth, nil } } if auth == nil { return } if result, ok := auth.Configs[registry]; ok { return result, nil } if result, ok := auth.Configs["https://"+registry]; ok { return result, nil } if result, ok := auth.Configs["https://"+registry+"/v1/"]; ok { return result, nil } // not sure /v2/ is needed, but just in case if result, ok := auth.Configs["https://"+registry+"/v2/"]; ok { return result, nil } if result, ok := auth.Configs["*"]; ok { return result, nil } return }
// PullDockerImage pulls an image and streams to a logger respecting terminal features func PullDockerImage(client *docker.Client, image *imagename.ImageName, auth *docker.AuthConfigurations) (*docker.Image, error) { if image.Storage == imagename.StorageS3 { s3storage := s3.New(client, os.TempDir()) if err := s3storage.Pull(image.String()); err != nil { return nil, err } } else { pipeReader, pipeWriter := io.Pipe() pullOpts := docker.PullImageOptions{ Repository: image.NameWithRegistry(), Registry: image.Registry, Tag: image.Tag, OutputStream: pipeWriter, RawJSONStream: true, } repoAuth, err := dockerclient.GetAuthForRegistry(auth, image) if err != nil { return nil, fmt.Errorf("Failed to authenticate registry %s, error: %s", image.Registry, err) } errch := make(chan error, 1) go func() { err := client.PullImage(pullOpts, repoAuth) if err := pipeWriter.Close(); err != nil { log.Errorf("Failed to close pull image stream for %s, error: %s", image, err) } errch <- err }() def := log.StandardLogger() fd, isTerminal := term.GetFdInfo(def.Out) out := def.Out if !isTerminal { out = def.Writer() } if err := jsonmessage.DisplayJSONMessagesStream(pipeReader, out, fd, isTerminal); err != nil { return nil, fmt.Errorf("Failed to process json stream for image: %s, error: %s", image, err) } if err := <-errch; err != nil { return nil, fmt.Errorf("Failed to pull image %s, error: %s", image, err) } } img, err := client.InspectImage(image.String()) if err != nil { return nil, fmt.Errorf("Failed to inspect image %s after pull, error: %s", image, err) } return img, nil }
// lookupImage looks up for the image by name and returns *docker.Image object (result of the inspect) // `Pull` config option defines whether we want to update the latest version of the image from the remote registry // See build.Config struct for more details about other build config options. // // If `Pull` is false, it tries to lookup locally by exact matching, e.g. if the image is already // pulled with that exact name given (no fuzzy semver matching) // // Then the function fetches the list of all pulled images and tries to match one of them by the given name. // // If `Pull` is set to true or if it cannot find the image locally, it then fetches all image // tags from the remote registry and finds the best match for the given image name. // // If it cannot find the image either locally or in the remote registry, it returns `nil` // // In case the given image has sha256 tag, it looks for it locally and pulls if it's not found. // No semver matching is done for sha256 tagged images. // // See also TestBuild_LookupImage_* test cases in build_test.go func (b *Build) lookupImage(name string) (img *docker.Image, err error) { var ( candidate, remoteCandidate *imagename.ImageName imgName = imagename.NewFromString(name) pull = false hub = b.cfg.Pull isSha = imgName.TagIsSha() ) // If hub is true, then there is no sense to inspect the local image if !hub || isSha { if isOld, warning := imagename.WarnIfOldS3ImageName(name); isOld { log.Warn(warning) } // Try to inspect image as is, without version resolution if img, err := b.client.InspectImage(imgName.String()); err != nil || img != nil { return img, err } } if isSha { // If we are still here and image not found locally, we want to pull it candidate = imgName hub = false pull = true } if !isSha && !hub { // List local images var localImages = []*imagename.ImageName{} if localImages, err = b.client.ListImages(); err != nil { return nil, err } // Resolve local candidate candidate = imgName.ResolveVersion(localImages, true) } // In case we want to include external images as well, pulling list of available // images from the remote registry if hub || candidate == nil { log.Debugf("Getting list of tags for %s from the registry", imgName) var remoteImages []*imagename.ImageName if remoteImages, err = b.client.ListImageTags(imgName.String()); err != nil { err = fmt.Errorf("Failed to list tags of image %s from the remote registry, error: %s", imgName, err) return } // Since we found the remote image, we want to pull it if remoteCandidate = imgName.ResolveVersion(remoteImages, false); remoteCandidate != nil { pull = true candidate = remoteCandidate // If we've found needed image on s3 it should be pulled in the same name style as lookuped image candidate.IsOldS3Name = imgName.IsOldS3Name } } // If not candidate found, it's an error if candidate == nil { err = fmt.Errorf("Image not found: %s (also checked in the remote registry)", imgName) return } if !isSha && imgName.GetTag() != candidate.GetTag() { if remoteCandidate != nil { log.Infof("Resolve %s --> %s (found remotely)", imgName, candidate.GetTag()) } else { log.Infof("Resolve %s --> %s", imgName, candidate.GetTag()) } } if pull { if err = b.client.PullImage(candidate.String()); err != nil { return } } return b.client.InspectImage(candidate.String()) }