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 }