mirror of
				https://github.com/containers/podman.git
				synced 2025-11-01 02:42:11 +08:00 
			
		
		
		
	 0154f9e9b3
			
		
	
	0154f9e9b3
	
	
	
		
			
			Currently the --layers flag set by the user is ignored, and only the BUILDAH_LAYERS environment variable being set is observed. Fixes: https://github.com/containers/podman/issues/8643 Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
		
			
				
	
	
		
			317 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			317 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package compat
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/containers/buildah"
 | |
| 	"github.com/containers/buildah/imagebuildah"
 | |
| 	"github.com/containers/image/v5/types"
 | |
| 	"github.com/containers/podman/v2/libpod"
 | |
| 	"github.com/containers/podman/v2/pkg/api/handlers/utils"
 | |
| 	"github.com/containers/podman/v2/pkg/auth"
 | |
| 	"github.com/containers/podman/v2/pkg/channel"
 | |
| 	"github.com/containers/storage/pkg/archive"
 | |
| 	"github.com/gorilla/schema"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"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.Warnf("tar file content type is  %s, should use \"application/x-tar\" content type", contentType)
 | |
| 		case "application/x-tar":
 | |
| 			break
 | |
| 		default:
 | |
| 			utils.BadRequest(w, "Content-Type", hdr[0],
 | |
| 				fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0]))
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	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(errors.Wrapf(err, "failed to remove build scratch directory %q", filepath.Dir(contextDirectory)))
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	query := struct {
 | |
| 		BuildArgs   string   `schema:"buildargs"`
 | |
| 		CacheFrom   string   `schema:"cachefrom"`
 | |
| 		CpuPeriod   uint64   `schema:"cpuperiod"`  // nolint
 | |
| 		CpuQuota    int64    `schema:"cpuquota"`   // nolint
 | |
| 		CpuSetCpus  string   `schema:"cpusetcpus"` // nolint
 | |
| 		CpuShares   uint64   `schema:"cpushares"`  // nolint
 | |
| 		Dockerfile  string   `schema:"dockerfile"`
 | |
| 		ExtraHosts  string   `schema:"extrahosts"`
 | |
| 		ForceRm     bool     `schema:"forcerm"`
 | |
| 		HTTPProxy   bool     `schema:"httpproxy"`
 | |
| 		Labels      string   `schema:"labels"`
 | |
| 		Layers      bool     `schema:"layers"`
 | |
| 		MemSwap     int64    `schema:"memswap"`
 | |
| 		Memory      int64    `schema:"memory"`
 | |
| 		NetworkMode string   `schema:"networkmode"`
 | |
| 		NoCache     bool     `schema:"nocache"`
 | |
| 		Outputs     string   `schema:"outputs"`
 | |
| 		Platform    string   `schema:"platform"`
 | |
| 		Pull        bool     `schema:"pull"`
 | |
| 		Quiet       bool     `schema:"q"`
 | |
| 		Registry    string   `schema:"registry"`
 | |
| 		Remote      string   `schema:"remote"`
 | |
| 		Rm          bool     `schema:"rm"`
 | |
| 		ShmSize     int      `schema:"shmsize"`
 | |
| 		Squash      bool     `schema:"squash"`
 | |
| 		Tag         []string `schema:"t"`
 | |
| 		Target      string   `schema:"target"`
 | |
| 	}{
 | |
| 		Dockerfile: "Dockerfile",
 | |
| 		Registry:   "docker.io",
 | |
| 		Rm:         true,
 | |
| 		ShmSize:    64 * 1024 * 1024,
 | |
| 		Tag:        []string{},
 | |
| 	}
 | |
| 
 | |
| 	decoder := r.Context().Value("decoder").(*schema.Decoder)
 | |
| 	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
 | |
| 		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var output string
 | |
| 	if len(query.Tag) > 0 {
 | |
| 		output = query.Tag[0]
 | |
| 	}
 | |
| 
 | |
| 	var additionalNames []string
 | |
| 	if len(query.Tag) > 1 {
 | |
| 		additionalNames = query.Tag[1:]
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// convert label formats
 | |
| 	var labels = []string{}
 | |
| 	if _, found := r.URL.Query()["labels"]; found {
 | |
| 		var m = map[string]string{}
 | |
| 		if err := json.Unmarshal([]byte(query.Labels), &m); err != nil {
 | |
| 			utils.BadRequest(w, "labels", query.Labels, err)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		for k, v := range m {
 | |
| 			labels = append(labels, k+"="+v)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	pullPolicy := buildah.PullIfMissing
 | |
| 	if _, found := r.URL.Query()["pull"]; found {
 | |
| 		if query.Pull {
 | |
| 			pullPolicy = buildah.PullAlways
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	creds, authfile, key, err := auth.GetCredentials(r)
 | |
| 	if err != nil {
 | |
| 		// Credential value(s) not returned as their value is not human readable
 | |
| 		utils.BadRequest(w, key.String(), "n/a", err)
 | |
| 		return
 | |
| 	}
 | |
| 	defer auth.RemoveAuthfile(authfile)
 | |
| 
 | |
| 	// Channels all mux'ed in select{} below to follow API build protocol
 | |
| 	stdout := channel.NewWriter(make(chan []byte, 1))
 | |
| 	defer stdout.Close()
 | |
| 
 | |
| 	auxout := channel.NewWriter(make(chan []byte, 1))
 | |
| 	defer auxout.Close()
 | |
| 
 | |
| 	stderr := channel.NewWriter(make(chan []byte, 1))
 | |
| 	defer stderr.Close()
 | |
| 
 | |
| 	reporter := channel.NewWriter(make(chan []byte, 1))
 | |
| 	defer reporter.Close()
 | |
| 	buildOptions := imagebuildah.BuildOptions{
 | |
| 		ContextDirectory:               contextDirectory,
 | |
| 		PullPolicy:                     pullPolicy,
 | |
| 		Registry:                       query.Registry,
 | |
| 		IgnoreUnrecognizedInstructions: true,
 | |
| 		Quiet:                          query.Quiet,
 | |
| 		Layers:                         query.Layers,
 | |
| 		Isolation:                      buildah.IsolationChroot,
 | |
| 		Compression:                    archive.Gzip,
 | |
| 		Args:                           buildArgs,
 | |
| 		Output:                         output,
 | |
| 		AdditionalTags:                 additionalNames,
 | |
| 		Out:                            stdout,
 | |
| 		Err:                            auxout,
 | |
| 		ReportWriter:                   reporter,
 | |
| 		OutputFormat:                   buildah.Dockerv2ImageManifest,
 | |
| 		SystemContext: &types.SystemContext{
 | |
| 			AuthFilePath:     authfile,
 | |
| 			DockerAuthConfig: creds,
 | |
| 		},
 | |
| 		CommonBuildOpts: &buildah.CommonBuildOptions{
 | |
| 			CPUPeriod:  query.CpuPeriod,
 | |
| 			CPUQuota:   query.CpuQuota,
 | |
| 			CPUShares:  query.CpuShares,
 | |
| 			CPUSetCPUs: query.CpuSetCpus,
 | |
| 			HTTPProxy:  query.HTTPProxy,
 | |
| 			Memory:     query.Memory,
 | |
| 			MemorySwap: query.MemSwap,
 | |
| 			ShmSize:    strconv.Itoa(query.ShmSize),
 | |
| 		},
 | |
| 		Squash:                  query.Squash,
 | |
| 		Labels:                  labels,
 | |
| 		NoCache:                 query.NoCache,
 | |
| 		RemoveIntermediateCtrs:  query.Rm,
 | |
| 		ForceRmIntermediateCtrs: query.ForceRm,
 | |
| 		Target:                  query.Target,
 | |
| 	}
 | |
| 
 | |
| 	runtime := r.Context().Value("runtime").(*libpod.Runtime)
 | |
| 	runCtx, cancel := context.WithCancel(context.Background())
 | |
| 	var imageID string
 | |
| 	go func() {
 | |
| 		defer cancel()
 | |
| 		imageID, _, err = runtime.Build(r.Context(), buildOptions, query.Dockerfile)
 | |
| 		if err != nil {
 | |
| 			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.WriteHeader(http.StatusOK)
 | |
| 	w.Header().Add("Content-Type", "application/json")
 | |
| 	flush()
 | |
| 
 | |
| 	var failed bool
 | |
| 
 | |
| 	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)
 | |
| loop:
 | |
| 	for {
 | |
| 		m := struct {
 | |
| 			Stream string `json:"stream,omitempty"`
 | |
| 			Error  string `json:"error,omitempty"`
 | |
| 		}{}
 | |
| 
 | |
| 		select {
 | |
| 		case e := <-stdout.Chan():
 | |
| 			m.Stream = string(e)
 | |
| 			if err := enc.Encode(m); err != nil {
 | |
| 				stderr.Write([]byte(err.Error()))
 | |
| 			}
 | |
| 			flush()
 | |
| 		case e := <-auxout.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 := <-stderr.Chan():
 | |
| 			failed = true
 | |
| 			m.Error = string(e)
 | |
| 			if err := enc.Encode(m); err != nil {
 | |
| 				logrus.Warnf("Failed to json encode error %v", err)
 | |
| 			}
 | |
| 			flush()
 | |
| 		case <-runCtx.Done():
 | |
| 			if !failed {
 | |
| 				if !utils.IsLibpodRequest(r) {
 | |
| 					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()
 | |
| 				}
 | |
| 			}
 | |
| 			break loop
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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, 0600)
 | |
| 	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, 0700)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	_, _ = tarBall.Seek(0, 0)
 | |
| 	err = archive.Untar(tarBall, buildDir, nil)
 | |
| 	return buildDir, err
 | |
| }
 |