github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/compat/images_build.go (about) 1 package compat 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/containers/buildah" 17 buildahDefine "github.com/containers/buildah/define" 18 "github.com/containers/buildah/pkg/parse" 19 "github.com/containers/image/v5/types" 20 "github.com/hanks177/podman/v4/libpod" 21 "github.com/hanks177/podman/v4/pkg/api/handlers/utils" 22 api "github.com/hanks177/podman/v4/pkg/api/types" 23 "github.com/hanks177/podman/v4/pkg/auth" 24 "github.com/hanks177/podman/v4/pkg/channel" 25 "github.com/hanks177/podman/v4/pkg/rootless" 26 "github.com/containers/storage/pkg/archive" 27 "github.com/docker/docker/pkg/jsonmessage" 28 "github.com/gorilla/schema" 29 "github.com/opencontainers/runtime-spec/specs-go" 30 "github.com/pkg/errors" 31 "github.com/sirupsen/logrus" 32 ) 33 34 func BuildImage(w http.ResponseWriter, r *http.Request) { 35 if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { 36 contentType := hdr[0] 37 switch contentType { 38 case "application/tar": 39 logrus.Infof("tar file content type is %s, should use \"application/x-tar\" content type", contentType) 40 case "application/x-tar": 41 break 42 default: 43 if utils.IsLibpodRequest(r) { 44 utils.BadRequest(w, "Content-Type", hdr[0], 45 fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) 46 return 47 } 48 logrus.Infof("tar file content type is %s, should use \"application/x-tar\" content type", contentType) 49 } 50 } 51 52 contextDirectory, err := extractTarFile(r) 53 if err != nil { 54 utils.InternalServerError(w, err) 55 return 56 } 57 58 defer func() { 59 if logrus.IsLevelEnabled(logrus.DebugLevel) { 60 if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found { 61 if keep, _ := strconv.ParseBool(v); keep { 62 return 63 } 64 } 65 } 66 err := os.RemoveAll(filepath.Dir(contextDirectory)) 67 if err != nil { 68 logrus.Warn(errors.Wrapf(err, "failed to remove build scratch directory %q", filepath.Dir(contextDirectory))) 69 } 70 }() 71 72 query := struct { 73 AddHosts string `schema:"extrahosts"` 74 AdditionalCapabilities string `schema:"addcaps"` 75 AdditionalBuildContexts string `schema:"additionalbuildcontexts"` 76 AllPlatforms bool `schema:"allplatforms"` 77 Annotations string `schema:"annotations"` 78 AppArmor string `schema:"apparmor"` 79 BuildArgs string `schema:"buildargs"` 80 CacheFrom string `schema:"cachefrom"` 81 CgroupParent string `schema:"cgroupparent"` // nolint 82 Compression uint64 `schema:"compression"` 83 ConfigureNetwork string `schema:"networkmode"` 84 CPPFlags string `schema:"cppflags"` 85 CpuPeriod uint64 `schema:"cpuperiod"` // nolint 86 CpuQuota int64 `schema:"cpuquota"` // nolint 87 CpuSetCpus string `schema:"cpusetcpus"` // nolint 88 CpuSetMems string `schema:"cpusetmems"` // nolint 89 CpuShares uint64 `schema:"cpushares"` // nolint 90 DNSOptions string `schema:"dnsoptions"` 91 DNSSearch string `schema:"dnssearch"` 92 DNSServers string `schema:"dnsservers"` 93 Devices string `schema:"devices"` 94 Dockerfile string `schema:"dockerfile"` 95 DropCapabilities string `schema:"dropcaps"` 96 Envs []string `schema:"setenv"` 97 Excludes string `schema:"excludes"` 98 ForceRm bool `schema:"forcerm"` 99 From string `schema:"from"` 100 HTTPProxy bool `schema:"httpproxy"` 101 IdentityLabel bool `schema:"identitylabel"` 102 Ignore bool `schema:"ignore"` 103 Isolation string `schema:"isolation"` 104 Jobs int `schema:"jobs"` // nolint 105 LabelOpts string `schema:"labelopts"` 106 Labels string `schema:"labels"` 107 Layers bool `schema:"layers"` 108 LogRusage bool `schema:"rusage"` 109 Manifest string `schema:"manifest"` 110 MemSwap int64 `schema:"memswap"` 111 Memory int64 `schema:"memory"` 112 NamespaceOptions string `schema:"nsoptions"` 113 NoCache bool `schema:"nocache"` 114 OSFeatures []string `schema:"osfeature"` 115 OSVersion string `schema:"osversion"` 116 OutputFormat string `schema:"outputformat"` 117 Platform []string `schema:"platform"` 118 Pull bool `schema:"pull"` 119 PullPolicy string `schema:"pullpolicy"` 120 Quiet bool `schema:"q"` 121 Registry string `schema:"registry"` 122 Rm bool `schema:"rm"` 123 RusageLogFile string `schema:"rusagelogfile"` 124 Remote string `schema:"remote"` 125 Seccomp string `schema:"seccomp"` 126 Secrets string `schema:"secrets"` 127 SecurityOpt string `schema:"securityopt"` 128 ShmSize int `schema:"shmsize"` 129 Squash bool `schema:"squash"` 130 TLSVerify bool `schema:"tlsVerify"` 131 Tags []string `schema:"t"` 132 Target string `schema:"target"` 133 Timestamp int64 `schema:"timestamp"` 134 Ulimits string `schema:"ulimits"` 135 UnsetEnvs []string `schema:"unsetenv"` 136 }{ 137 Dockerfile: "Dockerfile", 138 IdentityLabel: true, 139 Registry: "docker.io", 140 Rm: true, 141 ShmSize: 64 * 1024 * 1024, 142 } 143 144 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 145 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 146 utils.Error(w, http.StatusBadRequest, err) 147 return 148 } 149 150 // if layers field not set assume its not from a valid podman-client 151 // could be a docker client, set `layers=true` since that is the default 152 // expected behaviour 153 if !utils.IsLibpodRequest(r) { 154 if _, found := r.URL.Query()["layers"]; !found { 155 query.Layers = true 156 } 157 } 158 159 // convert tag formats 160 tags := query.Tags 161 162 // convert addcaps formats 163 var addCaps = []string{} 164 if _, found := r.URL.Query()["addcaps"]; found { 165 var m = []string{} 166 if err := json.Unmarshal([]byte(query.AdditionalCapabilities), &m); err != nil { 167 utils.BadRequest(w, "addcaps", query.AdditionalCapabilities, err) 168 return 169 } 170 addCaps = m 171 } 172 173 // convert addcaps formats 174 containerFiles := []string{} 175 // Tells if query parameter `dockerfile` is set or not. 176 dockerFileSet := false 177 if utils.IsLibpodRequest(r) && query.Remote != "" { 178 // The context directory could be a URL. Try to handle that. 179 anchorDir, err := ioutil.TempDir(parse.GetTempDir(), "libpod_builder") 180 if err != nil { 181 utils.InternalServerError(w, err) 182 } 183 tempDir, subDir, err := buildahDefine.TempDirForURL(anchorDir, "buildah", query.Remote) 184 if err != nil { 185 utils.InternalServerError(w, err) 186 } 187 if tempDir != "" { 188 // We had to download it to a temporary directory. 189 // Delete it later. 190 defer func() { 191 if err = os.RemoveAll(tempDir); err != nil { 192 // We are deleting this on server so log on server end 193 // client does not have to worry about server cleanup. 194 logrus.Errorf("Cannot delete downloaded temp dir %q: %s", tempDir, err) 195 } 196 }() 197 contextDirectory = filepath.Join(tempDir, subDir) 198 } else { 199 // Nope, it was local. Use it as is. 200 absDir, err := filepath.Abs(query.Remote) 201 if err != nil { 202 utils.BadRequest(w, "remote", query.Remote, err) 203 } 204 contextDirectory = absDir 205 } 206 } else { 207 if _, found := r.URL.Query()["dockerfile"]; found { 208 var m = []string{} 209 if err := json.Unmarshal([]byte(query.Dockerfile), &m); err != nil { 210 // it's not json, assume just a string 211 m = []string{filepath.Join(contextDirectory, query.Dockerfile)} 212 } 213 containerFiles = m 214 dockerFileSet = true 215 } 216 } 217 218 if !dockerFileSet { 219 containerFiles = []string{filepath.Join(contextDirectory, "Dockerfile")} 220 if utils.IsLibpodRequest(r) { 221 containerFiles = []string{filepath.Join(contextDirectory, "Containerfile")} 222 if _, err = os.Stat(containerFiles[0]); err != nil { 223 containerFiles = []string{filepath.Join(contextDirectory, "Dockerfile")} 224 if _, err1 := os.Stat(containerFiles[0]); err1 != nil { 225 utils.BadRequest(w, "dockerfile", query.Dockerfile, err) 226 } 227 } 228 } 229 } 230 231 addhosts := []string{} 232 if _, found := r.URL.Query()["extrahosts"]; found { 233 if err := json.Unmarshal([]byte(query.AddHosts), &addhosts); err != nil { 234 utils.BadRequest(w, "extrahosts", query.AddHosts, err) 235 return 236 } 237 } 238 239 compression := archive.Compression(query.Compression) 240 241 // convert dropcaps formats 242 var dropCaps = []string{} 243 if _, found := r.URL.Query()["dropcaps"]; found { 244 var m = []string{} 245 if err := json.Unmarshal([]byte(query.DropCapabilities), &m); err != nil { 246 utils.BadRequest(w, "dropcaps", query.DropCapabilities, err) 247 return 248 } 249 dropCaps = m 250 } 251 252 // convert devices formats 253 var devices = []string{} 254 if _, found := r.URL.Query()["devices"]; found { 255 var m = []string{} 256 if err := json.Unmarshal([]byte(query.Devices), &m); err != nil { 257 utils.BadRequest(w, "devices", query.Devices, err) 258 return 259 } 260 devices = m 261 } 262 263 var dnsservers = []string{} 264 if _, found := r.URL.Query()["dnsservers"]; found { 265 var m = []string{} 266 if err := json.Unmarshal([]byte(query.DNSServers), &m); err != nil { 267 utils.BadRequest(w, "dnsservers", query.DNSServers, err) 268 return 269 } 270 dnsservers = m 271 } 272 273 var dnsoptions = []string{} 274 if _, found := r.URL.Query()["dnsoptions"]; found { 275 var m = []string{} 276 if err := json.Unmarshal([]byte(query.DNSOptions), &m); err != nil { 277 utils.BadRequest(w, "dnsoptions", query.DNSOptions, err) 278 return 279 } 280 dnsoptions = m 281 } 282 283 var dnssearch = []string{} 284 if _, found := r.URL.Query()["dnssearch"]; found { 285 var m = []string{} 286 if err := json.Unmarshal([]byte(query.DNSSearch), &m); err != nil { 287 utils.BadRequest(w, "dnssearches", query.DNSSearch, err) 288 return 289 } 290 dnssearch = m 291 } 292 293 var secrets = []string{} 294 if _, found := r.URL.Query()["secrets"]; found { 295 var m = []string{} 296 if err := json.Unmarshal([]byte(query.Secrets), &m); err != nil { 297 utils.BadRequest(w, "secrets", query.Secrets, err) 298 return 299 } 300 301 // for podman-remote all secrets must be picked from context director 302 // hence modify src so contextdir is added as prefix 303 304 for _, secret := range m { 305 secretOpt := strings.Split(secret, ",") 306 if len(secretOpt) > 0 { 307 modifiedOpt := []string{} 308 for _, token := range secretOpt { 309 arr := strings.SplitN(token, "=", 2) 310 if len(arr) > 1 { 311 if arr[0] == "src" { 312 /* move secret away from contextDir */ 313 /* to make sure we dont accidentally commit temporary secrets to image*/ 314 builderDirectory, _ := filepath.Split(contextDirectory) 315 // following path is outside build context 316 newSecretPath := filepath.Join(builderDirectory, arr[1]) 317 oldSecretPath := filepath.Join(contextDirectory, arr[1]) 318 err := os.Rename(oldSecretPath, newSecretPath) 319 if err != nil { 320 utils.BadRequest(w, "secrets", query.Secrets, err) 321 return 322 } 323 324 modifiedSrc := fmt.Sprintf("src=%s", newSecretPath) 325 modifiedOpt = append(modifiedOpt, modifiedSrc) 326 } else { 327 modifiedOpt = append(modifiedOpt, token) 328 } 329 } 330 } 331 secrets = append(secrets, strings.Join(modifiedOpt, ",")) 332 } 333 } 334 } 335 336 var output string 337 if len(tags) > 0 { 338 possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, tags[0]) 339 if err != nil { 340 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) 341 return 342 } 343 output = possiblyNormalizedName 344 } 345 format := buildah.Dockerv2ImageManifest 346 registry := query.Registry 347 isolation := buildah.IsolationDefault 348 if utils.IsLibpodRequest(r) { 349 var err error 350 isolation, err = parseLibPodIsolation(query.Isolation) 351 if err != nil { 352 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to parse isolation")) 353 return 354 } 355 356 // make sure to force rootless as rootless otherwise buildah runs code which is intended to be run only as root. 357 if isolation == buildah.IsolationOCI && rootless.IsRootless() { 358 isolation = buildah.IsolationOCIRootless 359 } 360 registry = "" 361 format = query.OutputFormat 362 } else { 363 if _, found := r.URL.Query()["isolation"]; found { 364 if query.Isolation != "" && query.Isolation != "default" { 365 logrus.Debugf("invalid `isolation` parameter: %q", query.Isolation) 366 } 367 } 368 } 369 var additionalTags []string // nolint 370 for i := 1; i < len(tags); i++ { 371 possiblyNormalizedTag, err := utils.NormalizeToDockerHub(r, tags[i]) 372 if err != nil { 373 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) 374 return 375 } 376 additionalTags = append(additionalTags, possiblyNormalizedTag) 377 } 378 379 var additionalBuildContexts = map[string]*buildahDefine.AdditionalBuildContext{} 380 if _, found := r.URL.Query()["additionalbuildcontexts"]; found { 381 if err := json.Unmarshal([]byte(query.AdditionalBuildContexts), &additionalBuildContexts); err != nil { 382 utils.BadRequest(w, "additionalbuildcontexts", query.AdditionalBuildContexts, err) 383 return 384 } 385 } 386 387 var buildArgs = map[string]string{} 388 if _, found := r.URL.Query()["buildargs"]; found { 389 if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { 390 utils.BadRequest(w, "buildargs", query.BuildArgs, err) 391 return 392 } 393 } 394 395 var excludes = []string{} 396 if _, found := r.URL.Query()["excludes"]; found { 397 if err := json.Unmarshal([]byte(query.Excludes), &excludes); err != nil { 398 utils.BadRequest(w, "excludes", query.Excludes, err) 399 return 400 } 401 } 402 403 // convert annotations formats 404 var annotations = []string{} 405 if _, found := r.URL.Query()["annotations"]; found { 406 if err := json.Unmarshal([]byte(query.Annotations), &annotations); err != nil { 407 utils.BadRequest(w, "annotations", query.Annotations, err) 408 return 409 } 410 } 411 412 // convert cppflags formats 413 var cppflags = []string{} 414 if _, found := r.URL.Query()["cppflags"]; found { 415 if err := json.Unmarshal([]byte(query.CPPFlags), &cppflags); err != nil { 416 utils.BadRequest(w, "cppflags", query.CPPFlags, err) 417 return 418 } 419 } 420 421 // convert nsoptions formats 422 nsoptions := buildah.NamespaceOptions{} 423 if _, found := r.URL.Query()["nsoptions"]; found { 424 if err := json.Unmarshal([]byte(query.NamespaceOptions), &nsoptions); err != nil { 425 utils.BadRequest(w, "nsoptions", query.NamespaceOptions, err) 426 return 427 } 428 } else { 429 nsoptions = append(nsoptions, buildah.NamespaceOption{ 430 Name: string(specs.NetworkNamespace), 431 Host: true, 432 }) 433 } 434 // convert label formats 435 var labels = []string{} 436 if _, found := r.URL.Query()["labels"]; found { 437 makeLabels := make(map[string]string) 438 err := json.Unmarshal([]byte(query.Labels), &makeLabels) 439 if err == nil { 440 for k, v := range makeLabels { 441 labels = append(labels, k+"="+v) 442 } 443 } else { 444 if err := json.Unmarshal([]byte(query.Labels), &labels); err != nil { 445 utils.BadRequest(w, "labels", query.Labels, err) 446 return 447 } 448 } 449 } 450 451 jobs := 1 452 if _, found := r.URL.Query()["jobs"]; found { 453 jobs = query.Jobs 454 } 455 456 var ( 457 labelOpts = []string{} 458 seccomp string 459 apparmor string 460 ) 461 462 if utils.IsLibpodRequest(r) { 463 seccomp = query.Seccomp 464 apparmor = query.AppArmor 465 // convert labelopts formats 466 if _, found := r.URL.Query()["labelopts"]; found { 467 var m = []string{} 468 if err := json.Unmarshal([]byte(query.LabelOpts), &m); err != nil { 469 utils.BadRequest(w, "labelopts", query.LabelOpts, err) 470 return 471 } 472 labelOpts = m 473 } 474 } else { 475 // handle security-opt 476 if _, found := r.URL.Query()["securityopt"]; found { 477 var securityOpts = []string{} 478 if err := json.Unmarshal([]byte(query.SecurityOpt), &securityOpts); err != nil { 479 utils.BadRequest(w, "securityopt", query.SecurityOpt, err) 480 return 481 } 482 for _, opt := range securityOpts { 483 if opt == "no-new-privileges" { 484 utils.BadRequest(w, "securityopt", query.SecurityOpt, errors.New("no-new-privileges is not supported")) 485 return 486 } 487 con := strings.SplitN(opt, "=", 2) 488 if len(con) != 2 { 489 utils.BadRequest(w, "securityopt", query.SecurityOpt, errors.Errorf("Invalid --security-opt name=value pair: %q", opt)) 490 return 491 } 492 493 switch con[0] { 494 case "label": 495 labelOpts = append(labelOpts, con[1]) 496 case "apparmor": 497 apparmor = con[1] 498 case "seccomp": 499 seccomp = con[1] 500 default: 501 utils.BadRequest(w, "securityopt", query.SecurityOpt, errors.Errorf("Invalid --security-opt 2: %q", opt)) 502 return 503 } 504 } 505 } 506 } 507 508 // convert ulimits formats 509 var ulimits = []string{} 510 if _, found := r.URL.Query()["ulimits"]; found { 511 var m = []string{} 512 if err := json.Unmarshal([]byte(query.Ulimits), &m); err != nil { 513 utils.BadRequest(w, "ulimits", query.Ulimits, err) 514 return 515 } 516 ulimits = m 517 } 518 519 pullPolicy := buildahDefine.PullIfMissing 520 if utils.IsLibpodRequest(r) { 521 pullPolicy = buildahDefine.PolicyMap[query.PullPolicy] 522 } else { 523 if _, found := r.URL.Query()["pull"]; found { 524 if query.Pull { 525 pullPolicy = buildahDefine.PullAlways 526 } 527 } 528 } 529 530 creds, authfile, err := auth.GetCredentials(r) 531 if err != nil { 532 // Credential value(s) not returned as their value is not human readable 533 utils.Error(w, http.StatusBadRequest, err) 534 return 535 } 536 defer auth.RemoveAuthfile(authfile) 537 538 fromImage := query.From 539 if fromImage != "" { 540 possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, fromImage) 541 if err != nil { 542 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) 543 return 544 } 545 fromImage = possiblyNormalizedName 546 } 547 548 systemContext := &types.SystemContext{ 549 AuthFilePath: authfile, 550 DockerAuthConfig: creds, 551 } 552 utils.PossiblyEnforceDockerHub(r, systemContext) 553 554 if _, found := r.URL.Query()["tlsVerify"]; found { 555 systemContext.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) 556 systemContext.OCIInsecureSkipTLSVerify = !query.TLSVerify 557 systemContext.DockerDaemonInsecureSkipTLSVerify = !query.TLSVerify 558 } 559 // Channels all mux'ed in select{} below to follow API build protocol 560 stdout := channel.NewWriter(make(chan []byte)) 561 defer stdout.Close() 562 563 auxout := channel.NewWriter(make(chan []byte)) 564 defer auxout.Close() 565 566 stderr := channel.NewWriter(make(chan []byte)) 567 defer stderr.Close() 568 569 reporter := channel.NewWriter(make(chan []byte)) 570 defer reporter.Close() 571 572 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 573 buildOptions := buildahDefine.BuildOptions{ 574 AddCapabilities: addCaps, 575 AdditionalBuildContexts: additionalBuildContexts, 576 AdditionalTags: additionalTags, 577 Annotations: annotations, 578 CPPFlags: cppflags, 579 Args: buildArgs, 580 AllPlatforms: query.AllPlatforms, 581 CommonBuildOpts: &buildah.CommonBuildOptions{ 582 AddHost: addhosts, 583 ApparmorProfile: apparmor, 584 CPUPeriod: query.CpuPeriod, 585 CPUQuota: query.CpuQuota, 586 CPUSetCPUs: query.CpuSetCpus, 587 CPUSetMems: query.CpuSetMems, 588 CPUShares: query.CpuShares, 589 CgroupParent: query.CgroupParent, 590 DNSOptions: dnsoptions, 591 DNSSearch: dnssearch, 592 DNSServers: dnsservers, 593 HTTPProxy: query.HTTPProxy, 594 IdentityLabel: types.NewOptionalBool(query.IdentityLabel), 595 LabelOpts: labelOpts, 596 Memory: query.Memory, 597 MemorySwap: query.MemSwap, 598 SeccompProfilePath: seccomp, 599 ShmSize: strconv.Itoa(query.ShmSize), 600 Ulimit: ulimits, 601 Secrets: secrets, 602 }, 603 Compression: compression, 604 ConfigureNetwork: parseNetworkConfigurationPolicy(query.ConfigureNetwork), 605 ContextDirectory: contextDirectory, 606 Devices: devices, 607 DropCapabilities: dropCaps, 608 Envs: query.Envs, 609 Err: auxout, 610 Excludes: excludes, 611 ForceRmIntermediateCtrs: query.ForceRm, 612 From: fromImage, 613 IgnoreUnrecognizedInstructions: query.Ignore, 614 Isolation: isolation, 615 Jobs: &jobs, 616 Labels: labels, 617 Layers: query.Layers, 618 LogRusage: query.LogRusage, 619 Manifest: query.Manifest, 620 MaxPullPushRetries: 3, 621 NamespaceOptions: nsoptions, 622 NoCache: query.NoCache, 623 OSFeatures: query.OSFeatures, 624 OSVersion: query.OSVersion, 625 Out: stdout, 626 Output: output, 627 OutputFormat: format, 628 PullPolicy: pullPolicy, 629 PullPushRetryDelay: 2 * time.Second, 630 Quiet: query.Quiet, 631 Registry: registry, 632 RemoveIntermediateCtrs: query.Rm, 633 ReportWriter: reporter, 634 RusageLogFile: query.RusageLogFile, 635 Squash: query.Squash, 636 SystemContext: systemContext, 637 Target: query.Target, 638 UnsetEnvs: query.UnsetEnvs, 639 } 640 641 for _, platformSpec := range query.Platform { 642 os, arch, variant, err := parse.Platform(platformSpec) 643 if err != nil { 644 utils.BadRequest(w, "platform", platformSpec, err) 645 return 646 } 647 buildOptions.Platforms = append(buildOptions.Platforms, struct{ OS, Arch, Variant string }{ 648 OS: os, 649 Arch: arch, 650 Variant: variant, 651 }) 652 } 653 if _, found := r.URL.Query()["timestamp"]; found { 654 ts := time.Unix(query.Timestamp, 0) 655 buildOptions.Timestamp = &ts 656 } 657 658 var ( 659 imageID string 660 success bool 661 ) 662 663 runCtx, cancel := context.WithCancel(context.Background()) 664 go func() { 665 defer cancel() 666 imageID, _, err = runtime.Build(r.Context(), buildOptions, containerFiles...) 667 if err == nil { 668 success = true 669 } else { 670 stderr.Write([]byte(err.Error() + "\n")) 671 } 672 }() 673 674 flush := func() { 675 if flusher, ok := w.(http.Flusher); ok { 676 flusher.Flush() 677 } 678 } 679 680 // Send headers and prime client for stream to come 681 w.Header().Set("Content-Type", "application/json") 682 w.WriteHeader(http.StatusOK) 683 flush() 684 685 body := w.(io.Writer) 686 if logrus.IsLevelEnabled(logrus.DebugLevel) { 687 if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found { 688 if keep, _ := strconv.ParseBool(v); keep { 689 t, _ := ioutil.TempFile("", "build_*_server") 690 defer t.Close() 691 body = io.MultiWriter(t, w) 692 } 693 } 694 } 695 696 enc := json.NewEncoder(body) 697 enc.SetEscapeHTML(true) 698 var stepErrors []string 699 700 for { 701 type BuildResponse struct { 702 Stream string `json:"stream,omitempty"` 703 Error *jsonmessage.JSONError `json:"errorDetail,omitempty"` 704 // NOTE: `error` is being deprecated check https://github.com/moby/moby/blob/master/pkg/jsonmessage/jsonmessage.go#L148 705 ErrorMessage string `json:"error,omitempty"` // deprecate this slowly 706 Aux json.RawMessage `json:"aux,omitempty"` 707 } 708 m := BuildResponse{} 709 710 select { 711 case e := <-stdout.Chan(): 712 m.Stream = string(e) 713 if err := enc.Encode(m); err != nil { 714 stderr.Write([]byte(err.Error())) 715 } 716 flush() 717 case e := <-reporter.Chan(): 718 m.Stream = string(e) 719 if err := enc.Encode(m); err != nil { 720 stderr.Write([]byte(err.Error())) 721 } 722 flush() 723 case e := <-auxout.Chan(): 724 if !query.Quiet { 725 m.Stream = string(e) 726 if err := enc.Encode(m); err != nil { 727 stderr.Write([]byte(err.Error())) 728 } 729 flush() 730 } else { 731 stepErrors = append(stepErrors, string(e)) 732 } 733 case e := <-stderr.Chan(): 734 // Docker-API Compat parity : Build failed so 735 // output all step errors irrespective of quiet 736 // flag. 737 for _, stepError := range stepErrors { 738 t := BuildResponse{} 739 t.Stream = stepError 740 if err := enc.Encode(t); err != nil { 741 stderr.Write([]byte(err.Error())) 742 } 743 flush() 744 } 745 m.ErrorMessage = string(e) 746 m.Error = &jsonmessage.JSONError{ 747 Message: m.ErrorMessage, 748 } 749 if err := enc.Encode(m); err != nil { 750 logrus.Warnf("Failed to json encode error %v", err) 751 } 752 flush() 753 return 754 case <-runCtx.Done(): 755 if success { 756 if !utils.IsLibpodRequest(r) && !query.Quiet { 757 m.Aux = []byte(fmt.Sprintf(`{"ID":"sha256:%s"}`, imageID)) 758 if err := enc.Encode(m); err != nil { 759 logrus.Warnf("failed to json encode error %v", err) 760 } 761 m.Aux = nil 762 m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID) 763 if err := enc.Encode(m); err != nil { 764 logrus.Warnf("Failed to json encode error %v", err) 765 } 766 flush() 767 for _, tag := range tags { 768 m.Stream = fmt.Sprintf("Successfully tagged %s\n", tag) 769 if err := enc.Encode(m); err != nil { 770 logrus.Warnf("Failed to json encode error %v", err) 771 } 772 flush() 773 } 774 } 775 } 776 flush() 777 return 778 case <-r.Context().Done(): 779 cancel() 780 logrus.Infof("Client disconnect reported for build %q / %q.", registry, query.Dockerfile) 781 return 782 } 783 } 784 } 785 786 func parseNetworkConfigurationPolicy(network string) buildah.NetworkConfigurationPolicy { 787 if val, err := strconv.Atoi(network); err == nil { 788 return buildah.NetworkConfigurationPolicy(val) 789 } 790 switch network { 791 case "NetworkDefault": 792 return buildah.NetworkDefault 793 case "NetworkDisabled": 794 return buildah.NetworkDisabled 795 case "NetworkEnabled": 796 return buildah.NetworkEnabled 797 default: 798 return buildah.NetworkDefault 799 } 800 } 801 802 func parseLibPodIsolation(isolation string) (buildah.Isolation, error) { // nolint 803 if val, err := strconv.Atoi(isolation); err == nil { 804 return buildah.Isolation(val), nil 805 } 806 return parse.IsolationOption(isolation) 807 } 808 809 func extractTarFile(r *http.Request) (string, error) { 810 // build a home for the request body 811 anchorDir, err := ioutil.TempDir("", "libpod_builder") 812 if err != nil { 813 return "", err 814 } 815 816 path := filepath.Join(anchorDir, "tarBall") 817 tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) 818 if err != nil { 819 return "", err 820 } 821 defer tarBall.Close() 822 823 // Content-Length not used as too many existing API clients didn't honor it 824 _, err = io.Copy(tarBall, r.Body) 825 r.Body.Close() 826 if err != nil { 827 return "", fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI) 828 } 829 830 buildDir := filepath.Join(anchorDir, "build") 831 err = os.Mkdir(buildDir, 0o700) 832 if err != nil { 833 return "", err 834 } 835 836 _, _ = tarBall.Seek(0, 0) 837 err = archive.Untar(tarBall, buildDir, nil) 838 return buildDir, err 839 }