image.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. package local
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "strings"
  10. "github.com/Sirupsen/logrus"
  11. "github.com/docker/docker/api/server/httputils"
  12. "github.com/docker/docker/api/types"
  13. "github.com/docker/docker/builder"
  14. "github.com/docker/docker/builder/dockerfile"
  15. "github.com/docker/docker/cliconfig"
  16. "github.com/docker/docker/daemon/daemonbuilder"
  17. derr "github.com/docker/docker/errors"
  18. "github.com/docker/docker/graph"
  19. "github.com/docker/docker/graph/tags"
  20. "github.com/docker/docker/pkg/archive"
  21. "github.com/docker/docker/pkg/chrootarchive"
  22. "github.com/docker/docker/pkg/ioutils"
  23. "github.com/docker/docker/pkg/parsers"
  24. "github.com/docker/docker/pkg/progressreader"
  25. "github.com/docker/docker/pkg/streamformatter"
  26. "github.com/docker/docker/pkg/ulimit"
  27. "github.com/docker/docker/registry"
  28. "github.com/docker/docker/runconfig"
  29. "github.com/docker/docker/utils"
  30. "golang.org/x/net/context"
  31. )
  32. func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  33. if err := httputils.ParseForm(r); err != nil {
  34. return err
  35. }
  36. if err := httputils.CheckForJSON(r); err != nil {
  37. return err
  38. }
  39. cname := r.Form.Get("container")
  40. pause := httputils.BoolValue(r, "pause")
  41. version := httputils.VersionFromContext(ctx)
  42. if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
  43. pause = true
  44. }
  45. c, _, err := runconfig.DecodeContainerConfig(r.Body)
  46. if err != nil && err != io.EOF { //Do not fail if body is empty.
  47. return err
  48. }
  49. commitCfg := &dockerfile.CommitConfig{
  50. Pause: pause,
  51. Repo: r.Form.Get("repo"),
  52. Tag: r.Form.Get("tag"),
  53. Author: r.Form.Get("author"),
  54. Comment: r.Form.Get("comment"),
  55. Changes: r.Form["changes"],
  56. Config: c,
  57. }
  58. if !s.daemon.Exists(cname) {
  59. return derr.ErrorCodeNoSuchContainer.WithArgs(cname)
  60. }
  61. imgID, err := dockerfile.Commit(cname, s.daemon, commitCfg)
  62. if err != nil {
  63. return err
  64. }
  65. return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
  66. ID: string(imgID),
  67. })
  68. }
  69. // Creates an image from Pull or from Import
  70. func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  71. if err := httputils.ParseForm(r); err != nil {
  72. return err
  73. }
  74. var (
  75. image = r.Form.Get("fromImage")
  76. repo = r.Form.Get("repo")
  77. tag = r.Form.Get("tag")
  78. message = r.Form.Get("message")
  79. )
  80. authEncoded := r.Header.Get("X-Registry-Auth")
  81. authConfig := &cliconfig.AuthConfig{}
  82. if authEncoded != "" {
  83. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  84. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  85. // for a pull it is not an error if no auth was given
  86. // to increase compatibility with the existing api it is defaulting to be empty
  87. authConfig = &cliconfig.AuthConfig{}
  88. }
  89. }
  90. var (
  91. err error
  92. output = ioutils.NewWriteFlusher(w)
  93. )
  94. w.Header().Set("Content-Type", "application/json")
  95. if image != "" { //pull
  96. if tag == "" {
  97. image, tag = parsers.ParseRepositoryTag(image)
  98. }
  99. metaHeaders := map[string][]string{}
  100. for k, v := range r.Header {
  101. if strings.HasPrefix(k, "X-Meta-") {
  102. metaHeaders[k] = v
  103. }
  104. }
  105. imagePullConfig := &graph.ImagePullConfig{
  106. MetaHeaders: metaHeaders,
  107. AuthConfig: authConfig,
  108. OutStream: output,
  109. }
  110. err = s.daemon.PullImage(image, tag, imagePullConfig)
  111. } else { //import
  112. if tag == "" {
  113. repo, tag = parsers.ParseRepositoryTag(repo)
  114. }
  115. src := r.Form.Get("fromSrc")
  116. // 'err' MUST NOT be defined within this block, we need any error
  117. // generated from the download to be available to the output
  118. // stream processing below
  119. var newConfig *runconfig.Config
  120. newConfig, err = dockerfile.BuildFromConfig(&runconfig.Config{}, r.Form["changes"])
  121. if err != nil {
  122. return err
  123. }
  124. err = s.daemon.ImportImage(src, repo, tag, message, r.Body, output, newConfig)
  125. }
  126. if err != nil {
  127. if !output.Flushed() {
  128. return err
  129. }
  130. sf := streamformatter.NewJSONStreamFormatter()
  131. output.Write(sf.FormatError(err))
  132. }
  133. return nil
  134. }
  135. func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  136. metaHeaders := map[string][]string{}
  137. for k, v := range r.Header {
  138. if strings.HasPrefix(k, "X-Meta-") {
  139. metaHeaders[k] = v
  140. }
  141. }
  142. if err := httputils.ParseForm(r); err != nil {
  143. return err
  144. }
  145. authConfig := &cliconfig.AuthConfig{}
  146. authEncoded := r.Header.Get("X-Registry-Auth")
  147. if authEncoded != "" {
  148. // the new format is to handle the authConfig as a header
  149. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  150. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  151. // to increase compatibility to existing api it is defaulting to be empty
  152. authConfig = &cliconfig.AuthConfig{}
  153. }
  154. } else {
  155. // the old format is supported for compatibility if there was no authConfig header
  156. if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
  157. return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
  158. }
  159. }
  160. name := vars["name"]
  161. output := ioutils.NewWriteFlusher(w)
  162. imagePushConfig := &graph.ImagePushConfig{
  163. MetaHeaders: metaHeaders,
  164. AuthConfig: authConfig,
  165. Tag: r.Form.Get("tag"),
  166. OutStream: output,
  167. }
  168. w.Header().Set("Content-Type", "application/json")
  169. if err := s.daemon.PushImage(name, imagePushConfig); err != nil {
  170. if !output.Flushed() {
  171. return err
  172. }
  173. sf := streamformatter.NewJSONStreamFormatter()
  174. output.Write(sf.FormatError(err))
  175. }
  176. return nil
  177. }
  178. func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  179. if err := httputils.ParseForm(r); err != nil {
  180. return err
  181. }
  182. w.Header().Set("Content-Type", "application/x-tar")
  183. output := ioutils.NewWriteFlusher(w)
  184. var names []string
  185. if name, ok := vars["name"]; ok {
  186. names = []string{name}
  187. } else {
  188. names = r.Form["names"]
  189. }
  190. if err := s.daemon.ExportImage(names, output); err != nil {
  191. if !output.Flushed() {
  192. return err
  193. }
  194. sf := streamformatter.NewJSONStreamFormatter()
  195. output.Write(sf.FormatError(err))
  196. }
  197. return nil
  198. }
  199. func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  200. return s.daemon.LoadImage(r.Body, w)
  201. }
  202. func (s *router) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  203. if err := httputils.ParseForm(r); err != nil {
  204. return err
  205. }
  206. name := vars["name"]
  207. if name == "" {
  208. return fmt.Errorf("image name cannot be blank")
  209. }
  210. force := httputils.BoolValue(r, "force")
  211. prune := !httputils.BoolValue(r, "noprune")
  212. list, err := s.daemon.ImageDelete(name, force, prune)
  213. if err != nil {
  214. return err
  215. }
  216. return httputils.WriteJSON(w, http.StatusOK, list)
  217. }
  218. func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  219. imageInspect, err := s.daemon.LookupImage(vars["name"])
  220. if err != nil {
  221. return err
  222. }
  223. return httputils.WriteJSON(w, http.StatusOK, imageInspect)
  224. }
  225. func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  226. var (
  227. authConfigs = map[string]cliconfig.AuthConfig{}
  228. authConfigsEncoded = r.Header.Get("X-Registry-Config")
  229. buildConfig = &dockerfile.Config{}
  230. )
  231. if authConfigsEncoded != "" {
  232. authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
  233. if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
  234. // for a pull it is not an error if no auth was given
  235. // to increase compatibility with the existing api it is defaulting
  236. // to be empty.
  237. }
  238. }
  239. w.Header().Set("Content-Type", "application/json")
  240. version := httputils.VersionFromContext(ctx)
  241. output := ioutils.NewWriteFlusher(w)
  242. sf := streamformatter.NewJSONStreamFormatter()
  243. errf := func(err error) error {
  244. // Do not write the error in the http output if it's still empty.
  245. // This prevents from writing a 200(OK) when there is an interal error.
  246. if !output.Flushed() {
  247. return err
  248. }
  249. _, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
  250. if err != nil {
  251. logrus.Warnf("could not write error response: %v", err)
  252. }
  253. return nil
  254. }
  255. if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
  256. buildConfig.Remove = true
  257. } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
  258. buildConfig.Remove = true
  259. } else {
  260. buildConfig.Remove = httputils.BoolValue(r, "rm")
  261. }
  262. if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
  263. buildConfig.Pull = true
  264. }
  265. repoName, tag := parsers.ParseRepositoryTag(r.FormValue("t"))
  266. if repoName != "" {
  267. if err := registry.ValidateRepositoryName(repoName); err != nil {
  268. return errf(err)
  269. }
  270. if len(tag) > 0 {
  271. if err := tags.ValidateTagName(tag); err != nil {
  272. return errf(err)
  273. }
  274. }
  275. }
  276. buildConfig.DockerfileName = r.FormValue("dockerfile")
  277. buildConfig.Verbose = !httputils.BoolValue(r, "q")
  278. buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
  279. buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
  280. buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
  281. buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
  282. buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
  283. buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
  284. buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
  285. buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
  286. buildConfig.CPUSetMems = r.FormValue("cpusetmems")
  287. buildConfig.CgroupParent = r.FormValue("cgroupparent")
  288. var buildUlimits = []*ulimit.Ulimit{}
  289. ulimitsJSON := r.FormValue("ulimits")
  290. if ulimitsJSON != "" {
  291. if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
  292. return errf(err)
  293. }
  294. buildConfig.Ulimits = buildUlimits
  295. }
  296. var buildArgs = map[string]string{}
  297. buildArgsJSON := r.FormValue("buildargs")
  298. if buildArgsJSON != "" {
  299. if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
  300. return errf(err)
  301. }
  302. buildConfig.BuildArgs = buildArgs
  303. }
  304. remoteURL := r.FormValue("remote")
  305. // Currently, only used if context is from a remote url.
  306. // The field `In` is set by DetectContextFromRemoteURL.
  307. // Look at code in DetectContextFromRemoteURL for more information.
  308. pReader := &progressreader.Config{
  309. // TODO: make progressreader streamformatter-agnostic
  310. Out: output,
  311. Formatter: sf,
  312. Size: r.ContentLength,
  313. NewLines: true,
  314. ID: "Downloading context",
  315. Action: remoteURL,
  316. }
  317. var (
  318. context builder.ModifiableContext
  319. dockerfileName string
  320. err error
  321. )
  322. context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader)
  323. if err != nil {
  324. return errf(err)
  325. }
  326. defer func() {
  327. if err := context.Close(); err != nil {
  328. logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
  329. }
  330. }()
  331. uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
  332. defaultArchiver := &archive.Archiver{
  333. Untar: chrootarchive.Untar,
  334. UIDMaps: uidMaps,
  335. GIDMaps: gidMaps,
  336. }
  337. docker := daemonbuilder.Docker{s.daemon, output, authConfigs, defaultArchiver}
  338. b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{context}, nil)
  339. if err != nil {
  340. return errf(err)
  341. }
  342. b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
  343. b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
  344. if closeNotifier, ok := w.(http.CloseNotifier); ok {
  345. finished := make(chan struct{})
  346. defer close(finished)
  347. go func() {
  348. select {
  349. case <-finished:
  350. case <-closeNotifier.CloseNotify():
  351. logrus.Infof("Client disconnected, cancelling job: build")
  352. b.Cancel()
  353. }
  354. }()
  355. }
  356. if len(dockerfileName) > 0 {
  357. b.DockerfileName = dockerfileName
  358. }
  359. imgID, err := b.Build()
  360. if err != nil {
  361. return errf(err)
  362. }
  363. if repoName != "" {
  364. if err := s.daemon.TagImage(repoName, tag, string(imgID), true); err != nil {
  365. return errf(err)
  366. }
  367. }
  368. return nil
  369. }
  370. func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  371. if err := httputils.ParseForm(r); err != nil {
  372. return err
  373. }
  374. // FIXME: The filter parameter could just be a match filter
  375. images, err := s.daemon.ListImages(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
  376. if err != nil {
  377. return err
  378. }
  379. return httputils.WriteJSON(w, http.StatusOK, images)
  380. }
  381. func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  382. name := vars["name"]
  383. history, err := s.daemon.ImageHistory(name)
  384. if err != nil {
  385. return err
  386. }
  387. return httputils.WriteJSON(w, http.StatusOK, history)
  388. }
  389. func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  390. if err := httputils.ParseForm(r); err != nil {
  391. return err
  392. }
  393. repo := r.Form.Get("repo")
  394. tag := r.Form.Get("tag")
  395. name := vars["name"]
  396. force := httputils.BoolValue(r, "force")
  397. if err := s.daemon.TagImage(repo, tag, name, force); err != nil {
  398. return err
  399. }
  400. w.WriteHeader(http.StatusCreated)
  401. return nil
  402. }
  403. func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  404. if err := httputils.ParseForm(r); err != nil {
  405. return err
  406. }
  407. var (
  408. config *cliconfig.AuthConfig
  409. authEncoded = r.Header.Get("X-Registry-Auth")
  410. headers = map[string][]string{}
  411. )
  412. if authEncoded != "" {
  413. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  414. if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
  415. // for a search it is not an error if no auth was given
  416. // to increase compatibility with the existing api it is defaulting to be empty
  417. config = &cliconfig.AuthConfig{}
  418. }
  419. }
  420. for k, v := range r.Header {
  421. if strings.HasPrefix(k, "X-Meta-") {
  422. headers[k] = v
  423. }
  424. }
  425. query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers)
  426. if err != nil {
  427. return err
  428. }
  429. return httputils.WriteJSON(w, http.StatusOK, query.Results)
  430. }