github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/command/image/build.go (about)

     1  package image
     2  
     3  import (
     4  	"archive/tar"
     5  	"bufio"
     6  	"bytes"
     7  	"context"
     8  	"encoding/csv"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  	"runtime"
    17  	"strings"
    18  
    19  	"github.com/docker/cli/cli"
    20  	"github.com/docker/cli/cli/command"
    21  	"github.com/docker/cli/cli/command/image/build"
    22  	"github.com/docker/cli/opts"
    23  	"github.com/docker/distribution/reference"
    24  	"github.com/docker/docker/api"
    25  	"github.com/docker/docker/api/types"
    26  	"github.com/docker/docker/api/types/container"
    27  	"github.com/docker/docker/pkg/archive"
    28  	"github.com/docker/docker/pkg/idtools"
    29  	"github.com/docker/docker/pkg/jsonmessage"
    30  	"github.com/docker/docker/pkg/progress"
    31  	"github.com/docker/docker/pkg/streamformatter"
    32  	"github.com/docker/docker/pkg/urlutil"
    33  	units "github.com/docker/go-units"
    34  	"github.com/pkg/errors"
    35  	"github.com/spf13/cobra"
    36  )
    37  
    38  var errStdinConflict = errors.New("invalid argument: can't use stdin for both build context and dockerfile")
    39  
    40  type buildOptions struct {
    41  	context        string
    42  	dockerfileName string
    43  	tags           opts.ListOpts
    44  	labels         opts.ListOpts
    45  	buildArgs      opts.ListOpts
    46  	extraHosts     opts.ListOpts
    47  	ulimits        *opts.UlimitOpt
    48  	memory         opts.MemBytes
    49  	memorySwap     opts.MemSwapBytes
    50  	shmSize        opts.MemBytes
    51  	cpuShares      int64
    52  	cpuPeriod      int64
    53  	cpuQuota       int64
    54  	cpuSetCpus     string
    55  	cpuSetMems     string
    56  	cgroupParent   string
    57  	isolation      string
    58  	quiet          bool
    59  	noCache        bool
    60  	progress       string
    61  	rm             bool
    62  	forceRm        bool
    63  	pull           bool
    64  	cacheFrom      []string
    65  	compress       bool
    66  	securityOpt    []string
    67  	networkMode    string
    68  	squash         bool
    69  	target         string
    70  	imageIDFile    string
    71  	stream         bool
    72  	platform       string
    73  	untrusted      bool
    74  	secrets        []string
    75  	ssh            []string
    76  	outputs        []string
    77  }
    78  
    79  // dockerfileFromStdin returns true when the user specified that the Dockerfile
    80  // should be read from stdin instead of a file
    81  func (o buildOptions) dockerfileFromStdin() bool {
    82  	return o.dockerfileName == "-"
    83  }
    84  
    85  // contextFromStdin returns true when the user specified that the build context
    86  // should be read from stdin
    87  func (o buildOptions) contextFromStdin() bool {
    88  	return o.context == "-"
    89  }
    90  
    91  func newBuildOptions() buildOptions {
    92  	ulimits := make(map[string]*units.Ulimit)
    93  	return buildOptions{
    94  		tags:       opts.NewListOpts(validateTag),
    95  		buildArgs:  opts.NewListOpts(opts.ValidateEnv),
    96  		ulimits:    opts.NewUlimitOpt(&ulimits),
    97  		labels:     opts.NewListOpts(opts.ValidateLabel),
    98  		extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
    99  	}
   100  }
   101  
   102  // NewBuildCommand creates a new `docker build` command
   103  func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
   104  	options := newBuildOptions()
   105  
   106  	cmd := &cobra.Command{
   107  		Use:   "build [OPTIONS] PATH | URL | -",
   108  		Short: "Build an image from a Dockerfile",
   109  		Args:  cli.ExactArgs(1),
   110  		RunE: func(cmd *cobra.Command, args []string) error {
   111  			options.context = args[0]
   112  			return runBuild(dockerCli, options)
   113  		},
   114  	}
   115  
   116  	// Wrap the global pre-run to handle non-BuildKit use of the --platform flag.
   117  	//
   118  	// We're doing it here so that we're only contacting the daemon when actually
   119  	// running the command, and not during initialization.
   120  	// TODO remove this hack once we no longer support the experimental use of --platform
   121  	rootFn := cmd.Root().PersistentPreRunE
   122  	cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
   123  		if ok, _ := command.BuildKitEnabled(dockerCli.ServerInfo()); !ok {
   124  			f := cmd.Flag("platform")
   125  			delete(f.Annotations, "buildkit")
   126  			f.Annotations["version"] = []string{"1.32"}
   127  			f.Annotations["experimental"] = nil
   128  		}
   129  		if rootFn != nil {
   130  			return rootFn(cmd, args)
   131  		}
   132  		return nil
   133  	}
   134  
   135  	flags := cmd.Flags()
   136  
   137  	flags.VarP(&options.tags, "tag", "t", "Name and optionally a tag in the 'name:tag' format")
   138  	flags.Var(&options.buildArgs, "build-arg", "Set build-time variables")
   139  	flags.Var(options.ulimits, "ulimit", "Ulimit options")
   140  	flags.SetAnnotation("ulimit", "no-buildkit", nil)
   141  	flags.StringVarP(&options.dockerfileName, "file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
   142  	flags.VarP(&options.memory, "memory", "m", "Memory limit")
   143  	flags.SetAnnotation("memory", "no-buildkit", nil)
   144  	flags.Var(&options.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
   145  	flags.SetAnnotation("memory-swap", "no-buildkit", nil)
   146  	flags.Var(&options.shmSize, "shm-size", "Size of /dev/shm")
   147  	flags.SetAnnotation("shm-size", "no-buildkit", nil)
   148  	flags.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
   149  	flags.SetAnnotation("cpu-shares", "no-buildkit", nil)
   150  	flags.Int64Var(&options.cpuPeriod, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
   151  	flags.SetAnnotation("cpu-period", "no-buildkit", nil)
   152  	flags.Int64Var(&options.cpuQuota, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
   153  	flags.SetAnnotation("cpu-quota", "no-buildkit", nil)
   154  	flags.StringVar(&options.cpuSetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
   155  	flags.SetAnnotation("cpuset-cpus", "no-buildkit", nil)
   156  	flags.StringVar(&options.cpuSetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
   157  	flags.SetAnnotation("cpuset-mems", "no-buildkit", nil)
   158  	flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
   159  	flags.SetAnnotation("cgroup-parent", "no-buildkit", nil)
   160  	flags.StringVar(&options.isolation, "isolation", "", "Container isolation technology")
   161  	flags.Var(&options.labels, "label", "Set metadata for an image")
   162  	flags.BoolVar(&options.noCache, "no-cache", false, "Do not use cache when building the image")
   163  	flags.BoolVar(&options.rm, "rm", true, "Remove intermediate containers after a successful build")
   164  	flags.SetAnnotation("rm", "no-buildkit", nil)
   165  	flags.BoolVar(&options.forceRm, "force-rm", false, "Always remove intermediate containers")
   166  	flags.SetAnnotation("force-rm", "no-buildkit", nil)
   167  	flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
   168  	flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
   169  	flags.StringSliceVar(&options.cacheFrom, "cache-from", []string{}, "Images to consider as cache sources")
   170  	flags.BoolVar(&options.compress, "compress", false, "Compress the build context using gzip")
   171  	flags.SetAnnotation("compress", "no-buildkit", nil)
   172  	flags.StringSliceVar(&options.securityOpt, "security-opt", []string{}, "Security options")
   173  	flags.SetAnnotation("security-opt", "no-buildkit", nil)
   174  	flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
   175  	flags.SetAnnotation("network", "version", []string{"1.25"})
   176  	flags.Var(&options.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
   177  	flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
   178  	flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
   179  
   180  	command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
   181  
   182  	flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
   183  	flags.SetAnnotation("platform", "version", []string{"1.38"})
   184  	flags.SetAnnotation("platform", "buildkit", nil)
   185  
   186  	flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
   187  	flags.SetAnnotation("squash", "experimental", nil)
   188  	flags.SetAnnotation("squash", "version", []string{"1.25"})
   189  
   190  	flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context")
   191  	flags.MarkHidden("stream")
   192  
   193  	flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
   194  	flags.SetAnnotation("progress", "buildkit", nil)
   195  
   196  	flags.StringArrayVar(&options.secrets, "secret", []string{}, "Secret file to expose to the build (only if BuildKit enabled): id=mysecret,src=/local/secret")
   197  	flags.SetAnnotation("secret", "version", []string{"1.39"})
   198  	flags.SetAnnotation("secret", "buildkit", nil)
   199  
   200  	flags.StringArrayVar(&options.ssh, "ssh", []string{}, "SSH agent socket or keys to expose to the build (only if BuildKit enabled) (format: default|<id>[=<socket>|<key>[,<key>]])")
   201  	flags.SetAnnotation("ssh", "version", []string{"1.39"})
   202  	flags.SetAnnotation("ssh", "buildkit", nil)
   203  
   204  	flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
   205  	flags.SetAnnotation("output", "version", []string{"1.40"})
   206  	flags.SetAnnotation("output", "buildkit", nil)
   207  
   208  	return cmd
   209  }
   210  
   211  // lastProgressOutput is the same as progress.Output except
   212  // that it only output with the last update. It is used in
   213  // non terminal scenarios to suppress verbose messages
   214  type lastProgressOutput struct {
   215  	output progress.Output
   216  }
   217  
   218  // WriteProgress formats progress information from a ProgressReader.
   219  func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
   220  	if !prog.LastUpdate {
   221  		return nil
   222  	}
   223  
   224  	return out.output.WriteProgress(prog)
   225  }
   226  
   227  // nolint: gocyclo
   228  func runBuild(dockerCli command.Cli, options buildOptions) error {
   229  	buildkitEnabled, err := command.BuildKitEnabled(dockerCli.ServerInfo())
   230  	if err != nil {
   231  		return err
   232  	}
   233  	if buildkitEnabled {
   234  		return runBuildBuildKit(dockerCli, options)
   235  	}
   236  
   237  	var (
   238  		buildCtx      io.ReadCloser
   239  		dockerfileCtx io.ReadCloser
   240  		contextDir    string
   241  		tempDir       string
   242  		relDockerfile string
   243  		progBuff      io.Writer
   244  		buildBuff     io.Writer
   245  		remote        string
   246  	)
   247  
   248  	if options.stream {
   249  		return errors.New("Experimental flag --stream was removed, enable BuildKit instead with DOCKER_BUILDKIT=1")
   250  	}
   251  
   252  	if options.dockerfileFromStdin() {
   253  		if options.contextFromStdin() {
   254  			return errStdinConflict
   255  		}
   256  		dockerfileCtx = dockerCli.In()
   257  	}
   258  
   259  	specifiedContext := options.context
   260  	progBuff = dockerCli.Out()
   261  	buildBuff = dockerCli.Out()
   262  	if options.quiet {
   263  		progBuff = bytes.NewBuffer(nil)
   264  		buildBuff = bytes.NewBuffer(nil)
   265  	}
   266  	if options.imageIDFile != "" {
   267  		// Avoid leaving a stale file if we eventually fail
   268  		if err := os.Remove(options.imageIDFile); err != nil && !os.IsNotExist(err) {
   269  			return errors.Wrap(err, "Removing image ID file")
   270  		}
   271  	}
   272  
   273  	switch {
   274  	case options.contextFromStdin():
   275  		// buildCtx is tar archive. if stdin was dockerfile then it is wrapped
   276  		buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
   277  	case isLocalDir(specifiedContext):
   278  		contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
   279  		if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
   280  			// Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx
   281  			dockerfileCtx, err = os.Open(options.dockerfileName)
   282  			if err != nil {
   283  				return errors.Errorf("unable to open Dockerfile: %v", err)
   284  			}
   285  			defer dockerfileCtx.Close()
   286  		}
   287  	case urlutil.IsGitURL(specifiedContext):
   288  		tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, options.dockerfileName)
   289  	case urlutil.IsURL(specifiedContext):
   290  		buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName)
   291  	default:
   292  		return errors.Errorf("unable to prepare context: path %q not found", specifiedContext)
   293  	}
   294  
   295  	if err != nil {
   296  		if options.quiet && urlutil.IsURL(specifiedContext) {
   297  			fmt.Fprintln(dockerCli.Err(), progBuff)
   298  		}
   299  		return errors.Errorf("unable to prepare context: %s", err)
   300  	}
   301  
   302  	if tempDir != "" {
   303  		defer os.RemoveAll(tempDir)
   304  		contextDir = tempDir
   305  	}
   306  
   307  	// read from a directory into tar archive
   308  	if buildCtx == nil {
   309  		excludes, err := build.ReadDockerignore(contextDir)
   310  		if err != nil {
   311  			return err
   312  		}
   313  
   314  		if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
   315  			return errors.Errorf("error checking context: '%s'.", err)
   316  		}
   317  
   318  		// And canonicalize dockerfile name to a platform-independent one
   319  		relDockerfile = archive.CanonicalTarNameForPath(relDockerfile)
   320  
   321  		excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
   322  		buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
   323  			ExcludePatterns: excludes,
   324  			ChownOpts:       &idtools.Identity{UID: 0, GID: 0},
   325  		})
   326  		if err != nil {
   327  			return err
   328  		}
   329  	}
   330  
   331  	// replace Dockerfile if it was added from stdin or a file outside the build-context, and there is archive context
   332  	if dockerfileCtx != nil && buildCtx != nil {
   333  		buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
   334  		if err != nil {
   335  			return err
   336  		}
   337  	}
   338  
   339  	ctx, cancel := context.WithCancel(context.Background())
   340  	defer cancel()
   341  
   342  	var resolvedTags []*resolvedTag
   343  	if !options.untrusted {
   344  		translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
   345  			return TrustedReference(ctx, dockerCli, ref, nil)
   346  		}
   347  		// if there is a tar wrapper, the dockerfile needs to be replaced inside it
   348  		if buildCtx != nil {
   349  			// Wrap the tar archive to replace the Dockerfile entry with the rewritten
   350  			// Dockerfile which uses trusted pulls.
   351  			buildCtx = replaceDockerfileForContentTrust(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
   352  		} else if dockerfileCtx != nil {
   353  			// if there was not archive context still do the possible replacements in Dockerfile
   354  			newDockerfile, _, err := rewriteDockerfileFromForContentTrust(ctx, dockerfileCtx, translator)
   355  			if err != nil {
   356  				return err
   357  			}
   358  			dockerfileCtx = ioutil.NopCloser(bytes.NewBuffer(newDockerfile))
   359  		}
   360  	}
   361  
   362  	if options.compress {
   363  		buildCtx, err = build.Compress(buildCtx)
   364  		if err != nil {
   365  			return err
   366  		}
   367  	}
   368  
   369  	// Setup an upload progress bar
   370  	progressOutput := streamformatter.NewProgressOutput(progBuff)
   371  	if !dockerCli.Out().IsTerminal() {
   372  		progressOutput = &lastProgressOutput{output: progressOutput}
   373  	}
   374  
   375  	// if up to this point nothing has set the context then we must have another
   376  	// way for sending it(streaming) and set the context to the Dockerfile
   377  	if dockerfileCtx != nil && buildCtx == nil {
   378  		buildCtx = dockerfileCtx
   379  	}
   380  
   381  	var body io.Reader
   382  	if buildCtx != nil {
   383  		body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
   384  	}
   385  
   386  	configFile := dockerCli.ConfigFile()
   387  	creds, _ := configFile.GetAllCredentials()
   388  	authConfigs := make(map[string]types.AuthConfig, len(creds))
   389  	for k, auth := range creds {
   390  		authConfigs[k] = types.AuthConfig(auth)
   391  	}
   392  	buildOptions := imageBuildOptions(dockerCli, options)
   393  	buildOptions.Version = types.BuilderV1
   394  	buildOptions.Dockerfile = relDockerfile
   395  	buildOptions.AuthConfigs = authConfigs
   396  	buildOptions.RemoteContext = remote
   397  
   398  	response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
   399  	if err != nil {
   400  		if options.quiet {
   401  			fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
   402  		}
   403  		cancel()
   404  		return err
   405  	}
   406  	defer response.Body.Close()
   407  
   408  	imageID := ""
   409  	aux := func(msg jsonmessage.JSONMessage) {
   410  		var result types.BuildResult
   411  		if err := json.Unmarshal(*msg.Aux, &result); err != nil {
   412  			fmt.Fprintf(dockerCli.Err(), "Failed to parse aux message: %s", err)
   413  		} else {
   414  			imageID = result.ID
   415  		}
   416  	}
   417  
   418  	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), aux)
   419  	if err != nil {
   420  		if jerr, ok := err.(*jsonmessage.JSONError); ok {
   421  			// If no error code is set, default to 1
   422  			if jerr.Code == 0 {
   423  				jerr.Code = 1
   424  			}
   425  			if options.quiet {
   426  				fmt.Fprintf(dockerCli.Err(), "%s%s", progBuff, buildBuff)
   427  			}
   428  			return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
   429  		}
   430  		return err
   431  	}
   432  
   433  	// Windows: show error message about modified file permissions if the
   434  	// daemon isn't running Windows.
   435  	if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
   436  		fmt.Fprintln(dockerCli.Out(), "SECURITY WARNING: You are building a Docker "+
   437  			"image from Windows against a non-Windows Docker host. All files and "+
   438  			"directories added to build context will have '-rwxr-xr-x' permissions. "+
   439  			"It is recommended to double check and reset permissions for sensitive "+
   440  			"files and directories.")
   441  	}
   442  
   443  	// Everything worked so if -q was provided the output from the daemon
   444  	// should be just the image ID and we'll print that to stdout.
   445  	if options.quiet {
   446  		imageID = fmt.Sprintf("%s", buildBuff)
   447  		_, _ = fmt.Fprint(dockerCli.Out(), imageID)
   448  	}
   449  
   450  	if options.imageIDFile != "" {
   451  		if imageID == "" {
   452  			return errors.Errorf("Server did not provide an image ID. Cannot write %s", options.imageIDFile)
   453  		}
   454  		if err := ioutil.WriteFile(options.imageIDFile, []byte(imageID), 0666); err != nil {
   455  			return err
   456  		}
   457  	}
   458  	if !options.untrusted {
   459  		// Since the build was successful, now we must tag any of the resolved
   460  		// images from the above Dockerfile rewrite.
   461  		for _, resolved := range resolvedTags {
   462  			if err := TagTrusted(ctx, dockerCli, resolved.digestRef, resolved.tagRef); err != nil {
   463  				return err
   464  			}
   465  		}
   466  	}
   467  
   468  	return nil
   469  }
   470  
   471  func isLocalDir(c string) bool {
   472  	_, err := os.Stat(c)
   473  	return err == nil
   474  }
   475  
   476  type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error)
   477  
   478  // validateTag checks if the given image name can be resolved.
   479  func validateTag(rawRepo string) (string, error) {
   480  	_, err := reference.ParseNormalizedNamed(rawRepo)
   481  	if err != nil {
   482  		return "", err
   483  	}
   484  
   485  	return rawRepo, nil
   486  }
   487  
   488  var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
   489  
   490  // resolvedTag records the repository, tag, and resolved digest reference
   491  // from a Dockerfile rewrite.
   492  type resolvedTag struct {
   493  	digestRef reference.Canonical
   494  	tagRef    reference.NamedTagged
   495  }
   496  
   497  // rewriteDockerfileFromForContentTrust rewrites the given Dockerfile by resolving images in
   498  // "FROM <image>" instructions to a digest reference. `translator` is a
   499  // function that takes a repository name and tag reference and returns a
   500  // trusted digest reference.
   501  // This should be called *only* when content trust is enabled
   502  func rewriteDockerfileFromForContentTrust(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) {
   503  	scanner := bufio.NewScanner(dockerfile)
   504  	buf := bytes.NewBuffer(nil)
   505  
   506  	// Scan the lines of the Dockerfile, looking for a "FROM" line.
   507  	for scanner.Scan() {
   508  		line := scanner.Text()
   509  
   510  		matches := dockerfileFromLinePattern.FindStringSubmatch(line)
   511  		if matches != nil && matches[1] != api.NoBaseImageSpecifier {
   512  			// Replace the line with a resolved "FROM repo@digest"
   513  			var ref reference.Named
   514  			ref, err = reference.ParseNormalizedNamed(matches[1])
   515  			if err != nil {
   516  				return nil, nil, err
   517  			}
   518  			ref = reference.TagNameOnly(ref)
   519  			if ref, ok := ref.(reference.NamedTagged); ok {
   520  				trustedRef, err := translator(ctx, ref)
   521  				if err != nil {
   522  					return nil, nil, err
   523  				}
   524  
   525  				line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef)))
   526  				resolvedTags = append(resolvedTags, &resolvedTag{
   527  					digestRef: trustedRef,
   528  					tagRef:    ref,
   529  				})
   530  			}
   531  		}
   532  
   533  		_, err := fmt.Fprintln(buf, line)
   534  		if err != nil {
   535  			return nil, nil, err
   536  		}
   537  	}
   538  
   539  	return buf.Bytes(), resolvedTags, scanner.Err()
   540  }
   541  
   542  // replaceDockerfileForContentTrust wraps the given input tar archive stream and
   543  // uses the translator to replace the Dockerfile which uses a trusted reference.
   544  // Returns a new tar archive stream with the replaced Dockerfile.
   545  func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser {
   546  	pipeReader, pipeWriter := io.Pipe()
   547  	go func() {
   548  		tarReader := tar.NewReader(inputTarStream)
   549  		tarWriter := tar.NewWriter(pipeWriter)
   550  
   551  		defer inputTarStream.Close()
   552  
   553  		for {
   554  			hdr, err := tarReader.Next()
   555  			if err == io.EOF {
   556  				// Signals end of archive.
   557  				tarWriter.Close()
   558  				pipeWriter.Close()
   559  				return
   560  			}
   561  			if err != nil {
   562  				pipeWriter.CloseWithError(err)
   563  				return
   564  			}
   565  
   566  			content := io.Reader(tarReader)
   567  			if hdr.Name == dockerfileName {
   568  				// This entry is the Dockerfile. Since the tar archive was
   569  				// generated from a directory on the local filesystem, the
   570  				// Dockerfile will only appear once in the archive.
   571  				var newDockerfile []byte
   572  				newDockerfile, *resolvedTags, err = rewriteDockerfileFromForContentTrust(ctx, content, translator)
   573  				if err != nil {
   574  					pipeWriter.CloseWithError(err)
   575  					return
   576  				}
   577  				hdr.Size = int64(len(newDockerfile))
   578  				content = bytes.NewBuffer(newDockerfile)
   579  			}
   580  
   581  			if err := tarWriter.WriteHeader(hdr); err != nil {
   582  				pipeWriter.CloseWithError(err)
   583  				return
   584  			}
   585  
   586  			if _, err := io.Copy(tarWriter, content); err != nil {
   587  				pipeWriter.CloseWithError(err)
   588  				return
   589  			}
   590  		}
   591  	}()
   592  
   593  	return pipeReader
   594  }
   595  
   596  func imageBuildOptions(dockerCli command.Cli, options buildOptions) types.ImageBuildOptions {
   597  	configFile := dockerCli.ConfigFile()
   598  	return types.ImageBuildOptions{
   599  		Memory:         options.memory.Value(),
   600  		MemorySwap:     options.memorySwap.Value(),
   601  		Tags:           options.tags.GetAll(),
   602  		SuppressOutput: options.quiet,
   603  		NoCache:        options.noCache,
   604  		Remove:         options.rm,
   605  		ForceRemove:    options.forceRm,
   606  		PullParent:     options.pull,
   607  		Isolation:      container.Isolation(options.isolation),
   608  		CPUSetCPUs:     options.cpuSetCpus,
   609  		CPUSetMems:     options.cpuSetMems,
   610  		CPUShares:      options.cpuShares,
   611  		CPUQuota:       options.cpuQuota,
   612  		CPUPeriod:      options.cpuPeriod,
   613  		CgroupParent:   options.cgroupParent,
   614  		ShmSize:        options.shmSize.Value(),
   615  		Ulimits:        options.ulimits.GetList(),
   616  		BuildArgs:      configFile.ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(options.buildArgs.GetAll())),
   617  		Labels:         opts.ConvertKVStringsToMap(options.labels.GetAll()),
   618  		CacheFrom:      options.cacheFrom,
   619  		SecurityOpt:    options.securityOpt,
   620  		NetworkMode:    options.networkMode,
   621  		Squash:         options.squash,
   622  		ExtraHosts:     options.extraHosts.GetAll(),
   623  		Target:         options.target,
   624  		Platform:       options.platform,
   625  	}
   626  }
   627  
   628  func parseOutputs(inp []string) ([]types.ImageBuildOutput, error) {
   629  	var outs []types.ImageBuildOutput
   630  	if len(inp) == 0 {
   631  		return nil, nil
   632  	}
   633  	for _, s := range inp {
   634  		csvReader := csv.NewReader(strings.NewReader(s))
   635  		fields, err := csvReader.Read()
   636  		if err != nil {
   637  			return nil, err
   638  		}
   639  		if len(fields) == 1 && fields[0] == s && !strings.HasPrefix(s, "type=") {
   640  			if s == "-" {
   641  				outs = append(outs, types.ImageBuildOutput{
   642  					Type: "tar",
   643  					Attrs: map[string]string{
   644  						"dest": s,
   645  					},
   646  				})
   647  			} else {
   648  				outs = append(outs, types.ImageBuildOutput{
   649  					Type: "local",
   650  					Attrs: map[string]string{
   651  						"dest": s,
   652  					},
   653  				})
   654  			}
   655  			continue
   656  		}
   657  
   658  		out := types.ImageBuildOutput{
   659  			Attrs: map[string]string{},
   660  		}
   661  		for _, field := range fields {
   662  			parts := strings.SplitN(field, "=", 2)
   663  			if len(parts) != 2 {
   664  				return nil, errors.Errorf("invalid value %s", field)
   665  			}
   666  			key := strings.ToLower(parts[0])
   667  			value := parts[1]
   668  			switch key {
   669  			case "type":
   670  				out.Type = value
   671  			default:
   672  				out.Attrs[key] = value
   673  			}
   674  		}
   675  		if out.Type == "" {
   676  			return nil, errors.Errorf("type is required for output")
   677  		}
   678  		outs = append(outs, out)
   679  	}
   680  	return outs, nil
   681  }