// CmdPull pulls an image or a repository from the registry. // // Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST] func (cli *DockerCli) CmdPull(args ...string) error { cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, Cli.DockerCommands["pull"].Description, true) allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") addTrustedFlags(cmd, true) cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) remote := cmd.Arg(0) distributionRef, err := reference.ParseNamed(remote) if err != nil { return err } var tag string switch x := distributionRef.(type) { case reference.Digested: if *allTags { return errTagCantBeUsed } tag = x.Digest().String() case reference.Tagged: if *allTags { return errTagCantBeUsed } tag = x.Tag() default: if !*allTags { tag = tagpkg.DefaultTag distributionRef, err = reference.WithTag(distributionRef, tag) if err != nil { return err } fmt.Fprintf(cli.out, "Using default tag: %s\n", tag) } } ref := registry.ParseReference(tag) // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := registry.ParseRepositoryInfo(distributionRef) if err != nil { return err } if isTrusted() && !ref.HasDigest() { // Check if tag is digest authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) return cli.trustedPull(repoInfo, ref, authConfig) } v := url.Values{} v.Set("fromImage", distributionRef.String()) _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull") return err }
func convertTarget(t client.Target) (target, error) { h, ok := t.Hashes["sha256"] if !ok { return target{}, errors.New("no valid hash, expecting sha256") } return target{ reference: registry.ParseReference(t.Name), digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)), size: t.Length, }, nil }
// CmdPull pulls an image or a repository from the registry. // // Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST] func (cli *DockerCli) CmdPull(args ...string) error { cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, Cli.DockerCommands["pull"].Description, true) allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") addTrustedFlags(cmd, true) cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) remote := cmd.Arg(0) distributionRef, err := reference.ParseNamed(remote) if err != nil { return err } if *allTags && !reference.IsNameOnly(distributionRef) { return errors.New("tag can't be used with --all-tags/-a") } if !*allTags && reference.IsNameOnly(distributionRef) { distributionRef = reference.WithDefaultTag(distributionRef) fmt.Fprintf(cli.out, "Using default tag: %s\n", reference.DefaultTag) } var tag string switch x := distributionRef.(type) { case reference.Canonical: tag = x.Digest().String() case reference.NamedTagged: tag = x.Tag() } registryRef := registry.ParseReference(tag) // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := registry.ParseRepositoryInfo(distributionRef) if err != nil { return err } ctx := context.Background() authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index) requestPrivilege := cli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "pull") if IsTrusted() && !registryRef.HasDigest() { // Check if tag is digest return cli.trustedPull(ctx, repoInfo, registryRef, authConfig, requestPrivilege) } <<<<<<< HEAD return cli.imagePullPrivileged(ctx, authConfig, distributionRef.String(), requestPrivilege, *allTags) }
func runPull(dockerCli *command.DockerCli, opts pullOptions) error { distributionRef, err := reference.ParseNamed(opts.remote) if err != nil { return err } if opts.all && !reference.IsNameOnly(distributionRef) { return errors.New("tag can't be used with --all-tags/-a") } if !opts.all && reference.IsNameOnly(distributionRef) { distributionRef = reference.WithDefaultTag(distributionRef) fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", reference.DefaultTag) } var tag string switch x := distributionRef.(type) { case reference.Canonical: tag = x.Digest().String() case reference.NamedTagged: tag = x.Tag() } registryRef := registry.ParseReference(tag) // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := registry.ParseRepositoryInfo(distributionRef) if err != nil { return err } ctx := context.Background() authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index) requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "pull") if command.IsTrusted() && !registryRef.HasDigest() { // Check if tag is digest err = trustedPull(ctx, dockerCli, repoInfo, registryRef, authConfig, requestPrivilege) } else { err = imagePullPrivileged(ctx, dockerCli, authConfig, distributionRef.String(), requestPrivilege, opts.all) } if err != nil { if strings.Contains(err.Error(), "target is a plugin") { return errors.New(err.Error() + " - Use `docker plugin install`") } return err } return nil }
// CmdPull pulls an image or a repository from the registry. // // Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST] func (cli *DockerCli) CmdPull(args ...string) error { cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", true) allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") dryRun := cmd.Bool([]string{"d", "-dry-run"}, false, "Dry run mode - only displays the download size") addTrustedFlags(cmd, true) cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) remote := cmd.Arg(0) taglessRemote, tag := parsers.ParseRepositoryTag(remote) if tag == "" && !*allTags { tag = tags.DefaultTag fmt.Fprintf(cli.out, "Using default tag: %s\n", tag) } else if tag != "" && *allTags { return fmt.Errorf("tag can't be used with --all-tags/-a") } ref := registry.ParseReference(tag) // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) if err != nil { return err } if isTrusted() && !ref.HasDigest() { // Check if tag is digest authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) return cli.trustedPull(repoInfo, ref, authConfig) } v := url.Values{} v.Set("fromImage", ref.ImageName(taglessRemote)) if *dryRun { fmt.Fprintf(cli.out, "**** Dry Run - nothing will be downloaded ****\n") v.Set("dryRun", "true") } _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull") return err }
func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) { r, w := io.Pipe() out := io.MultiWriter(in, w) targetChan := make(chan []target) go func() { targets := []target{} scanner := bufio.NewScanner(r) scanner.Split(ansiescape.ScanANSILines) for scanner.Scan() { line := scanner.Bytes() if matches := targetRegexp.FindSubmatch(line); len(matches) == 4 { dgst, err := digest.ParseDigest(string(matches[2])) if err != nil { // Line does match what is expected, continue looking for valid lines logrus.Debugf("Bad digest value %q in matched line, ignoring\n", string(matches[2])) continue } s, err := strconv.ParseInt(string(matches[3]), 10, 64) if err != nil { // Line does match what is expected, continue looking for valid lines logrus.Debugf("Bad size value %q in matched line, ignoring\n", string(matches[3])) continue } targets = append(targets, target{ reference: registry.ParseReference(string(matches[1])), digest: dgst, size: s, }) } } targetChan <- targets }() return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan }
func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig types.AuthConfig, requestPrivilege apiclient.RequestPrivilegeFunc) error { responseBody, err := cli.imagePushPrivileged(authConfig, repoInfo.Name(), tag, requestPrivilege) if err != nil { return err } defer responseBody.Close() targets := []target{} handleTarget := func(aux *json.RawMessage) { var pushResult distribution.PushResult err := json.Unmarshal(*aux, &pushResult) if err == nil && pushResult.Tag != "" && pushResult.Digest.Validate() == nil { targets = append(targets, target{ reference: registry.ParseReference(pushResult.Tag), digest: pushResult.Digest, size: int64(pushResult.Size), }) } } err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget) if err != nil { return err } if tag == "" { fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n") return nil } if len(targets) == 0 { fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n") return nil } fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n") repo, err := cli.getNotaryRepository(repoInfo, authConfig) if err != nil { fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err) return err } for _, target := range targets { h, err := hex.DecodeString(target.digest.Hex()) if err != nil { return err } t := &client.Target{ Name: target.reference.String(), Hashes: data.Hashes{ string(target.digest.Algorithm()): h, }, Length: int64(target.size), } if err := repo.AddTarget(t, releasesRole); err != nil { return err } } err = repo.Publish() if _, ok := err.(client.ErrRepoNotInitialized); !ok { return notaryError(repoInfo.FullName(), err) } keys := repo.CryptoService.ListKeys(data.CanonicalRootRole) var rootKeyID string // always select the first root key if len(keys) > 0 { sort.Strings(keys) rootKeyID = keys[0] } else { rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey) if err != nil { return err } rootKeyID = rootPublicKey.ID() } if err := repo.Initialize(rootKeyID); err != nil { return notaryError(repoInfo.FullName(), err) } fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName()) return notaryError(repoInfo.FullName(), repo.Publish()) }
func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) { containerValues := url.Values{} if name != "" { containerValues.Set("name", name) } mergedConfig := runconfig.MergeConfigs(config, hostConfig) var containerIDFile *cidFile if cidfile != "" { var err error if containerIDFile, err = newCIDFile(cidfile); err != nil { return nil, err } defer containerIDFile.Close() } repo, tag := parsers.ParseRepositoryTag(config.Image) if tag == "" { tag = tags.DEFAULTTAG } ref := registry.ParseReference(tag) var trustedRef registry.Reference if isTrusted() && !ref.HasDigest() { var err error trustedRef, err = cli.trustedReference(repo, ref) if err != nil { return nil, err } config.Image = trustedRef.ImageName(repo) } //create the container serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil) //if image not found try to pull it if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) { fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.ImageName(repo)) // we don't want to write to stdout anything apart from container.ID if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil { return nil, err } if trustedRef != nil && !ref.HasDigest() { repoInfo, err := registry.ParseRepositoryInfo(repo) if err != nil { return nil, err } if err := cli.tagTrusted(repoInfo, trustedRef, ref); err != nil { return nil, err } } // Retry if serverResp, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil { return nil, err } } else if err != nil { return nil, err } defer serverResp.body.Close() var response types.ContainerCreateResponse if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil { return nil, err } for _, warning := range response.Warnings { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } if containerIDFile != nil { if err = containerIDFile.Write(response.ID); err != nil { return nil, err } } return &response, nil }
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in // "FROM <image>" instructions to a digest reference. `translator` is a // function that takes a repository name and tag reference and returns a // trusted digest reference. func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) { dockerfile, err := os.Open(dockerfileName) if err != nil { return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err) } defer dockerfile.Close() scanner := bufio.NewScanner(dockerfile) // Make a tempfile to store the rewritten Dockerfile. tempFile, err := ioutil.TempFile("", "trusted-dockerfile-") if err != nil { return nil, nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err) } trustedFile := &trustedDockerfile{ File: tempFile, } defer func() { if err != nil { // Close the tempfile if there was an error during Notary lookups. // Otherwise the caller should close it. trustedFile.Close() } }() // Scan the lines of the Dockerfile, looking for a "FROM" line. for scanner.Scan() { line := scanner.Text() matches := dockerfileFromLinePattern.FindStringSubmatch(line) if matches != nil && matches[1] != "scratch" { // Replace the line with a resolved "FROM repo@digest" repo, tag := parsers.ParseRepositoryTag(matches[1]) if tag == "" { tag = tags.DefaultTag } repoInfo, err := registry.ParseRepositoryInfo(repo) if err != nil { return nil, nil, fmt.Errorf("unable to parse repository info %q: %v", repo, err) } ref := registry.ParseReference(tag) if !ref.HasDigest() && isTrusted() { trustedRef, err := translator(repo, ref) if err != nil { return nil, nil, err } line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo))) resolvedTags = append(resolvedTags, &resolvedTag{ repoInfo: repoInfo, digestRef: trustedRef, tagRef: ref, }) } } n, err := fmt.Fprintln(tempFile, line) if err != nil { return nil, nil, err } trustedFile.size += int64(n) } tempFile.Seek(0, os.SEEK_SET) return trustedFile, resolvedTags, scanner.Err() }
// TrustedPush handles content trust pushing of an image func (cli *DockerCli) TrustedPush(ctx context.Context, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { responseBody, err := cli.ImagePushPrivileged(ctx, authConfig, ref.String(), requestPrivilege) if err != nil { return err } defer responseBody.Close() // If it is a trusted push we would like to find the target entry which match the // tag provided in the function and then do an AddTarget later. target := &client.Target{} // Count the times of calling for handleTarget, // if it is called more that once, that should be considered an error in a trusted push. cnt := 0 handleTarget := func(aux *json.RawMessage) { cnt++ if cnt > 1 { // handleTarget should only be called one. This will be treated as an error. return } var pushResult distribution.PushResult err := json.Unmarshal(*aux, &pushResult) if err == nil && pushResult.Tag != "" && pushResult.Digest.Validate() == nil { h, err := hex.DecodeString(pushResult.Digest.Hex()) if err != nil { target = nil return } target.Name = registry.ParseReference(pushResult.Tag).String() target.Hashes = data.Hashes{string(pushResult.Digest.Algorithm()): h} target.Length = int64(pushResult.Size) } } var tag string switch x := ref.(type) { case reference.Canonical: return errors.New("cannot push a digest reference") case reference.NamedTagged: tag = x.Tag() } // We want trust signatures to always take an explicit tag, // otherwise it will act as an untrusted push. if tag == "" { if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil); err != nil { return err } fmt.Fprintln(cli.out, "No tag specified, skipping trust metadata push") return nil } if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), handleTarget); err != nil { return err } if cnt > 1 { return fmt.Errorf("internal error: only one call to handleTarget expected") } if target == nil { fmt.Fprintln(cli.out, "No targets found, please provide a specific tag in order to sign it") return nil } fmt.Fprintln(cli.out, "Signing and pushing trust metadata") repo, err := cli.getNotaryRepository(repoInfo, authConfig, "push", "pull") if err != nil { fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err) return err } // get the latest repository metadata so we can figure out which roles to sign err = repo.Update(false) switch err.(type) { case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist: keys := repo.CryptoService.ListKeys(data.CanonicalRootRole) var rootKeyID string // always select the first root key if len(keys) > 0 { sort.Strings(keys) rootKeyID = keys[0] } else { rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey) if err != nil { return err } rootKeyID = rootPublicKey.ID() } // Initialize the notary repository with a remotely managed snapshot key if err := repo.Initialize(rootKeyID, data.CanonicalSnapshotRole); err != nil { return notaryError(repoInfo.FullName(), err) } fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName()) err = repo.AddTarget(target, data.CanonicalTargetsRole) case nil: // already initialized and we have successfully downloaded the latest metadata err = cli.addTargetToAllSignableRoles(repo, target) default: return notaryError(repoInfo.FullName(), err) } if err == nil { err = repo.Publish() } if err != nil { fmt.Fprintf(cli.out, "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error()) return notaryError(repoInfo.FullName(), err) } fmt.Fprintf(cli.out, "Successfully signed %q:%s\n", repoInfo.FullName(), tag) return nil }