github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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 242 if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 { 243 _, _ = output.Write(notVerboseBuffer.Bytes()) 244 } 245 246 // Do not write the error in the http output if it's still empty. 247 // This prevents from writing a 200(OK) when there is an internal error. 248 if !output.Flushed() { 249 return err 250 } 251 _, err = output.Write(streamformatter.FormatError(err)) 252 if err != nil { 253 logrus.Warnf("could not write error response: %v", err) 254 } 255 return nil 256 } 257 258 buildOptions, err := newImageBuildOptions(ctx, r) 259 if err != nil { 260 return errf(err) 261 } 262 buildOptions.AuthConfigs = getAuthConfigs(r.Header) 263 264 if buildOptions.Squash && !br.daemon.HasExperimental() { 265 return errdefs.InvalidParameter(errors.New("squash is only supported with experimental mode")) 266 } 267 268 out := io.Writer(output) 269 if buildOptions.SuppressOutput { 270 out = notVerboseBuffer 271 } 272 273 // Currently, only used if context is from a remote url. 274 // Look at code in DetectContextFromRemoteURL for more information. 275 createProgressReader := func(in io.ReadCloser) io.ReadCloser { 276 progressOutput := streamformatter.NewJSONProgressOutput(out, true) 277 return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", buildOptions.RemoteContext) 278 } 279 280 wantAux := versions.GreaterThanOrEqualTo(version, "1.30") 281 282 imgID, err := br.backend.Build(ctx, backend.BuildConfig{ 283 Source: body, 284 Options: buildOptions, 285 ProgressWriter: buildProgressWriter(out, wantAux, createProgressReader), 286 }) 287 if err != nil { 288 return errf(err) 289 } 290 291 // Everything worked so if -q was provided the output from the daemon 292 // should be just the image ID and we'll print that to stdout. 293 if buildOptions.SuppressOutput { 294 _, _ = fmt.Fprintln(streamformatter.NewStdoutWriter(output), imgID) 295 } 296 return nil 297 } 298 299 func getAuthConfigs(header http.Header) map[string]types.AuthConfig { 300 authConfigs := map[string]types.AuthConfig{} 301 authConfigsEncoded := header.Get("X-Registry-Config") 302 303 if authConfigsEncoded == "" { 304 return authConfigs 305 } 306 307 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) 308 // Pulling an image does not error when no auth is provided so to remain 309 // consistent with the existing api decode errors are ignored 310 _ = json.NewDecoder(authConfigsJSON).Decode(&authConfigs) 311 return authConfigs 312 } 313 314 type syncWriter struct { 315 w io.Writer 316 mu sync.Mutex 317 } 318 319 func (s *syncWriter) Write(b []byte) (count int, err error) { 320 s.mu.Lock() 321 count, err = s.w.Write(b) 322 s.mu.Unlock() 323 return 324 } 325 326 func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func(io.ReadCloser) io.ReadCloser) backend.ProgressWriter { 327 out = &syncWriter{w: out} 328 329 var aux *streamformatter.AuxFormatter 330 if wantAux { 331 aux = &streamformatter.AuxFormatter{Writer: out} 332 } 333 334 return backend.ProgressWriter{ 335 Output: out, 336 StdoutFormatter: streamformatter.NewStdoutWriter(out), 337 StderrFormatter: streamformatter.NewStderrWriter(out), 338 AuxFormatter: aux, 339 ProgressReaderFunc: createProgressReader, 340 } 341 } 342 343 type flusher interface { 344 Flush() 345 } 346 347 func wrapOutputBufferedUntilRequestRead(rc io.ReadCloser, out io.Writer) (io.ReadCloser, io.Writer) { 348 var fl flusher = &ioutils.NopFlusher{} 349 if f, ok := out.(flusher); ok { 350 fl = f 351 } 352 353 w := &wcf{ 354 buf: bytes.NewBuffer(nil), 355 Writer: out, 356 flusher: fl, 357 } 358 r := bufio.NewReader(rc) 359 _, err := r.Peek(1) 360 if err != nil { 361 return rc, out 362 } 363 rc = &rcNotifier{ 364 Reader: r, 365 Closer: rc, 366 notify: w.notify, 367 } 368 return rc, w 369 } 370 371 type rcNotifier struct { 372 io.Reader 373 io.Closer 374 notify func() 375 } 376 377 func (r *rcNotifier) Read(b []byte) (int, error) { 378 n, err := r.Reader.Read(b) 379 if err != nil { 380 r.notify() 381 } 382 return n, err 383 } 384 385 func (r *rcNotifier) Close() error { 386 r.notify() 387 return r.Closer.Close() 388 } 389 390 type wcf struct { 391 io.Writer 392 flusher 393 mu sync.Mutex 394 ready bool 395 buf *bytes.Buffer 396 flushed bool 397 } 398 399 func (w *wcf) Flush() { 400 w.mu.Lock() 401 w.flushed = true 402 if !w.ready { 403 w.mu.Unlock() 404 return 405 } 406 w.mu.Unlock() 407 w.flusher.Flush() 408 } 409 410 func (w *wcf) Flushed() bool { 411 w.mu.Lock() 412 b := w.flushed 413 w.mu.Unlock() 414 return b 415 } 416 417 func (w *wcf) Write(b []byte) (int, error) { 418 w.mu.Lock() 419 if !w.ready { 420 n, err := w.buf.Write(b) 421 w.mu.Unlock() 422 return n, err 423 } 424 w.mu.Unlock() 425 return w.Writer.Write(b) 426 } 427 428 func (w *wcf) notify() { 429 w.mu.Lock() 430 if !w.ready { 431 if w.buf.Len() > 0 { 432 _, _ = io.Copy(w.Writer, w.buf) 433 } 434 if w.flushed { 435 w.flusher.Flush() 436 } 437 w.ready = true 438 } 439 w.mu.Unlock() 440 }