build_routes.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package build
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "strconv"
  11. "strings"
  12. "github.com/Sirupsen/logrus"
  13. "github.com/docker/docker/api/server/httputils"
  14. "github.com/docker/docker/api/types"
  15. "github.com/docker/docker/api/types/container"
  16. "github.com/docker/docker/builder"
  17. "github.com/docker/docker/builder/dockerfile"
  18. "github.com/docker/docker/daemon/daemonbuilder"
  19. "github.com/docker/docker/pkg/archive"
  20. "github.com/docker/docker/pkg/chrootarchive"
  21. "github.com/docker/docker/pkg/ioutils"
  22. "github.com/docker/docker/pkg/progress"
  23. "github.com/docker/docker/pkg/streamformatter"
  24. "github.com/docker/docker/reference"
  25. "github.com/docker/docker/utils"
  26. "github.com/docker/go-units"
  27. "golang.org/x/net/context"
  28. )
  29. // sanitizeRepoAndTags parses the raw "t" parameter received from the client
  30. // to a slice of repoAndTag.
  31. // It also validates each repoName and tag.
  32. func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
  33. var (
  34. repoAndTags []reference.Named
  35. // This map is used for deduplicating the "-t" parameter.
  36. uniqNames = make(map[string]struct{})
  37. )
  38. for _, repo := range names {
  39. if repo == "" {
  40. continue
  41. }
  42. ref, err := reference.ParseNamed(repo)
  43. if err != nil {
  44. return nil, err
  45. }
  46. ref = reference.WithDefaultTag(ref)
  47. if _, isCanonical := ref.(reference.Canonical); isCanonical {
  48. return nil, errors.New("build tag cannot contain a digest")
  49. }
  50. if _, isTagged := ref.(reference.NamedTagged); !isTagged {
  51. ref, err = reference.WithTag(ref, reference.DefaultTag)
  52. }
  53. nameWithTag := ref.String()
  54. if _, exists := uniqNames[nameWithTag]; !exists {
  55. uniqNames[nameWithTag] = struct{}{}
  56. repoAndTags = append(repoAndTags, ref)
  57. }
  58. }
  59. return repoAndTags, nil
  60. }
  61. func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  62. var (
  63. authConfigs = map[string]types.AuthConfig{}
  64. authConfigsEncoded = r.Header.Get("X-Registry-Config")
  65. buildConfig = &dockerfile.Config{}
  66. notVerboseBuffer = bytes.NewBuffer(nil)
  67. )
  68. if authConfigsEncoded != "" {
  69. authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
  70. if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
  71. // for a pull it is not an error if no auth was given
  72. // to increase compatibility with the existing api it is defaulting
  73. // to be empty.
  74. }
  75. }
  76. w.Header().Set("Content-Type", "application/json")
  77. version := httputils.VersionFromContext(ctx)
  78. output := ioutils.NewWriteFlusher(w)
  79. defer output.Close()
  80. sf := streamformatter.NewJSONStreamFormatter()
  81. errf := func(err error) error {
  82. if !buildConfig.Verbose && notVerboseBuffer.Len() > 0 {
  83. output.Write(notVerboseBuffer.Bytes())
  84. }
  85. // Do not write the error in the http output if it's still empty.
  86. // This prevents from writing a 200(OK) when there is an internal error.
  87. if !output.Flushed() {
  88. return err
  89. }
  90. _, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
  91. if err != nil {
  92. logrus.Warnf("could not write error response: %v", err)
  93. }
  94. return nil
  95. }
  96. if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
  97. buildConfig.Remove = true
  98. } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
  99. buildConfig.Remove = true
  100. } else {
  101. buildConfig.Remove = httputils.BoolValue(r, "rm")
  102. }
  103. if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
  104. buildConfig.Pull = true
  105. }
  106. repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
  107. if err != nil {
  108. return errf(err)
  109. }
  110. buildConfig.DockerfileName = r.FormValue("dockerfile")
  111. buildConfig.Verbose = !httputils.BoolValue(r, "q")
  112. buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
  113. buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
  114. buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
  115. buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
  116. buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
  117. buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
  118. buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
  119. buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
  120. buildConfig.CPUSetMems = r.FormValue("cpusetmems")
  121. buildConfig.CgroupParent = r.FormValue("cgroupparent")
  122. if r.Form.Get("shmsize") != "" {
  123. shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
  124. if err != nil {
  125. return errf(err)
  126. }
  127. buildConfig.ShmSize = &shmSize
  128. }
  129. if i := container.IsolationLevel(r.FormValue("isolation")); i != "" {
  130. if !container.IsolationLevel.IsValid(i) {
  131. return errf(fmt.Errorf("Unsupported isolation: %q", i))
  132. }
  133. buildConfig.Isolation = i
  134. }
  135. var buildUlimits = []*units.Ulimit{}
  136. ulimitsJSON := r.FormValue("ulimits")
  137. if ulimitsJSON != "" {
  138. if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
  139. return errf(err)
  140. }
  141. buildConfig.Ulimits = buildUlimits
  142. }
  143. var buildArgs = map[string]string{}
  144. buildArgsJSON := r.FormValue("buildargs")
  145. if buildArgsJSON != "" {
  146. if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
  147. return errf(err)
  148. }
  149. buildConfig.BuildArgs = buildArgs
  150. }
  151. remoteURL := r.FormValue("remote")
  152. // Currently, only used if context is from a remote url.
  153. // Look at code in DetectContextFromRemoteURL for more information.
  154. createProgressReader := func(in io.ReadCloser) io.ReadCloser {
  155. progressOutput := sf.NewProgressOutput(output, true)
  156. if !buildConfig.Verbose {
  157. progressOutput = sf.NewProgressOutput(notVerboseBuffer, true)
  158. }
  159. return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
  160. }
  161. var (
  162. context builder.ModifiableContext
  163. dockerfileName string
  164. )
  165. context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
  166. if err != nil {
  167. return errf(err)
  168. }
  169. defer func() {
  170. if err := context.Close(); err != nil {
  171. logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
  172. }
  173. }()
  174. uidMaps, gidMaps := br.backend.GetUIDGIDMaps()
  175. defaultArchiver := &archive.Archiver{
  176. Untar: chrootarchive.Untar,
  177. UIDMaps: uidMaps,
  178. GIDMaps: gidMaps,
  179. }
  180. docker := &daemonbuilder.Docker{
  181. Daemon: br.backend,
  182. OutOld: output,
  183. AuthConfigs: authConfigs,
  184. Archiver: defaultArchiver,
  185. }
  186. if !buildConfig.Verbose {
  187. docker.OutOld = notVerboseBuffer
  188. }
  189. b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil)
  190. if err != nil {
  191. return errf(err)
  192. }
  193. b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
  194. b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
  195. if !buildConfig.Verbose {
  196. b.Stdout = &streamformatter.StdoutFormatter{Writer: notVerboseBuffer, StreamFormatter: sf}
  197. b.Stderr = &streamformatter.StderrFormatter{Writer: notVerboseBuffer, StreamFormatter: sf}
  198. }
  199. if closeNotifier, ok := w.(http.CloseNotifier); ok {
  200. finished := make(chan struct{})
  201. defer close(finished)
  202. go func() {
  203. select {
  204. case <-finished:
  205. case <-closeNotifier.CloseNotify():
  206. logrus.Infof("Client disconnected, cancelling job: build")
  207. b.Cancel()
  208. }
  209. }()
  210. }
  211. if len(dockerfileName) > 0 {
  212. b.DockerfileName = dockerfileName
  213. }
  214. imgID, err := b.Build()
  215. if err != nil {
  216. return errf(err)
  217. }
  218. for _, rt := range repoAndTags {
  219. if err := br.backend.TagImage(rt, imgID); err != nil {
  220. return errf(err)
  221. }
  222. }
  223. // Everything worked so if -q was provided the output from the daemon
  224. // should be just the image ID and we'll print that to stdout.
  225. if !buildConfig.Verbose {
  226. stdout := &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
  227. fmt.Fprintf(stdout, "%s\n", string(imgID))
  228. }
  229. return nil
  230. }