github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/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 14 "github.com/containers/buildah" 15 "github.com/containers/buildah/imagebuildah" 16 "github.com/containers/image/v5/types" 17 "github.com/containers/podman/v2/libpod" 18 "github.com/containers/podman/v2/pkg/api/handlers/utils" 19 "github.com/containers/podman/v2/pkg/auth" 20 "github.com/containers/podman/v2/pkg/channel" 21 "github.com/containers/storage/pkg/archive" 22 "github.com/gorilla/schema" 23 "github.com/pkg/errors" 24 "github.com/sirupsen/logrus" 25 ) 26 27 func BuildImage(w http.ResponseWriter, r *http.Request) { 28 if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { 29 contentType := hdr[0] 30 switch contentType { 31 case "application/tar": 32 logrus.Warnf("tar file content type is %s, should use \"application/x-tar\" content type", contentType) 33 case "application/x-tar": 34 break 35 default: 36 utils.BadRequest(w, "Content-Type", hdr[0], 37 fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) 38 return 39 } 40 } 41 42 contextDirectory, err := extractTarFile(r) 43 if err != nil { 44 utils.InternalServerError(w, err) 45 return 46 } 47 48 defer func() { 49 if logrus.IsLevelEnabled(logrus.DebugLevel) { 50 if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found { 51 if keep, _ := strconv.ParseBool(v); keep { 52 return 53 } 54 } 55 } 56 err := os.RemoveAll(filepath.Dir(contextDirectory)) 57 if err != nil { 58 logrus.Warn(errors.Wrapf(err, "failed to remove build scratch directory %q", filepath.Dir(contextDirectory))) 59 } 60 }() 61 62 query := struct { 63 BuildArgs string `schema:"buildargs"` 64 CacheFrom string `schema:"cachefrom"` 65 CpuPeriod uint64 `schema:"cpuperiod"` // nolint 66 CpuQuota int64 `schema:"cpuquota"` // nolint 67 CpuSetCpus string `schema:"cpusetcpus"` // nolint 68 CpuShares uint64 `schema:"cpushares"` // nolint 69 Dockerfile string `schema:"dockerfile"` 70 ExtraHosts string `schema:"extrahosts"` 71 ForceRm bool `schema:"forcerm"` 72 HTTPProxy bool `schema:"httpproxy"` 73 Labels string `schema:"labels"` 74 MemSwap int64 `schema:"memswap"` 75 Memory int64 `schema:"memory"` 76 NetworkMode string `schema:"networkmode"` 77 NoCache bool `schema:"nocache"` 78 Outputs string `schema:"outputs"` 79 Platform string `schema:"platform"` 80 Pull bool `schema:"pull"` 81 Quiet bool `schema:"q"` 82 Registry string `schema:"registry"` 83 Remote string `schema:"remote"` 84 Rm bool `schema:"rm"` 85 ShmSize int `schema:"shmsize"` 86 Squash bool `schema:"squash"` 87 Tag []string `schema:"t"` 88 Target string `schema:"target"` 89 }{ 90 Dockerfile: "Dockerfile", 91 Registry: "docker.io", 92 Rm: true, 93 ShmSize: 64 * 1024 * 1024, 94 Tag: []string{}, 95 } 96 97 decoder := r.Context().Value("decoder").(*schema.Decoder) 98 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 99 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) 100 return 101 } 102 103 var output string 104 if len(query.Tag) > 0 { 105 output = query.Tag[0] 106 } 107 108 var additionalNames []string 109 if len(query.Tag) > 1 { 110 additionalNames = query.Tag[1:] 111 } 112 113 var buildArgs = map[string]string{} 114 if _, found := r.URL.Query()["buildargs"]; found { 115 if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { 116 utils.BadRequest(w, "buildargs", query.BuildArgs, err) 117 return 118 } 119 } 120 121 // convert label formats 122 var labels = []string{} 123 if _, found := r.URL.Query()["labels"]; found { 124 var m = map[string]string{} 125 if err := json.Unmarshal([]byte(query.Labels), &m); err != nil { 126 utils.BadRequest(w, "labels", query.Labels, err) 127 return 128 } 129 130 for k, v := range m { 131 labels = append(labels, k+"="+v) 132 } 133 } 134 135 pullPolicy := buildah.PullIfMissing 136 if _, found := r.URL.Query()["pull"]; found { 137 if query.Pull { 138 pullPolicy = buildah.PullAlways 139 } 140 } 141 142 creds, authfile, key, err := auth.GetCredentials(r) 143 if err != nil { 144 // Credential value(s) not returned as their value is not human readable 145 utils.BadRequest(w, key.String(), "n/a", err) 146 return 147 } 148 defer auth.RemoveAuthfile(authfile) 149 150 // Channels all mux'ed in select{} below to follow API build protocol 151 stdout := channel.NewWriter(make(chan []byte, 1)) 152 defer stdout.Close() 153 154 auxout := channel.NewWriter(make(chan []byte, 1)) 155 defer auxout.Close() 156 157 stderr := channel.NewWriter(make(chan []byte, 1)) 158 defer stderr.Close() 159 160 reporter := channel.NewWriter(make(chan []byte, 1)) 161 defer reporter.Close() 162 buildOptions := imagebuildah.BuildOptions{ 163 ContextDirectory: contextDirectory, 164 PullPolicy: pullPolicy, 165 Registry: query.Registry, 166 IgnoreUnrecognizedInstructions: true, 167 Quiet: query.Quiet, 168 Isolation: buildah.IsolationChroot, 169 Compression: archive.Gzip, 170 Args: buildArgs, 171 Output: output, 172 AdditionalTags: additionalNames, 173 Out: stdout, 174 Err: auxout, 175 ReportWriter: reporter, 176 OutputFormat: buildah.Dockerv2ImageManifest, 177 SystemContext: &types.SystemContext{ 178 AuthFilePath: authfile, 179 DockerAuthConfig: creds, 180 }, 181 CommonBuildOpts: &buildah.CommonBuildOptions{ 182 CPUPeriod: query.CpuPeriod, 183 CPUQuota: query.CpuQuota, 184 CPUShares: query.CpuShares, 185 CPUSetCPUs: query.CpuSetCpus, 186 HTTPProxy: query.HTTPProxy, 187 Memory: query.Memory, 188 MemorySwap: query.MemSwap, 189 ShmSize: strconv.Itoa(query.ShmSize), 190 }, 191 Squash: query.Squash, 192 Labels: labels, 193 NoCache: query.NoCache, 194 RemoveIntermediateCtrs: query.Rm, 195 ForceRmIntermediateCtrs: query.ForceRm, 196 Target: query.Target, 197 } 198 199 runtime := r.Context().Value("runtime").(*libpod.Runtime) 200 runCtx, cancel := context.WithCancel(context.Background()) 201 var imageID string 202 go func() { 203 defer cancel() 204 imageID, _, err = runtime.Build(r.Context(), buildOptions, query.Dockerfile) 205 if err != nil { 206 stderr.Write([]byte(err.Error() + "\n")) 207 } 208 }() 209 210 flush := func() { 211 if flusher, ok := w.(http.Flusher); ok { 212 flusher.Flush() 213 } 214 } 215 216 // Send headers and prime client for stream to come 217 w.WriteHeader(http.StatusOK) 218 w.Header().Add("Content-Type", "application/json") 219 flush() 220 221 var failed bool 222 223 body := w.(io.Writer) 224 if logrus.IsLevelEnabled(logrus.DebugLevel) { 225 if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found { 226 if keep, _ := strconv.ParseBool(v); keep { 227 t, _ := ioutil.TempFile("", "build_*_server") 228 defer t.Close() 229 body = io.MultiWriter(t, w) 230 } 231 } 232 } 233 234 enc := json.NewEncoder(body) 235 enc.SetEscapeHTML(true) 236 loop: 237 for { 238 m := struct { 239 Stream string `json:"stream,omitempty"` 240 Error string `json:"error,omitempty"` 241 }{} 242 243 select { 244 case e := <-stdout.Chan(): 245 m.Stream = string(e) 246 if err := enc.Encode(m); err != nil { 247 stderr.Write([]byte(err.Error())) 248 } 249 flush() 250 case e := <-auxout.Chan(): 251 m.Stream = string(e) 252 if err := enc.Encode(m); err != nil { 253 stderr.Write([]byte(err.Error())) 254 } 255 flush() 256 case e := <-reporter.Chan(): 257 m.Stream = string(e) 258 if err := enc.Encode(m); err != nil { 259 stderr.Write([]byte(err.Error())) 260 } 261 flush() 262 case e := <-stderr.Chan(): 263 failed = true 264 m.Error = string(e) 265 if err := enc.Encode(m); err != nil { 266 logrus.Warnf("Failed to json encode error %q", err.Error()) 267 } 268 flush() 269 case <-runCtx.Done(): 270 if !failed { 271 if !utils.IsLibpodRequest(r) { 272 m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID) 273 if err := enc.Encode(m); err != nil { 274 logrus.Warnf("Failed to json encode error %q", err.Error()) 275 } 276 flush() 277 } 278 } 279 break loop 280 } 281 } 282 } 283 284 func extractTarFile(r *http.Request) (string, error) { 285 // build a home for the request body 286 anchorDir, err := ioutil.TempDir("", "libpod_builder") 287 if err != nil { 288 return "", err 289 } 290 291 path := filepath.Join(anchorDir, "tarBall") 292 tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) 293 if err != nil { 294 return "", err 295 } 296 defer tarBall.Close() 297 298 // Content-Length not used as too many existing API clients didn't honor it 299 _, err = io.Copy(tarBall, r.Body) 300 r.Body.Close() 301 if err != nil { 302 return "", fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI) 303 } 304 305 buildDir := filepath.Join(anchorDir, "build") 306 err = os.Mkdir(buildDir, 0700) 307 if err != nil { 308 return "", err 309 } 310 311 _, _ = tarBall.Seek(0, 0) 312 err = archive.Untar(tarBall, buildDir, nil) 313 return buildDir, err 314 }