mirror of
				https://github.com/containers/podman.git
				synced 2025-11-01 02:42:11 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			914 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			914 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package compat
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"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"`
 | |
| 		IDMappingOptions        string   `schema:"idmappingoptions"`
 | |
| 		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 := os.MkdirTemp(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("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("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
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var idMappingOptions buildahDefine.IDMappingOptions
 | |
| 	if _, found := r.URL.Query()["idmappingoptions"]; found {
 | |
| 		if err := json.Unmarshal([]byte(query.IDMappingOptions), &idMappingOptions); err != nil {
 | |
| 			utils.BadRequest(w, "idmappingoptions", query.IDMappingOptions, err)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 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 ignore 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("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,
 | |
| 		IDMappingOptions:               &idMappingOptions,
 | |
| 		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(r.Context())
 | |
| 	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, _ := os.CreateTemp("", "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)
 | |
| 					}
 | |
| 					flush()
 | |
| 					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 := os.MkdirTemp("", "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
 | |
| }
 | 
