github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/api/handlers/compat/images_build.go (about)

     1  package compat
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/containers/buildah"
    17  	"github.com/containers/buildah/imagebuildah"
    18  	"github.com/containers/libpod/libpod"
    19  	"github.com/containers/libpod/pkg/api/handlers"
    20  	"github.com/containers/libpod/pkg/api/handlers/utils"
    21  	"github.com/containers/storage/pkg/archive"
    22  	"github.com/gorilla/schema"
    23  )
    24  
    25  func BuildImage(w http.ResponseWriter, r *http.Request) {
    26  	authConfigs := map[string]handlers.AuthConfig{}
    27  	if hdr, found := r.Header["X-Registry-Config"]; found && len(hdr) > 0 {
    28  		authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(hdr[0]))
    29  		if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil {
    30  			utils.BadRequest(w, "X-Registry-Config", hdr[0], json.NewDecoder(authConfigsJSON).Decode(&authConfigs))
    31  			return
    32  		}
    33  	}
    34  
    35  	if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 {
    36  		if hdr[0] != "application/x-tar" {
    37  			utils.BadRequest(w, "Content-Type", hdr[0],
    38  				fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0]))
    39  		}
    40  	}
    41  
    42  	anchorDir, err := extractTarFile(r, w)
    43  	if err != nil {
    44  		utils.InternalServerError(w, err)
    45  		return
    46  	}
    47  	defer os.RemoveAll(anchorDir)
    48  
    49  	query := struct {
    50  		Dockerfile  string `schema:"dockerfile"`
    51  		Tag         string `schema:"t"`
    52  		ExtraHosts  string `schema:"extrahosts"`
    53  		Remote      string `schema:"remote"`
    54  		Quiet       bool   `schema:"q"`
    55  		NoCache     bool   `schema:"nocache"`
    56  		CacheFrom   string `schema:"cachefrom"`
    57  		Pull        bool   `schema:"pull"`
    58  		Rm          bool   `schema:"rm"`
    59  		ForceRm     bool   `schema:"forcerm"`
    60  		Memory      int64  `schema:"memory"`
    61  		MemSwap     int64  `schema:"memswap"`
    62  		CpuShares   uint64 `schema:"cpushares"`
    63  		CpuSetCpus  string `schema:"cpusetcpus"`
    64  		CpuPeriod   uint64 `schema:"cpuperiod"`
    65  		CpuQuota    int64  `schema:"cpuquota"`
    66  		BuildArgs   string `schema:"buildargs"`
    67  		ShmSize     int    `schema:"shmsize"`
    68  		Squash      bool   `schema:"squash"`
    69  		Labels      string `schema:"labels"`
    70  		NetworkMode string `schema:"networkmode"`
    71  		Platform    string `schema:"platform"`
    72  		Target      string `schema:"target"`
    73  		Outputs     string `schema:"outputs"`
    74  		Registry    string `schema:"registry"`
    75  	}{
    76  		Dockerfile:  "Dockerfile",
    77  		Tag:         "",
    78  		ExtraHosts:  "",
    79  		Remote:      "",
    80  		Quiet:       false,
    81  		NoCache:     false,
    82  		CacheFrom:   "",
    83  		Pull:        false,
    84  		Rm:          true,
    85  		ForceRm:     false,
    86  		Memory:      0,
    87  		MemSwap:     0,
    88  		CpuShares:   0,
    89  		CpuSetCpus:  "",
    90  		CpuPeriod:   0,
    91  		CpuQuota:    0,
    92  		BuildArgs:   "",
    93  		ShmSize:     64 * 1024 * 1024,
    94  		Squash:      false,
    95  		Labels:      "",
    96  		NetworkMode: "",
    97  		Platform:    "",
    98  		Target:      "",
    99  		Outputs:     "",
   100  		Registry:    "docker.io",
   101  	}
   102  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   103  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   104  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
   105  		return
   106  	}
   107  
   108  	var (
   109  		// Tag is the name with optional tag...
   110  		name = query.Tag
   111  		tag  = "latest"
   112  	)
   113  	if strings.Contains(query.Tag, ":") {
   114  		tokens := strings.SplitN(query.Tag, ":", 2)
   115  		name = tokens[0]
   116  		tag = tokens[1]
   117  	}
   118  
   119  	if _, found := r.URL.Query()["target"]; found {
   120  		name = query.Target
   121  	}
   122  
   123  	var buildArgs = map[string]string{}
   124  	if _, found := r.URL.Query()["buildargs"]; found {
   125  		if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil {
   126  			utils.BadRequest(w, "buildargs", query.BuildArgs, err)
   127  			return
   128  		}
   129  	}
   130  
   131  	// convert label formats
   132  	var labels = []string{}
   133  	if _, found := r.URL.Query()["labels"]; found {
   134  		var m = map[string]string{}
   135  		if err := json.Unmarshal([]byte(query.Labels), &m); err != nil {
   136  			utils.BadRequest(w, "labels", query.Labels, err)
   137  			return
   138  		}
   139  
   140  		for k, v := range m {
   141  			labels = append(labels, k+"="+v)
   142  		}
   143  	}
   144  
   145  	pullPolicy := buildah.PullIfMissing
   146  	if _, found := r.URL.Query()["pull"]; found {
   147  		if query.Pull {
   148  			pullPolicy = buildah.PullAlways
   149  		}
   150  	}
   151  
   152  	// build events will be recorded here
   153  	var (
   154  		buildEvents = []string{}
   155  		progress    = bytes.Buffer{}
   156  	)
   157  
   158  	buildOptions := imagebuildah.BuildOptions{
   159  		ContextDirectory:               filepath.Join(anchorDir, "build"),
   160  		PullPolicy:                     pullPolicy,
   161  		Registry:                       query.Registry,
   162  		IgnoreUnrecognizedInstructions: true,
   163  		Quiet:                          query.Quiet,
   164  		Isolation:                      buildah.IsolationChroot,
   165  		Runtime:                        "",
   166  		RuntimeArgs:                    nil,
   167  		TransientMounts:                nil,
   168  		Compression:                    archive.Gzip,
   169  		Args:                           buildArgs,
   170  		Output:                         name,
   171  		AdditionalTags:                 []string{tag},
   172  		Log: func(format string, args ...interface{}) {
   173  			buildEvents = append(buildEvents, fmt.Sprintf(format, args...))
   174  		},
   175  		In:                  nil,
   176  		Out:                 &progress,
   177  		Err:                 &progress,
   178  		SignaturePolicyPath: "",
   179  		ReportWriter:        &progress,
   180  		OutputFormat:        buildah.Dockerv2ImageManifest,
   181  		SystemContext:       nil,
   182  		NamespaceOptions:    nil,
   183  		ConfigureNetwork:    0,
   184  		CNIPluginPath:       "",
   185  		CNIConfigDir:        "",
   186  		IDMappingOptions:    nil,
   187  		AddCapabilities:     nil,
   188  		DropCapabilities:    nil,
   189  		CommonBuildOpts: &buildah.CommonBuildOptions{
   190  			AddHost:            nil,
   191  			CgroupParent:       "",
   192  			CPUPeriod:          query.CpuPeriod,
   193  			CPUQuota:           query.CpuQuota,
   194  			CPUShares:          query.CpuShares,
   195  			CPUSetCPUs:         query.CpuSetCpus,
   196  			CPUSetMems:         "",
   197  			HTTPProxy:          false,
   198  			Memory:             query.Memory,
   199  			DNSSearch:          nil,
   200  			DNSServers:         nil,
   201  			DNSOptions:         nil,
   202  			MemorySwap:         query.MemSwap,
   203  			LabelOpts:          nil,
   204  			SeccompProfilePath: "",
   205  			ApparmorProfile:    "",
   206  			ShmSize:            strconv.Itoa(query.ShmSize),
   207  			Ulimit:             nil,
   208  			Volumes:            nil,
   209  		},
   210  		DefaultMountsFilePath:   "",
   211  		IIDFile:                 "",
   212  		Squash:                  query.Squash,
   213  		Labels:                  labels,
   214  		Annotations:             nil,
   215  		OnBuild:                 nil,
   216  		Layers:                  false,
   217  		NoCache:                 query.NoCache,
   218  		RemoveIntermediateCtrs:  query.Rm,
   219  		ForceRmIntermediateCtrs: query.ForceRm,
   220  		BlobDirectory:           "",
   221  		Target:                  query.Target,
   222  		Devices:                 nil,
   223  	}
   224  
   225  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   226  	id, _, err := runtime.Build(r.Context(), buildOptions, query.Dockerfile)
   227  	if err != nil {
   228  		utils.InternalServerError(w, err)
   229  	}
   230  
   231  	// Find image ID that was built...
   232  	utils.WriteResponse(w, http.StatusOK,
   233  		struct {
   234  			Stream string `json:"stream"`
   235  		}{
   236  			Stream: progress.String() + "\n" +
   237  				strings.Join(buildEvents, "\n") +
   238  				fmt.Sprintf("\nSuccessfully built %s\n", id),
   239  		})
   240  }
   241  
   242  func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) {
   243  	// build a home for the request body
   244  	anchorDir, err := ioutil.TempDir("", "libpod_builder")
   245  	if err != nil {
   246  		return "", err
   247  	}
   248  	buildDir := filepath.Join(anchorDir, "build")
   249  
   250  	path := filepath.Join(anchorDir, "tarBall")
   251  	tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   252  	if err != nil {
   253  		return "", err
   254  	}
   255  	defer tarBall.Close()
   256  
   257  	// Content-Length not used as too many existing API clients didn't honor it
   258  	_, err = io.Copy(tarBall, r.Body)
   259  	r.Body.Close()
   260  
   261  	if err != nil {
   262  		utils.InternalServerError(w,
   263  			fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI))
   264  	}
   265  
   266  	_, _ = tarBall.Seek(0, 0)
   267  	if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil {
   268  		return "", err
   269  	}
   270  	return anchorDir, nil
   271  }