image_routes.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. package image
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "strconv"
  9. "strings"
  10. "github.com/docker/docker/api/server/httputils"
  11. "github.com/docker/docker/api/types/backend"
  12. "github.com/docker/docker/pkg/ioutils"
  13. "github.com/docker/docker/pkg/streamformatter"
  14. "github.com/docker/docker/registry"
  15. "github.com/docker/engine-api/types"
  16. "github.com/docker/engine-api/types/container"
  17. "github.com/docker/engine-api/types/versions"
  18. "golang.org/x/net/context"
  19. )
  20. func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  21. if err := httputils.ParseForm(r); err != nil {
  22. return err
  23. }
  24. if err := httputils.CheckForJSON(r); err != nil {
  25. return err
  26. }
  27. cname := r.Form.Get("container")
  28. pause := httputils.BoolValue(r, "pause")
  29. version := httputils.VersionFromContext(ctx)
  30. if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") {
  31. pause = true
  32. }
  33. c, _, _, err := s.decoder.DecodeConfig(r.Body)
  34. if err != nil && err != io.EOF { //Do not fail if body is empty.
  35. return err
  36. }
  37. if c == nil {
  38. c = &container.Config{}
  39. }
  40. commitCfg := &backend.ContainerCommitConfig{
  41. ContainerCommitConfig: types.ContainerCommitConfig{
  42. Pause: pause,
  43. Repo: r.Form.Get("repo"),
  44. Tag: r.Form.Get("tag"),
  45. Author: r.Form.Get("author"),
  46. Comment: r.Form.Get("comment"),
  47. Config: c,
  48. MergeConfigs: true,
  49. },
  50. Changes: r.Form["changes"],
  51. }
  52. imgID, err := s.backend.Commit(cname, commitCfg)
  53. if err != nil {
  54. return err
  55. }
  56. return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
  57. ID: string(imgID),
  58. })
  59. }
  60. // Creates an image from Pull or from Import
  61. func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  62. if err := httputils.ParseForm(r); err != nil {
  63. return err
  64. }
  65. var (
  66. image = r.Form.Get("fromImage")
  67. repo = r.Form.Get("repo")
  68. tag = r.Form.Get("tag")
  69. message = r.Form.Get("message")
  70. err error
  71. output = ioutils.NewWriteFlusher(w)
  72. )
  73. defer output.Close()
  74. w.Header().Set("Content-Type", "application/json")
  75. if image != "" { //pull
  76. metaHeaders := map[string][]string{}
  77. for k, v := range r.Header {
  78. if strings.HasPrefix(k, "X-Meta-") {
  79. metaHeaders[k] = v
  80. }
  81. }
  82. authEncoded := r.Header.Get("X-Registry-Auth")
  83. authConfig := &types.AuthConfig{}
  84. if authEncoded != "" {
  85. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  86. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  87. // for a pull it is not an error if no auth was given
  88. // to increase compatibility with the existing api it is defaulting to be empty
  89. authConfig = &types.AuthConfig{}
  90. }
  91. }
  92. err = s.backend.PullImage(ctx, image, tag, metaHeaders, authConfig, output)
  93. } else { //import
  94. src := r.Form.Get("fromSrc")
  95. // 'err' MUST NOT be defined within this block, we need any error
  96. // generated from the download to be available to the output
  97. // stream processing below
  98. err = s.backend.ImportImage(src, repo, tag, message, r.Body, output, r.Form["changes"])
  99. }
  100. if err != nil {
  101. if !output.Flushed() {
  102. return err
  103. }
  104. sf := streamformatter.NewJSONStreamFormatter()
  105. output.Write(sf.FormatError(err))
  106. }
  107. return nil
  108. }
  109. func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  110. metaHeaders := map[string][]string{}
  111. for k, v := range r.Header {
  112. if strings.HasPrefix(k, "X-Meta-") {
  113. metaHeaders[k] = v
  114. }
  115. }
  116. if err := httputils.ParseForm(r); err != nil {
  117. return err
  118. }
  119. authConfig := &types.AuthConfig{}
  120. authEncoded := r.Header.Get("X-Registry-Auth")
  121. if authEncoded != "" {
  122. // the new format is to handle the authConfig as a header
  123. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  124. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  125. // to increase compatibility to existing api it is defaulting to be empty
  126. authConfig = &types.AuthConfig{}
  127. }
  128. } else {
  129. // the old format is supported for compatibility if there was no authConfig header
  130. if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
  131. return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
  132. }
  133. }
  134. image := vars["name"]
  135. tag := r.Form.Get("tag")
  136. output := ioutils.NewWriteFlusher(w)
  137. defer output.Close()
  138. w.Header().Set("Content-Type", "application/json")
  139. if err := s.backend.PushImage(ctx, image, tag, metaHeaders, authConfig, output); err != nil {
  140. if !output.Flushed() {
  141. return err
  142. }
  143. sf := streamformatter.NewJSONStreamFormatter()
  144. output.Write(sf.FormatError(err))
  145. }
  146. return nil
  147. }
  148. func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  149. if err := httputils.ParseForm(r); err != nil {
  150. return err
  151. }
  152. w.Header().Set("Content-Type", "application/x-tar")
  153. output := ioutils.NewWriteFlusher(w)
  154. defer output.Close()
  155. var names []string
  156. if name, ok := vars["name"]; ok {
  157. names = []string{name}
  158. } else {
  159. names = r.Form["names"]
  160. }
  161. if err := s.backend.ExportImage(names, output); err != nil {
  162. if !output.Flushed() {
  163. return err
  164. }
  165. sf := streamformatter.NewJSONStreamFormatter()
  166. output.Write(sf.FormatError(err))
  167. }
  168. return nil
  169. }
  170. func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  171. if err := httputils.ParseForm(r); err != nil {
  172. return err
  173. }
  174. quiet := httputils.BoolValueOrDefault(r, "quiet", true)
  175. if !quiet {
  176. w.Header().Set("Content-Type", "application/json")
  177. output := ioutils.NewWriteFlusher(w)
  178. defer output.Close()
  179. if err := s.backend.LoadImage(r.Body, output, quiet); err != nil {
  180. output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err))
  181. }
  182. return nil
  183. }
  184. return s.backend.LoadImage(r.Body, w, quiet)
  185. }
  186. func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  187. if err := httputils.ParseForm(r); err != nil {
  188. return err
  189. }
  190. name := vars["name"]
  191. if strings.TrimSpace(name) == "" {
  192. return fmt.Errorf("image name cannot be blank")
  193. }
  194. force := httputils.BoolValue(r, "force")
  195. prune := !httputils.BoolValue(r, "noprune")
  196. list, err := s.backend.ImageDelete(name, force, prune)
  197. if err != nil {
  198. return err
  199. }
  200. return httputils.WriteJSON(w, http.StatusOK, list)
  201. }
  202. func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  203. imageInspect, err := s.backend.LookupImage(vars["name"])
  204. if err != nil {
  205. return err
  206. }
  207. return httputils.WriteJSON(w, http.StatusOK, imageInspect)
  208. }
  209. func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  210. if err := httputils.ParseForm(r); err != nil {
  211. return err
  212. }
  213. // FIXME: The filter parameter could just be a match filter
  214. images, err := s.backend.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
  215. if err != nil {
  216. return err
  217. }
  218. return httputils.WriteJSON(w, http.StatusOK, images)
  219. }
  220. func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  221. name := vars["name"]
  222. history, err := s.backend.ImageHistory(name)
  223. if err != nil {
  224. return err
  225. }
  226. return httputils.WriteJSON(w, http.StatusOK, history)
  227. }
  228. func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  229. if err := httputils.ParseForm(r); err != nil {
  230. return err
  231. }
  232. if err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil {
  233. return err
  234. }
  235. w.WriteHeader(http.StatusCreated)
  236. return nil
  237. }
  238. func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  239. if err := httputils.ParseForm(r); err != nil {
  240. return err
  241. }
  242. var (
  243. config *types.AuthConfig
  244. authEncoded = r.Header.Get("X-Registry-Auth")
  245. headers = map[string][]string{}
  246. )
  247. if authEncoded != "" {
  248. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  249. if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
  250. // for a search it is not an error if no auth was given
  251. // to increase compatibility with the existing api it is defaulting to be empty
  252. config = &types.AuthConfig{}
  253. }
  254. }
  255. for k, v := range r.Header {
  256. if strings.HasPrefix(k, "X-Meta-") {
  257. headers[k] = v
  258. }
  259. }
  260. limit := registry.DefaultSearchLimit
  261. if r.Form.Get("limit") != "" {
  262. limitValue, err := strconv.Atoi(r.Form.Get("limit"))
  263. if err != nil {
  264. return err
  265. }
  266. limit = limitValue
  267. }
  268. query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), limit, config, headers)
  269. if err != nil {
  270. return err
  271. }
  272. return httputils.WriteJSON(w, http.StatusOK, query.Results)
  273. }