github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/image/build.go (about)

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