build_routes.go 12 KB

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