github.com/rumpl/bof@v23.0.0-rc.2+incompatible/api/server/router/build/build_routes.go (about) 1 package build // import "github.com/docker/docker/api/server/router/build" 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "encoding/base64" 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 "runtime" 13 "strconv" 14 "strings" 15 "sync" 16 17 "github.com/docker/docker/api/server/httputils" 18 "github.com/docker/docker/api/types" 19 "github.com/docker/docker/api/types/backend" 20 "github.com/docker/docker/api/types/container" 21 "github.com/docker/docker/api/types/filters" 22 "github.com/docker/docker/api/types/versions" 23 "github.com/docker/docker/errdefs" 24 "github.com/docker/docker/pkg/ioutils" 25 "github.com/docker/docker/pkg/progress" 26 "github.com/docker/docker/pkg/streamformatter" 27 units "github.com/docker/go-units" 28 "github.com/pkg/errors" 29 "github.com/sirupsen/logrus" 30 ) 31 32 type invalidIsolationError string 33 34 func (e invalidIsolationError) Error() string { 35 return fmt.Sprintf("Unsupported isolation: %q", string(e)) 36 } 37 38 func (e invalidIsolationError) InvalidParameter() {} 39 40 func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) { 41 options := &types.ImageBuildOptions{ 42 Version: types.BuilderV1, // Builder V1 is the default, but can be overridden 43 Dockerfile: r.FormValue("dockerfile"), 44 SuppressOutput: httputils.BoolValue(r, "q"), 45 NoCache: httputils.BoolValue(r, "nocache"), 46 ForceRemove: httputils.BoolValue(r, "forcerm"), 47 MemorySwap: httputils.Int64ValueOrZero(r, "memswap"), 48 Memory: httputils.Int64ValueOrZero(r, "memory"), 49 CPUShares: httputils.Int64ValueOrZero(r, "cpushares"), 50 CPUPeriod: httputils.Int64ValueOrZero(r, "cpuperiod"), 51 CPUQuota: httputils.Int64ValueOrZero(r, "cpuquota"), 52 CPUSetCPUs: r.FormValue("cpusetcpus"), 53 CPUSetMems: r.FormValue("cpusetmems"), 54 CgroupParent: r.FormValue("cgroupparent"), 55 NetworkMode: r.FormValue("networkmode"), 56 Tags: r.Form["t"], 57 ExtraHosts: r.Form["extrahosts"], 58 SecurityOpt: r.Form["securityopt"], 59 Squash: httputils.BoolValue(r, "squash"), 60 Target: r.FormValue("target"), 61 RemoteContext: r.FormValue("remote"), 62 SessionID: r.FormValue("session"), 63 BuildID: r.FormValue("buildid"), 64 } 65 66 if runtime.GOOS != "windows" && options.SecurityOpt != nil { 67 return nil, errdefs.InvalidParameter(errors.New("The daemon on this platform does not support setting security options on build")) 68 } 69 70 version := httputils.VersionFromContext(ctx) 71 if httputils.BoolValue(r, "forcerm") && versions.GreaterThanOrEqualTo(version, "1.12") { 72 options.Remove = true 73 } else if r.FormValue("rm") == "" && versions.GreaterThanOrEqualTo(version, "1.12") { 74 options.Remove = true 75 } else { 76 options.Remove = httputils.BoolValue(r, "rm") 77 } 78 if httputils.BoolValue(r, "pull") && versions.GreaterThanOrEqualTo(version, "1.16") { 79 options.PullParent = true 80 } 81 if versions.GreaterThanOrEqualTo(version, "1.32") { 82 options.Platform = r.FormValue("platform") 83 } 84 if versions.GreaterThanOrEqualTo(version, "1.40") { 85 outputsJSON := r.FormValue("outputs") 86 if outputsJSON != "" { 87 var outputs []types.ImageBuildOutput 88 if err := json.Unmarshal([]byte(outputsJSON), &outputs); err != nil { 89 return nil, err 90 } 91 options.Outputs = outputs 92 } 93 } 94 95 if s := r.Form.Get("shmsize"); s != "" { 96 shmSize, err := strconv.ParseInt(s, 10, 64) 97 if err != nil { 98 return nil, err 99 } 100 options.ShmSize = shmSize 101 } 102 103 if i := r.FormValue("isolation"); i != "" { 104 options.Isolation = container.Isolation(i) 105 if !options.Isolation.IsValid() { 106 return nil, invalidIsolationError(options.Isolation) 107 } 108 } 109 110 if ulimitsJSON := r.FormValue("ulimits"); ulimitsJSON != "" { 111 var buildUlimits = []*units.Ulimit{} 112 if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil { 113 return nil, errors.Wrap(errdefs.InvalidParameter(err), "error reading ulimit settings") 114 } 115 options.Ulimits = buildUlimits 116 } 117 118 // Note that there are two ways a --build-arg might appear in the 119 // json of the query param: 120 // "foo":"bar" 121 // and "foo":nil 122 // The first is the normal case, ie. --build-arg foo=bar 123 // or --build-arg foo 124 // where foo's value was picked up from an env var. 125 // The second ("foo":nil) is where they put --build-arg foo 126 // but "foo" isn't set as an env var. In that case we can't just drop 127 // the fact they mentioned it, we need to pass that along to the builder 128 // so that it can print a warning about "foo" being unused if there is 129 // no "ARG foo" in the Dockerfile. 130 if buildArgsJSON := r.FormValue("buildargs"); buildArgsJSON != "" { 131 var buildArgs = map[string]*string{} 132 if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil { 133 return nil, errors.Wrap(errdefs.InvalidParameter(err), "error reading build args") 134 } 135 options.BuildArgs = buildArgs 136 } 137 138 if labelsJSON := r.FormValue("labels"); labelsJSON != "" { 139 var labels = map[string]string{} 140 if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil { 141 return nil, errors.Wrap(errdefs.InvalidParameter(err), "error reading labels") 142 } 143 options.Labels = labels 144 } 145 146 if cacheFromJSON := r.FormValue("cachefrom"); cacheFromJSON != "" { 147 var cacheFrom = []string{} 148 if err := json.Unmarshal([]byte(cacheFromJSON), &cacheFrom); err != nil { 149 return nil, err 150 } 151 options.CacheFrom = cacheFrom 152 } 153 154 if bv := r.FormValue("version"); bv != "" { 155 v, err := parseVersion(bv) 156 if err != nil { 157 return nil, err 158 } 159 options.Version = v 160 } 161 162 return options, nil 163 } 164 165 func parseVersion(s string) (types.BuilderVersion, error) { 166 switch types.BuilderVersion(s) { 167 case types.BuilderV1: 168 return types.BuilderV1, nil 169 case types.BuilderBuildKit: 170 return types.BuilderBuildKit, nil 171 default: 172 return "", errors.Errorf("invalid version %q", s) 173 } 174 } 175 176 func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 177 if err := httputils.ParseForm(r); err != nil { 178 return err 179 } 180 fltrs, err := filters.FromJSON(r.Form.Get("filters")) 181 if err != nil { 182 return errors.Wrap(err, "could not parse filters") 183 } 184 ksfv := r.FormValue("keep-storage") 185 if ksfv == "" { 186 ksfv = "0" 187 } 188 ks, err := strconv.Atoi(ksfv) 189 if err != nil { 190 return errors.Wrapf(err, "keep-storage is in bytes and expects an integer, got %v", ksfv) 191 } 192 193 opts := types.BuildCachePruneOptions{ 194 All: httputils.BoolValue(r, "all"), 195 Filters: fltrs, 196 KeepStorage: int64(ks), 197 } 198 199 report, err := br.backend.PruneCache(ctx, opts) 200 if err != nil { 201 return err 202 } 203 return httputils.WriteJSON(w, http.StatusOK, report) 204 } 205 206 func (br *buildRouter) postCancel(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 207 w.Header().Set("Content-Type", "application/json") 208 209 id := r.FormValue("id") 210 if id == "" { 211 return errors.Errorf("build ID not provided") 212 } 213 214 return br.backend.Cancel(ctx, id) 215 } 216 217 func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 218 var ( 219 notVerboseBuffer = bytes.NewBuffer(nil) 220 version = httputils.VersionFromContext(ctx) 221 ) 222 223 w.Header().Set("Content-Type", "application/json") 224 225 body := r.Body 226 var ww io.Writer = w 227 if body != nil { 228 // there is a possibility that output is written before request body 229 // has been fully read so we need to protect against it. 230 // this can be removed when 231 // https://github.com/golang/go/issues/15527 232 // https://github.com/golang/go/issues/22209 233 // has been fixed 234 body, ww = wrapOutputBufferedUntilRequestRead(body, ww) 235 } 236 237 output := ioutils.NewWriteFlusher(ww) 238 defer func() { _ = output.Close() }() 239 240 errf := func(err error) error { 241 if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 { 242 _, _ = output.Write(notVerboseBuffer.Bytes()) 243 } 244 245 // Do not write the error in the http output if it's still empty. 246 // This prevents from writing a 200(OK) when there is an internal error. 247 if !output.Flushed() { 248 return err 249 } 250 _, err = output.Write(streamformatter.FormatError(err)) 251 if err != nil { 252 logrus.Warnf("could not write error response: %v", err) 253 } 254 return nil 255 } 256 257 buildOptions, err := newImageBuildOptions(ctx, r) 258 if err != nil { 259 return errf(err) 260 } 261 buildOptions.AuthConfigs = getAuthConfigs(r.Header) 262 263 if buildOptions.Squash && !br.daemon.HasExperimental() { 264 return errdefs.InvalidParameter(errors.New("squash is only supported with experimental mode")) 265 } 266 267 out := io.Writer(output) 268 if buildOptions.SuppressOutput { 269 out = notVerboseBuffer 270 } 271 272 // Currently, only used if context is from a remote url. 273 // Look at code in DetectContextFromRemoteURL for more information. 274 createProgressReader := func(in io.ReadCloser) io.ReadCloser { 275 progressOutput := streamformatter.NewJSONProgressOutput(out, true) 276 return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", buildOptions.RemoteContext) 277 } 278 279 wantAux := versions.GreaterThanOrEqualTo(version, "1.30") 280 281 imgID, err := br.backend.Build(ctx, backend.BuildConfig{ 282 Source: body, 283 Options: buildOptions, 284 ProgressWriter: buildProgressWriter(out, wantAux, createProgressReader), 285 }) 286 if err != nil { 287 return errf(err) 288 } 289 290 // Everything worked so if -q was provided the output from the daemon 291 // should be just the image ID and we'll print that to stdout. 292 if buildOptions.SuppressOutput { 293 _, _ = fmt.Fprintln(streamformatter.NewStdoutWriter(output), imgID) 294 } 295 return nil 296 } 297 298 func getAuthConfigs(header http.Header) map[string]types.AuthConfig { 299 authConfigs := map[string]types.AuthConfig{} 300 authConfigsEncoded := header.Get("X-Registry-Config") 301 302 if authConfigsEncoded == "" { 303 return authConfigs 304 } 305 306 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) 307 // Pulling an image does not error when no auth is provided so to remain 308 // consistent with the existing api decode errors are ignored 309 _ = json.NewDecoder(authConfigsJSON).Decode(&authConfigs) 310 return authConfigs 311 } 312 313 type syncWriter struct { 314 w io.Writer 315 mu sync.Mutex 316 } 317 318 func (s *syncWriter) Write(b []byte) (count int, err error) { 319 s.mu.Lock() 320 count, err = s.w.Write(b) 321 s.mu.Unlock() 322 return 323 } 324 325 func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func(io.ReadCloser) io.ReadCloser) backend.ProgressWriter { 326 out = &syncWriter{w: out} 327 328 var aux *streamformatter.AuxFormatter 329 if wantAux { 330 aux = &streamformatter.AuxFormatter{Writer: out} 331 } 332 333 return backend.ProgressWriter{ 334 Output: out, 335 StdoutFormatter: streamformatter.NewStdoutWriter(out), 336 StderrFormatter: streamformatter.NewStderrWriter(out), 337 AuxFormatter: aux, 338 ProgressReaderFunc: createProgressReader, 339 } 340 } 341 342 type flusher interface { 343 Flush() 344 } 345 346 func wrapOutputBufferedUntilRequestRead(rc io.ReadCloser, out io.Writer) (io.ReadCloser, io.Writer) { 347 var fl flusher = &ioutils.NopFlusher{} 348 if f, ok := out.(flusher); ok { 349 fl = f 350 } 351 352 w := &wcf{ 353 buf: bytes.NewBuffer(nil), 354 Writer: out, 355 flusher: fl, 356 } 357 r := bufio.NewReader(rc) 358 _, err := r.Peek(1) 359 if err != nil { 360 return rc, out 361 } 362 rc = &rcNotifier{ 363 Reader: r, 364 Closer: rc, 365 notify: w.notify, 366 } 367 return rc, w 368 } 369 370 type rcNotifier struct { 371 io.Reader 372 io.Closer 373 notify func() 374 } 375 376 func (r *rcNotifier) Read(b []byte) (int, error) { 377 n, err := r.Reader.Read(b) 378 if err != nil { 379 r.notify() 380 } 381 return n, err 382 } 383 384 func (r *rcNotifier) Close() error { 385 r.notify() 386 return r.Closer.Close() 387 } 388 389 type wcf struct { 390 io.Writer 391 flusher 392 mu sync.Mutex 393 ready bool 394 buf *bytes.Buffer 395 flushed bool 396 } 397 398 func (w *wcf) Flush() { 399 w.mu.Lock() 400 w.flushed = true 401 if !w.ready { 402 w.mu.Unlock() 403 return 404 } 405 w.mu.Unlock() 406 w.flusher.Flush() 407 } 408 409 func (w *wcf) Flushed() bool { 410 w.mu.Lock() 411 b := w.flushed 412 w.mu.Unlock() 413 return b 414 } 415 416 func (w *wcf) Write(b []byte) (int, error) { 417 w.mu.Lock() 418 if !w.ready { 419 n, err := w.buf.Write(b) 420 w.mu.Unlock() 421 return n, err 422 } 423 w.mu.Unlock() 424 return w.Writer.Write(b) 425 } 426 427 func (w *wcf) notify() { 428 w.mu.Lock() 429 if !w.ready { 430 if w.buf.Len() > 0 { 431 _, _ = io.Copy(w.Writer, w.buf) 432 } 433 if w.flushed { 434 w.flusher.Flush() 435 } 436 w.ready = true 437 } 438 w.mu.Unlock() 439 }