image.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. package server
  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/types"
  12. "github.com/docker/docker/builder"
  13. "github.com/docker/docker/cliconfig"
  14. "github.com/docker/docker/graph"
  15. "github.com/docker/docker/pkg/ioutils"
  16. "github.com/docker/docker/pkg/parsers"
  17. "github.com/docker/docker/pkg/streamformatter"
  18. "github.com/docker/docker/pkg/ulimit"
  19. "github.com/docker/docker/runconfig"
  20. "github.com/docker/docker/utils"
  21. "golang.org/x/net/context"
  22. )
  23. func (s *Server) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  24. if err := parseForm(r); err != nil {
  25. return err
  26. }
  27. if err := checkForJSON(r); err != nil {
  28. return err
  29. }
  30. cname := r.Form.Get("container")
  31. pause := boolValue(r, "pause")
  32. version := versionFromContext(ctx)
  33. if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
  34. pause = true
  35. }
  36. c, _, err := runconfig.DecodeContainerConfig(r.Body)
  37. if err != nil && err != io.EOF { //Do not fail if body is empty.
  38. return err
  39. }
  40. commitCfg := &builder.CommitConfig{
  41. Pause: pause,
  42. Repo: r.Form.Get("repo"),
  43. Tag: r.Form.Get("tag"),
  44. Author: r.Form.Get("author"),
  45. Comment: r.Form.Get("comment"),
  46. Changes: r.Form["changes"],
  47. Config: c,
  48. }
  49. imgID, err := builder.Commit(cname, s.daemon, commitCfg)
  50. if err != nil {
  51. return err
  52. }
  53. return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
  54. ID: imgID,
  55. })
  56. }
  57. // Creates an image from Pull or from Import
  58. func (s *Server) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  59. if err := parseForm(r); err != nil {
  60. return err
  61. }
  62. var (
  63. image = r.Form.Get("fromImage")
  64. repo = r.Form.Get("repo")
  65. tag = r.Form.Get("tag")
  66. message = r.Form.Get("message")
  67. )
  68. authEncoded := r.Header.Get("X-Registry-Auth")
  69. authConfig := &cliconfig.AuthConfig{}
  70. if authEncoded != "" {
  71. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  72. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  73. // for a pull it is not an error if no auth was given
  74. // to increase compatibility with the existing api it is defaulting to be empty
  75. authConfig = &cliconfig.AuthConfig{}
  76. }
  77. }
  78. var (
  79. err error
  80. output = ioutils.NewWriteFlusher(w)
  81. )
  82. w.Header().Set("Content-Type", "application/json")
  83. if image != "" { //pull
  84. if tag == "" {
  85. image, tag = parsers.ParseRepositoryTag(image)
  86. }
  87. metaHeaders := map[string][]string{}
  88. for k, v := range r.Header {
  89. if strings.HasPrefix(k, "X-Meta-") {
  90. metaHeaders[k] = v
  91. }
  92. }
  93. imagePullConfig := &graph.ImagePullConfig{
  94. MetaHeaders: metaHeaders,
  95. AuthConfig: authConfig,
  96. OutStream: output,
  97. }
  98. err = s.daemon.Repositories().Pull(image, tag, imagePullConfig)
  99. } else { //import
  100. if tag == "" {
  101. repo, tag = parsers.ParseRepositoryTag(repo)
  102. }
  103. src := r.Form.Get("fromSrc")
  104. // 'err' MUST NOT be defined within this block, we need any error
  105. // generated from the download to be available to the output
  106. // stream processing below
  107. var newConfig *runconfig.Config
  108. newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"])
  109. if err != nil {
  110. return err
  111. }
  112. err = s.daemon.Repositories().Import(src, repo, tag, message, r.Body, output, newConfig)
  113. }
  114. if err != nil {
  115. if !output.Flushed() {
  116. return err
  117. }
  118. sf := streamformatter.NewJSONStreamFormatter()
  119. output.Write(sf.FormatError(err))
  120. }
  121. return nil
  122. }
  123. func (s *Server) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  124. if vars == nil {
  125. return fmt.Errorf("Missing parameter")
  126. }
  127. metaHeaders := map[string][]string{}
  128. for k, v := range r.Header {
  129. if strings.HasPrefix(k, "X-Meta-") {
  130. metaHeaders[k] = v
  131. }
  132. }
  133. if err := parseForm(r); err != nil {
  134. return err
  135. }
  136. authConfig := &cliconfig.AuthConfig{}
  137. authEncoded := r.Header.Get("X-Registry-Auth")
  138. if authEncoded != "" {
  139. // the new format is to handle the authConfig as a header
  140. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  141. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  142. // to increase compatibility to existing api it is defaulting to be empty
  143. authConfig = &cliconfig.AuthConfig{}
  144. }
  145. } else {
  146. // the old format is supported for compatibility if there was no authConfig header
  147. if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
  148. return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
  149. }
  150. }
  151. name := vars["name"]
  152. output := ioutils.NewWriteFlusher(w)
  153. imagePushConfig := &graph.ImagePushConfig{
  154. MetaHeaders: metaHeaders,
  155. AuthConfig: authConfig,
  156. Tag: r.Form.Get("tag"),
  157. OutStream: output,
  158. }
  159. w.Header().Set("Content-Type", "application/json")
  160. if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil {
  161. if !output.Flushed() {
  162. return err
  163. }
  164. sf := streamformatter.NewJSONStreamFormatter()
  165. output.Write(sf.FormatError(err))
  166. }
  167. return nil
  168. }
  169. func (s *Server) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  170. if vars == nil {
  171. return fmt.Errorf("Missing parameter")
  172. }
  173. if err := parseForm(r); err != nil {
  174. return err
  175. }
  176. w.Header().Set("Content-Type", "application/x-tar")
  177. output := ioutils.NewWriteFlusher(w)
  178. var names []string
  179. if name, ok := vars["name"]; ok {
  180. names = []string{name}
  181. } else {
  182. names = r.Form["names"]
  183. }
  184. if err := s.daemon.Repositories().ImageExport(names, output); err != nil {
  185. if !output.Flushed() {
  186. return err
  187. }
  188. sf := streamformatter.NewJSONStreamFormatter()
  189. output.Write(sf.FormatError(err))
  190. }
  191. return nil
  192. }
  193. func (s *Server) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  194. return s.daemon.Repositories().Load(r.Body, w)
  195. }
  196. func (s *Server) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  197. if err := parseForm(r); err != nil {
  198. return err
  199. }
  200. if vars == nil {
  201. return fmt.Errorf("Missing parameter")
  202. }
  203. name := vars["name"]
  204. if name == "" {
  205. return fmt.Errorf("image name cannot be blank")
  206. }
  207. force := boolValue(r, "force")
  208. prune := !boolValue(r, "noprune")
  209. list, err := s.daemon.ImageDelete(name, force, prune)
  210. if err != nil {
  211. return err
  212. }
  213. return writeJSON(w, http.StatusOK, list)
  214. }
  215. func (s *Server) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  216. if vars == nil {
  217. return fmt.Errorf("Missing parameter")
  218. }
  219. imageInspect, err := s.daemon.Repositories().Lookup(vars["name"])
  220. if err != nil {
  221. return err
  222. }
  223. return writeJSON(w, http.StatusOK, imageInspect)
  224. }
  225. func (s *Server) 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 = builder.NewBuildConfig()
  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 := versionFromContext(ctx)
  241. if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
  242. buildConfig.Remove = true
  243. } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
  244. buildConfig.Remove = true
  245. } else {
  246. buildConfig.Remove = boolValue(r, "rm")
  247. }
  248. if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
  249. buildConfig.Pull = true
  250. }
  251. output := ioutils.NewWriteFlusher(w)
  252. buildConfig.Stdout = output
  253. buildConfig.Context = r.Body
  254. buildConfig.RemoteURL = r.FormValue("remote")
  255. buildConfig.DockerfileName = r.FormValue("dockerfile")
  256. buildConfig.RepoName = r.FormValue("t")
  257. buildConfig.SuppressOutput = boolValue(r, "q")
  258. buildConfig.NoCache = boolValue(r, "nocache")
  259. buildConfig.ForceRemove = boolValue(r, "forcerm")
  260. buildConfig.AuthConfigs = authConfigs
  261. buildConfig.MemorySwap = int64ValueOrZero(r, "memswap")
  262. buildConfig.Memory = int64ValueOrZero(r, "memory")
  263. buildConfig.CPUShares = int64ValueOrZero(r, "cpushares")
  264. buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod")
  265. buildConfig.CPUQuota = int64ValueOrZero(r, "cpuquota")
  266. buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
  267. buildConfig.CPUSetMems = r.FormValue("cpusetmems")
  268. buildConfig.CgroupParent = r.FormValue("cgroupparent")
  269. var buildUlimits = []*ulimit.Ulimit{}
  270. ulimitsJSON := r.FormValue("ulimits")
  271. if ulimitsJSON != "" {
  272. if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
  273. return err
  274. }
  275. buildConfig.Ulimits = buildUlimits
  276. }
  277. var buildArgs = map[string]string{}
  278. buildArgsJSON := r.FormValue("buildargs")
  279. if buildArgsJSON != "" {
  280. if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
  281. return err
  282. }
  283. }
  284. buildConfig.BuildArgs = buildArgs
  285. // Job cancellation. Note: not all job types support this.
  286. if closeNotifier, ok := w.(http.CloseNotifier); ok {
  287. finished := make(chan struct{})
  288. defer close(finished)
  289. go func() {
  290. select {
  291. case <-finished:
  292. case <-closeNotifier.CloseNotify():
  293. logrus.Infof("Client disconnected, cancelling job: build")
  294. buildConfig.Cancel()
  295. }
  296. }()
  297. }
  298. if err := builder.Build(s.daemon, buildConfig); err != nil {
  299. // Do not write the error in the http output if it's still empty.
  300. // This prevents from writing a 200(OK) when there is an interal error.
  301. if !output.Flushed() {
  302. return err
  303. }
  304. sf := streamformatter.NewJSONStreamFormatter()
  305. w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
  306. }
  307. return nil
  308. }
  309. func (s *Server) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  310. if err := parseForm(r); err != nil {
  311. return err
  312. }
  313. // FIXME: The filter parameter could just be a match filter
  314. images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), boolValue(r, "all"))
  315. if err != nil {
  316. return err
  317. }
  318. return writeJSON(w, http.StatusOK, images)
  319. }
  320. func (s *Server) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  321. if vars == nil {
  322. return fmt.Errorf("Missing parameter")
  323. }
  324. name := vars["name"]
  325. history, err := s.daemon.Repositories().History(name)
  326. if err != nil {
  327. return err
  328. }
  329. return writeJSON(w, http.StatusOK, history)
  330. }
  331. func (s *Server) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  332. if err := parseForm(r); err != nil {
  333. return err
  334. }
  335. if vars == nil {
  336. return fmt.Errorf("Missing parameter")
  337. }
  338. repo := r.Form.Get("repo")
  339. tag := r.Form.Get("tag")
  340. force := boolValue(r, "force")
  341. name := vars["name"]
  342. if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil {
  343. return err
  344. }
  345. s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "")
  346. w.WriteHeader(http.StatusCreated)
  347. return nil
  348. }
  349. func (s *Server) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  350. if err := parseForm(r); err != nil {
  351. return err
  352. }
  353. var (
  354. config *cliconfig.AuthConfig
  355. authEncoded = r.Header.Get("X-Registry-Auth")
  356. headers = map[string][]string{}
  357. )
  358. if authEncoded != "" {
  359. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  360. if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
  361. // for a search it is not an error if no auth was given
  362. // to increase compatibility with the existing api it is defaulting to be empty
  363. config = &cliconfig.AuthConfig{}
  364. }
  365. }
  366. for k, v := range r.Header {
  367. if strings.HasPrefix(k, "X-Meta-") {
  368. headers[k] = v
  369. }
  370. }
  371. query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers)
  372. if err != nil {
  373. return err
  374. }
  375. return writeJSON(w, http.StatusOK, query.Results)
  376. }