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