Files
podman/pkg/api/handlers/compat/images_build.go
Aditya R 75b236a2b4 compat,build: handle docker's preconfigured cacheTo,cacheFrom
Docker's newer clients popuates `cacheFrom` and `cacheTo` parameter
by default as empty array for all commands but buildah's design of
distributed cache expects this to be a repo not image hence parse
only the first populated repo and igore if empty array.

Signed-off-by: Aditya R <arajan@redhat.com>
2023-01-05 22:44:01 +00:00

904 lines
28 KiB
Go

package compat
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/containers/buildah"
buildahDefine "github.com/containers/buildah/define"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/pkg/api/handlers/utils"
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/containers/podman/v4/pkg/auth"
"github.com/containers/podman/v4/pkg/channel"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/storage/pkg/archive"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/gorilla/schema"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
func BuildImage(w http.ResponseWriter, r *http.Request) {
if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 {
contentType := hdr[0]
switch contentType {
case "application/tar":
logrus.Infof("tar file content type is %s, should use \"application/x-tar\" content type", contentType)
case "application/x-tar":
break
default:
if utils.IsLibpodRequest(r) {
utils.BadRequest(w, "Content-Type", hdr[0],
fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0]))
return
}
logrus.Infof("tar file content type is %s, should use \"application/x-tar\" content type", contentType)
}
}
contextDirectory, err := extractTarFile(r)
if err != nil {
utils.InternalServerError(w, err)
return
}
defer func() {
if logrus.IsLevelEnabled(logrus.DebugLevel) {
if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found {
if keep, _ := strconv.ParseBool(v); keep {
return
}
}
}
err := os.RemoveAll(filepath.Dir(contextDirectory))
if err != nil {
logrus.Warn(fmt.Errorf("failed to remove build scratch directory %q: %w", filepath.Dir(contextDirectory), err))
}
}()
query := struct {
AddHosts string `schema:"extrahosts"`
AdditionalCapabilities string `schema:"addcaps"`
AdditionalBuildContexts string `schema:"additionalbuildcontexts"`
AllPlatforms bool `schema:"allplatforms"`
Annotations string `schema:"annotations"`
AppArmor string `schema:"apparmor"`
BuildArgs string `schema:"buildargs"`
CacheFrom string `schema:"cachefrom"`
CacheTo string `schema:"cacheto"`
CacheTTL string `schema:"cachettl"`
CgroupParent string `schema:"cgroupparent"`
Compression uint64 `schema:"compression"`
ConfigureNetwork string `schema:"networkmode"`
CPPFlags string `schema:"cppflags"`
CpuPeriod uint64 `schema:"cpuperiod"` //nolint:revive,stylecheck
CpuQuota int64 `schema:"cpuquota"` //nolint:revive,stylecheck
CpuSetCpus string `schema:"cpusetcpus"` //nolint:revive,stylecheck
CpuSetMems string `schema:"cpusetmems"` //nolint:revive,stylecheck
CpuShares uint64 `schema:"cpushares"` //nolint:revive,stylecheck
DNSOptions string `schema:"dnsoptions"`
DNSSearch string `schema:"dnssearch"`
DNSServers string `schema:"dnsservers"`
Devices string `schema:"devices"`
Dockerfile string `schema:"dockerfile"`
DropCapabilities string `schema:"dropcaps"`
Envs []string `schema:"setenv"`
Excludes string `schema:"excludes"`
ForceRm bool `schema:"forcerm"`
From string `schema:"from"`
HTTPProxy bool `schema:"httpproxy"`
IdentityLabel bool `schema:"identitylabel"`
Ignore bool `schema:"ignore"`
Isolation string `schema:"isolation"`
Jobs int `schema:"jobs"`
LabelOpts string `schema:"labelopts"`
Labels string `schema:"labels"`
Layers bool `schema:"layers"`
LogRusage bool `schema:"rusage"`
Manifest string `schema:"manifest"`
MemSwap int64 `schema:"memswap"`
Memory int64 `schema:"memory"`
NamespaceOptions string `schema:"nsoptions"`
NoCache bool `schema:"nocache"`
OmitHistory bool `schema:"omithistory"`
OSFeatures []string `schema:"osfeature"`
OSVersion string `schema:"osversion"`
OutputFormat string `schema:"outputformat"`
Platform []string `schema:"platform"`
Pull bool `schema:"pull"`
PullPolicy string `schema:"pullpolicy"`
Quiet bool `schema:"q"`
Registry string `schema:"registry"`
Rm bool `schema:"rm"`
RusageLogFile string `schema:"rusagelogfile"`
Remote string `schema:"remote"`
Seccomp string `schema:"seccomp"`
Secrets string `schema:"secrets"`
SecurityOpt string `schema:"securityopt"`
ShmSize int `schema:"shmsize"`
SkipUnusedStages bool `schema:"skipunusedstages"`
Squash bool `schema:"squash"`
TLSVerify bool `schema:"tlsVerify"`
Tags []string `schema:"t"`
Target string `schema:"target"`
Timestamp int64 `schema:"timestamp"`
Ulimits string `schema:"ulimits"`
UnsetEnvs []string `schema:"unsetenv"`
}{
Dockerfile: "Dockerfile",
IdentityLabel: true,
Registry: "docker.io",
Rm: true,
ShmSize: 64 * 1024 * 1024,
TLSVerify: true,
SkipUnusedStages: true,
}
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, err)
return
}
// if layers field not set assume its not from a valid podman-client
// could be a docker client, set `layers=true` since that is the default
// expected behaviour
if !utils.IsLibpodRequest(r) {
if _, found := r.URL.Query()["layers"]; !found {
query.Layers = true
}
}
// convert tag formats
tags := query.Tags
// convert addcaps formats
var addCaps = []string{}
if _, found := r.URL.Query()["addcaps"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.AdditionalCapabilities), &m); err != nil {
utils.BadRequest(w, "addcaps", query.AdditionalCapabilities, err)
return
}
addCaps = m
}
// convert addcaps formats
containerFiles := []string{}
// Tells if query parameter `dockerfile` is set or not.
dockerFileSet := false
if utils.IsLibpodRequest(r) && query.Remote != "" {
// The context directory could be a URL. Try to handle that.
anchorDir, err := ioutil.TempDir(parse.GetTempDir(), "libpod_builder")
if err != nil {
utils.InternalServerError(w, err)
}
tempDir, subDir, err := buildahDefine.TempDirForURL(anchorDir, "buildah", query.Remote)
if err != nil {
utils.InternalServerError(w, err)
}
if tempDir != "" {
// We had to download it to a temporary directory.
// Delete it later.
defer func() {
if err = os.RemoveAll(tempDir); err != nil {
// We are deleting this on server so log on server end
// client does not have to worry about server cleanup.
logrus.Errorf("Cannot delete downloaded temp dir %q: %s", tempDir, err)
}
}()
contextDirectory = filepath.Join(tempDir, subDir)
} else {
// Nope, it was local. Use it as is.
absDir, err := filepath.Abs(query.Remote)
if err != nil {
utils.BadRequest(w, "remote", query.Remote, err)
}
contextDirectory = absDir
}
} else {
if _, found := r.URL.Query()["dockerfile"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.Dockerfile), &m); err != nil {
// it's not json, assume just a string
m = []string{filepath.Join(contextDirectory, query.Dockerfile)}
}
containerFiles = m
dockerFileSet = true
}
}
if !dockerFileSet {
containerFiles = []string{filepath.Join(contextDirectory, "Dockerfile")}
if utils.IsLibpodRequest(r) {
containerFiles = []string{filepath.Join(contextDirectory, "Containerfile")}
if _, err = os.Stat(containerFiles[0]); err != nil {
containerFiles = []string{filepath.Join(contextDirectory, "Dockerfile")}
if _, err1 := os.Stat(containerFiles[0]); err1 != nil {
utils.BadRequest(w, "dockerfile", query.Dockerfile, err)
}
}
}
}
addhosts := []string{}
if _, found := r.URL.Query()["extrahosts"]; found {
if err := json.Unmarshal([]byte(query.AddHosts), &addhosts); err != nil {
utils.BadRequest(w, "extrahosts", query.AddHosts, err)
return
}
}
compression := archive.Compression(query.Compression)
// convert dropcaps formats
var dropCaps = []string{}
if _, found := r.URL.Query()["dropcaps"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.DropCapabilities), &m); err != nil {
utils.BadRequest(w, "dropcaps", query.DropCapabilities, err)
return
}
dropCaps = m
}
// convert devices formats
var devices = []string{}
if _, found := r.URL.Query()["devices"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.Devices), &m); err != nil {
utils.BadRequest(w, "devices", query.Devices, err)
return
}
devices = m
}
var dnsservers = []string{}
if _, found := r.URL.Query()["dnsservers"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.DNSServers), &m); err != nil {
utils.BadRequest(w, "dnsservers", query.DNSServers, err)
return
}
dnsservers = m
}
var dnsoptions = []string{}
if _, found := r.URL.Query()["dnsoptions"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.DNSOptions), &m); err != nil {
utils.BadRequest(w, "dnsoptions", query.DNSOptions, err)
return
}
dnsoptions = m
}
var dnssearch = []string{}
if _, found := r.URL.Query()["dnssearch"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.DNSSearch), &m); err != nil {
utils.BadRequest(w, "dnssearches", query.DNSSearch, err)
return
}
dnssearch = m
}
var secrets = []string{}
if _, found := r.URL.Query()["secrets"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.Secrets), &m); err != nil {
utils.BadRequest(w, "secrets", query.Secrets, err)
return
}
// for podman-remote all secrets must be picked from context director
// hence modify src so contextdir is added as prefix
for _, secret := range m {
secretOpt := strings.Split(secret, ",")
if len(secretOpt) > 0 {
modifiedOpt := []string{}
for _, token := range secretOpt {
arr := strings.SplitN(token, "=", 2)
if len(arr) > 1 {
if arr[0] == "src" {
/* move secret away from contextDir */
/* to make sure we dont accidentally commit temporary secrets to image*/
builderDirectory, _ := filepath.Split(contextDirectory)
// following path is outside build context
newSecretPath := filepath.Join(builderDirectory, arr[1])
oldSecretPath := filepath.Join(contextDirectory, arr[1])
err := os.Rename(oldSecretPath, newSecretPath)
if err != nil {
utils.BadRequest(w, "secrets", query.Secrets, err)
return
}
modifiedSrc := fmt.Sprintf("src=%s", newSecretPath)
modifiedOpt = append(modifiedOpt, modifiedSrc)
} else {
modifiedOpt = append(modifiedOpt, token)
}
}
}
secrets = append(secrets, strings.Join(modifiedOpt, ","))
}
}
}
var output string
if len(tags) > 0 {
possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, tags[0])
if err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err))
return
}
output = possiblyNormalizedName
}
format := buildah.Dockerv2ImageManifest
registry := query.Registry
isolation := buildah.IsolationDefault
if utils.IsLibpodRequest(r) {
var err error
isolation, err = parseLibPodIsolation(query.Isolation)
if err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse isolation: %w", err))
return
}
// make sure to force rootless as rootless otherwise buildah runs code which is intended to be run only as root.
if isolation == buildah.IsolationOCI && rootless.IsRootless() {
isolation = buildah.IsolationOCIRootless
}
registry = ""
format = query.OutputFormat
} else {
if _, found := r.URL.Query()["isolation"]; found {
if query.Isolation != "" && query.Isolation != "default" {
logrus.Debugf("invalid `isolation` parameter: %q", query.Isolation)
}
}
}
var additionalTags []string
for i := 1; i < len(tags); i++ {
possiblyNormalizedTag, err := utils.NormalizeToDockerHub(r, tags[i])
if err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err))
return
}
additionalTags = append(additionalTags, possiblyNormalizedTag)
}
var additionalBuildContexts = map[string]*buildahDefine.AdditionalBuildContext{}
if _, found := r.URL.Query()["additionalbuildcontexts"]; found {
if err := json.Unmarshal([]byte(query.AdditionalBuildContexts), &additionalBuildContexts); err != nil {
utils.BadRequest(w, "additionalbuildcontexts", query.AdditionalBuildContexts, err)
return
}
}
// Docker clients, as of v19.03.0, populates `cacheFrom` and `cacheTo`
// parameter by default as empty array for all commands but buildah's
// design of distributed cache expects this to be a repo not image hence
// parse only the first populated repo and igore if empty array.
// Read more here: https://github.com/containers/podman/issues/15928
// TODO: Remove this when buildah's API is extended.
compatIgnoreForcedCacheOptions := func(queryStr string) string {
query := queryStr
if strings.HasPrefix(query, "[") {
query = ""
var arr []string
parseErr := json.Unmarshal([]byte(query), &arr)
if parseErr != nil {
if len(arr) > 0 {
query = arr[0]
}
}
}
return query
}
var cacheFrom reference.Named
if _, found := r.URL.Query()["cachefrom"]; found {
cacheFromQuery := compatIgnoreForcedCacheOptions(query.CacheFrom)
if cacheFromQuery != "" {
cacheFrom, err = parse.RepoNameToNamedReference(cacheFromQuery)
if err != nil {
utils.BadRequest(w, "cacheFrom", cacheFromQuery, err)
return
}
}
}
var cacheTo reference.Named
if _, found := r.URL.Query()["cacheto"]; found {
cacheToQuery := compatIgnoreForcedCacheOptions(query.CacheTo)
if cacheToQuery != "" {
cacheTo, err = parse.RepoNameToNamedReference(cacheToQuery)
if err != nil {
utils.BadRequest(w, "cacheto", cacheToQuery, err)
return
}
}
}
var cacheTTL time.Duration
if _, found := r.URL.Query()["cachettl"]; found {
cacheTTL, err = time.ParseDuration(query.CacheTTL)
if err != nil {
utils.BadRequest(w, "cachettl", query.CacheTTL, err)
return
}
}
var buildArgs = map[string]string{}
if _, found := r.URL.Query()["buildargs"]; found {
if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil {
utils.BadRequest(w, "buildargs", query.BuildArgs, err)
return
}
}
var excludes = []string{}
if _, found := r.URL.Query()["excludes"]; found {
if err := json.Unmarshal([]byte(query.Excludes), &excludes); err != nil {
utils.BadRequest(w, "excludes", query.Excludes, err)
return
}
}
// convert annotations formats
var annotations = []string{}
if _, found := r.URL.Query()["annotations"]; found {
if err := json.Unmarshal([]byte(query.Annotations), &annotations); err != nil {
utils.BadRequest(w, "annotations", query.Annotations, err)
return
}
}
// convert cppflags formats
var cppflags = []string{}
if _, found := r.URL.Query()["cppflags"]; found {
if err := json.Unmarshal([]byte(query.CPPFlags), &cppflags); err != nil {
utils.BadRequest(w, "cppflags", query.CPPFlags, err)
return
}
}
// convert nsoptions formats
nsoptions := buildah.NamespaceOptions{}
if _, found := r.URL.Query()["nsoptions"]; found {
if err := json.Unmarshal([]byte(query.NamespaceOptions), &nsoptions); err != nil {
utils.BadRequest(w, "nsoptions", query.NamespaceOptions, err)
return
}
} else {
nsoptions = append(nsoptions, buildah.NamespaceOption{
Name: string(specs.NetworkNamespace),
Host: true,
})
}
// convert label formats
var labels = []string{}
if _, found := r.URL.Query()["labels"]; found {
makeLabels := make(map[string]string)
err := json.Unmarshal([]byte(query.Labels), &makeLabels)
if err == nil {
for k, v := range makeLabels {
labels = append(labels, k+"="+v)
}
} else {
if err := json.Unmarshal([]byte(query.Labels), &labels); err != nil {
utils.BadRequest(w, "labels", query.Labels, err)
return
}
}
}
jobs := 1
if _, found := r.URL.Query()["jobs"]; found {
jobs = query.Jobs
}
var (
labelOpts = []string{}
seccomp string
apparmor string
)
if utils.IsLibpodRequest(r) {
seccomp = query.Seccomp
apparmor = query.AppArmor
// convert labelopts formats
if _, found := r.URL.Query()["labelopts"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.LabelOpts), &m); err != nil {
utils.BadRequest(w, "labelopts", query.LabelOpts, err)
return
}
labelOpts = m
}
} else {
// handle security-opt
if _, found := r.URL.Query()["securityopt"]; found {
var securityOpts = []string{}
if err := json.Unmarshal([]byte(query.SecurityOpt), &securityOpts); err != nil {
utils.BadRequest(w, "securityopt", query.SecurityOpt, err)
return
}
for _, opt := range securityOpts {
if opt == "no-new-privileges" {
utils.BadRequest(w, "securityopt", query.SecurityOpt, errors.New("no-new-privileges is not supported"))
return
}
con := strings.SplitN(opt, "=", 2)
if len(con) != 2 {
utils.BadRequest(w, "securityopt", query.SecurityOpt, fmt.Errorf("invalid --security-opt name=value pair: %q", opt))
return
}
switch con[0] {
case "label":
labelOpts = append(labelOpts, con[1])
case "apparmor":
apparmor = con[1]
case "seccomp":
seccomp = con[1]
default:
utils.BadRequest(w, "securityopt", query.SecurityOpt, fmt.Errorf("invalid --security-opt 2: %q", opt))
return
}
}
}
}
// convert ulimits formats
var ulimits = []string{}
if _, found := r.URL.Query()["ulimits"]; found {
var m = []string{}
if err := json.Unmarshal([]byte(query.Ulimits), &m); err != nil {
utils.BadRequest(w, "ulimits", query.Ulimits, err)
return
}
ulimits = m
}
pullPolicy := buildahDefine.PullIfMissing
if utils.IsLibpodRequest(r) {
pullPolicy = buildahDefine.PolicyMap[query.PullPolicy]
} else {
if _, found := r.URL.Query()["pull"]; found {
if query.Pull {
pullPolicy = buildahDefine.PullAlways
}
}
}
creds, authfile, err := auth.GetCredentials(r)
if err != nil {
// Credential value(s) not returned as their value is not human readable
utils.Error(w, http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
fromImage := query.From
if fromImage != "" {
possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, fromImage)
if err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err))
return
}
fromImage = possiblyNormalizedName
}
systemContext := &types.SystemContext{
AuthFilePath: authfile,
DockerAuthConfig: creds,
}
utils.PossiblyEnforceDockerHub(r, systemContext)
if _, found := r.URL.Query()["tlsVerify"]; found {
systemContext.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
systemContext.OCIInsecureSkipTLSVerify = !query.TLSVerify
systemContext.DockerDaemonInsecureSkipTLSVerify = !query.TLSVerify
}
// Channels all mux'ed in select{} below to follow API build protocol
stdout := channel.NewWriter(make(chan []byte))
defer stdout.Close()
auxout := channel.NewWriter(make(chan []byte))
defer auxout.Close()
stderr := channel.NewWriter(make(chan []byte))
defer stderr.Close()
reporter := channel.NewWriter(make(chan []byte))
defer reporter.Close()
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
buildOptions := buildahDefine.BuildOptions{
AddCapabilities: addCaps,
AdditionalBuildContexts: additionalBuildContexts,
AdditionalTags: additionalTags,
Annotations: annotations,
CPPFlags: cppflags,
CacheFrom: cacheFrom,
CacheTo: cacheTo,
CacheTTL: cacheTTL,
Args: buildArgs,
AllPlatforms: query.AllPlatforms,
CommonBuildOpts: &buildah.CommonBuildOptions{
AddHost: addhosts,
ApparmorProfile: apparmor,
CPUPeriod: query.CpuPeriod,
CPUQuota: query.CpuQuota,
CPUSetCPUs: query.CpuSetCpus,
CPUSetMems: query.CpuSetMems,
CPUShares: query.CpuShares,
CgroupParent: query.CgroupParent,
DNSOptions: dnsoptions,
DNSSearch: dnssearch,
DNSServers: dnsservers,
HTTPProxy: query.HTTPProxy,
IdentityLabel: types.NewOptionalBool(query.IdentityLabel),
LabelOpts: labelOpts,
Memory: query.Memory,
MemorySwap: query.MemSwap,
OmitHistory: query.OmitHistory,
SeccompProfilePath: seccomp,
ShmSize: strconv.Itoa(query.ShmSize),
Ulimit: ulimits,
Secrets: secrets,
},
Compression: compression,
ConfigureNetwork: parseNetworkConfigurationPolicy(query.ConfigureNetwork),
ContextDirectory: contextDirectory,
Devices: devices,
DropCapabilities: dropCaps,
Envs: query.Envs,
Err: auxout,
Excludes: excludes,
ForceRmIntermediateCtrs: query.ForceRm,
From: fromImage,
IgnoreUnrecognizedInstructions: query.Ignore,
Isolation: isolation,
Jobs: &jobs,
Labels: labels,
Layers: query.Layers,
LogRusage: query.LogRusage,
Manifest: query.Manifest,
MaxPullPushRetries: 3,
NamespaceOptions: nsoptions,
NoCache: query.NoCache,
OSFeatures: query.OSFeatures,
OSVersion: query.OSVersion,
Out: stdout,
Output: output,
OutputFormat: format,
PullPolicy: pullPolicy,
PullPushRetryDelay: 2 * time.Second,
Quiet: query.Quiet,
Registry: registry,
RemoveIntermediateCtrs: query.Rm,
ReportWriter: reporter,
RusageLogFile: query.RusageLogFile,
SkipUnusedStages: types.NewOptionalBool(query.SkipUnusedStages),
Squash: query.Squash,
SystemContext: systemContext,
Target: query.Target,
UnsetEnvs: query.UnsetEnvs,
}
for _, platformSpec := range query.Platform {
os, arch, variant, err := parse.Platform(platformSpec)
if err != nil {
utils.BadRequest(w, "platform", platformSpec, err)
return
}
buildOptions.Platforms = append(buildOptions.Platforms, struct{ OS, Arch, Variant string }{
OS: os,
Arch: arch,
Variant: variant,
})
}
if _, found := r.URL.Query()["timestamp"]; found {
ts := time.Unix(query.Timestamp, 0)
buildOptions.Timestamp = &ts
}
var (
imageID string
success bool
)
runCtx, cancel := context.WithCancel(context.Background())
go func() {
defer cancel()
imageID, _, err = runtime.Build(r.Context(), buildOptions, containerFiles...)
if err == nil {
success = true
} else {
stderr.Write([]byte(err.Error() + "\n"))
}
}()
flush := func() {
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
}
// Send headers and prime client for stream to come
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
flush()
body := w.(io.Writer)
if logrus.IsLevelEnabled(logrus.DebugLevel) {
if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found {
if keep, _ := strconv.ParseBool(v); keep {
t, _ := ioutil.TempFile("", "build_*_server")
defer t.Close()
body = io.MultiWriter(t, w)
}
}
}
enc := json.NewEncoder(body)
enc.SetEscapeHTML(true)
var stepErrors []string
for {
type BuildResponse struct {
Stream string `json:"stream,omitempty"`
Error *jsonmessage.JSONError `json:"errorDetail,omitempty"`
// NOTE: `error` is being deprecated check https://github.com/moby/moby/blob/master/pkg/jsonmessage/jsonmessage.go#L148
ErrorMessage string `json:"error,omitempty"` // deprecate this slowly
Aux json.RawMessage `json:"aux,omitempty"`
}
m := BuildResponse{}
select {
case e := <-stdout.Chan():
m.Stream = string(e)
if err := enc.Encode(m); err != nil {
stderr.Write([]byte(err.Error()))
}
flush()
case e := <-reporter.Chan():
m.Stream = string(e)
if err := enc.Encode(m); err != nil {
stderr.Write([]byte(err.Error()))
}
flush()
case e := <-auxout.Chan():
if !query.Quiet {
m.Stream = string(e)
if err := enc.Encode(m); err != nil {
stderr.Write([]byte(err.Error()))
}
flush()
} else {
stepErrors = append(stepErrors, string(e))
}
case e := <-stderr.Chan():
// Docker-API Compat parity : Build failed so
// output all step errors irrespective of quiet
// flag.
for _, stepError := range stepErrors {
t := BuildResponse{}
t.Stream = stepError
if err := enc.Encode(t); err != nil {
stderr.Write([]byte(err.Error()))
}
flush()
}
m.ErrorMessage = string(e)
m.Error = &jsonmessage.JSONError{
Message: m.ErrorMessage,
}
if err := enc.Encode(m); err != nil {
logrus.Warnf("Failed to json encode error %v", err)
}
flush()
return
case <-runCtx.Done():
if success {
if !utils.IsLibpodRequest(r) && !query.Quiet {
m.Aux = []byte(fmt.Sprintf(`{"ID":"sha256:%s"}`, imageID))
if err := enc.Encode(m); err != nil {
logrus.Warnf("failed to json encode error %v", err)
}
m.Aux = nil
m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID)
if err := enc.Encode(m); err != nil {
logrus.Warnf("Failed to json encode error %v", err)
}
flush()
for _, tag := range tags {
m.Stream = fmt.Sprintf("Successfully tagged %s\n", tag)
if err := enc.Encode(m); err != nil {
logrus.Warnf("Failed to json encode error %v", err)
}
flush()
}
}
}
flush()
return
case <-r.Context().Done():
cancel()
logrus.Infof("Client disconnect reported for build %q / %q.", registry, query.Dockerfile)
return
}
}
}
func parseNetworkConfigurationPolicy(network string) buildah.NetworkConfigurationPolicy {
if val, err := strconv.Atoi(network); err == nil {
return buildah.NetworkConfigurationPolicy(val)
}
switch network {
case "NetworkDefault":
return buildah.NetworkDefault
case "NetworkDisabled":
return buildah.NetworkDisabled
case "NetworkEnabled":
return buildah.NetworkEnabled
default:
return buildah.NetworkDefault
}
}
func parseLibPodIsolation(isolation string) (buildah.Isolation, error) {
if val, err := strconv.Atoi(isolation); err == nil {
return buildah.Isolation(val), nil
}
return parse.IsolationOption(isolation)
}
func extractTarFile(r *http.Request) (string, error) {
// build a home for the request body
anchorDir, err := ioutil.TempDir("", "libpod_builder")
if err != nil {
return "", err
}
path := filepath.Join(anchorDir, "tarBall")
tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return "", err
}
defer tarBall.Close()
// Content-Length not used as too many existing API clients didn't honor it
_, err = io.Copy(tarBall, r.Body)
r.Body.Close()
if err != nil {
return "", fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI)
}
buildDir := filepath.Join(anchorDir, "build")
err = os.Mkdir(buildDir, 0o700)
if err != nil {
return "", err
}
_, _ = tarBall.Seek(0, 0)
err = archive.Untar(tarBall, buildDir, nil)
return buildDir, err
}