image.go 12 KB

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