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