build_routes.go 12 KB

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