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