image_routes.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. package image
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "io"
  6. "net/http"
  7. "runtime"
  8. "strconv"
  9. "strings"
  10. "github.com/docker/docker/api/server/httputils"
  11. "github.com/docker/docker/api/types"
  12. "github.com/docker/docker/api/types/backend"
  13. "github.com/docker/docker/api/types/container"
  14. "github.com/docker/docker/api/types/filters"
  15. "github.com/docker/docker/api/types/versions"
  16. "github.com/docker/docker/pkg/ioutils"
  17. "github.com/docker/docker/pkg/streamformatter"
  18. "github.com/docker/docker/pkg/system"
  19. "github.com/docker/docker/registry"
  20. "github.com/pkg/errors"
  21. "golang.org/x/net/context"
  22. )
  23. func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  24. if err := httputils.ParseForm(r); err != nil {
  25. return err
  26. }
  27. if err := httputils.CheckForJSON(r); err != nil {
  28. return err
  29. }
  30. cname := r.Form.Get("container")
  31. pause := httputils.BoolValue(r, "pause")
  32. version := httputils.VersionFromContext(ctx)
  33. if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") {
  34. pause = true
  35. }
  36. c, _, _, err := s.decoder.DecodeConfig(r.Body)
  37. if err != nil && err != io.EOF { //Do not fail if body is empty.
  38. return err
  39. }
  40. if c == nil {
  41. c = &container.Config{}
  42. }
  43. commitCfg := &backend.ContainerCommitConfig{
  44. ContainerCommitConfig: types.ContainerCommitConfig{
  45. Pause: pause,
  46. Repo: r.Form.Get("repo"),
  47. Tag: r.Form.Get("tag"),
  48. Author: r.Form.Get("author"),
  49. Comment: r.Form.Get("comment"),
  50. Config: c,
  51. MergeConfigs: true,
  52. },
  53. Changes: r.Form["changes"],
  54. }
  55. imgID, err := s.backend.Commit(cname, commitCfg)
  56. if err != nil {
  57. return err
  58. }
  59. return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID})
  60. }
  61. // Creates an image from Pull or from Import
  62. func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  63. if err := httputils.ParseForm(r); err != nil {
  64. return err
  65. }
  66. var (
  67. image = r.Form.Get("fromImage")
  68. repo = r.Form.Get("repo")
  69. tag = r.Form.Get("tag")
  70. message = r.Form.Get("message")
  71. err error
  72. output = ioutils.NewWriteFlusher(w)
  73. )
  74. defer output.Close()
  75. // TODO @jhowardmsft LCOW Support: Eventually we will need an API change
  76. // so that platform comes from (for example) r.Form.Get("platform"). For
  77. // the initial implementation, we assume that the platform is the
  78. // runtime OS of the host. It will also need a validation function such
  79. // as below which should be called after getting it from the API.
  80. //
  81. // Ensures the requested platform is valid and normalized
  82. //func validatePlatform(req string) (string, error) {
  83. // req = strings.ToLower(req)
  84. // if req == "" {
  85. // req = runtime.GOOS // default to host platform
  86. // }
  87. // valid := []string{runtime.GOOS}
  88. //
  89. // if system.LCOWSupported() {
  90. // valid = append(valid, "linux")
  91. // }
  92. //
  93. // for _, item := range valid {
  94. // if req == item {
  95. // return req, nil
  96. // }
  97. // }
  98. // return "", fmt.Errorf("invalid platform requested: %s", req)
  99. //}
  100. //
  101. // And in the call-site:
  102. // if platform, err = validatePlatform(platform); err != nil {
  103. // return err
  104. // }
  105. platform := runtime.GOOS
  106. if system.LCOWSupported() {
  107. platform = "linux"
  108. }
  109. w.Header().Set("Content-Type", "application/json")
  110. if image != "" { //pull
  111. metaHeaders := map[string][]string{}
  112. for k, v := range r.Header {
  113. if strings.HasPrefix(k, "X-Meta-") {
  114. metaHeaders[k] = v
  115. }
  116. }
  117. authEncoded := r.Header.Get("X-Registry-Auth")
  118. authConfig := &types.AuthConfig{}
  119. if authEncoded != "" {
  120. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  121. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  122. // for a pull it is not an error if no auth was given
  123. // to increase compatibility with the existing api it is defaulting to be empty
  124. authConfig = &types.AuthConfig{}
  125. }
  126. }
  127. err = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output)
  128. } else { //import
  129. src := r.Form.Get("fromSrc")
  130. // 'err' MUST NOT be defined within this block, we need any error
  131. // generated from the download to be available to the output
  132. // stream processing below
  133. err = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"])
  134. }
  135. if err != nil {
  136. if !output.Flushed() {
  137. return err
  138. }
  139. output.Write(streamformatter.FormatError(err))
  140. }
  141. return nil
  142. }
  143. type validationError struct {
  144. cause error
  145. }
  146. func (e validationError) Error() string {
  147. return e.cause.Error()
  148. }
  149. func (e validationError) Cause() error {
  150. return e.cause
  151. }
  152. func (validationError) InvalidParameter() {}
  153. func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  154. metaHeaders := map[string][]string{}
  155. for k, v := range r.Header {
  156. if strings.HasPrefix(k, "X-Meta-") {
  157. metaHeaders[k] = v
  158. }
  159. }
  160. if err := httputils.ParseForm(r); err != nil {
  161. return err
  162. }
  163. authConfig := &types.AuthConfig{}
  164. authEncoded := r.Header.Get("X-Registry-Auth")
  165. if authEncoded != "" {
  166. // the new format is to handle the authConfig as a header
  167. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  168. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  169. // to increase compatibility to existing api it is defaulting to be empty
  170. authConfig = &types.AuthConfig{}
  171. }
  172. } else {
  173. // the old format is supported for compatibility if there was no authConfig header
  174. if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
  175. return errors.Wrap(validationError{err}, "Bad parameters and missing X-Registry-Auth")
  176. }
  177. }
  178. image := vars["name"]
  179. tag := r.Form.Get("tag")
  180. output := ioutils.NewWriteFlusher(w)
  181. defer output.Close()
  182. w.Header().Set("Content-Type", "application/json")
  183. if err := s.backend.PushImage(ctx, image, tag, metaHeaders, authConfig, output); err != nil {
  184. if !output.Flushed() {
  185. return err
  186. }
  187. output.Write(streamformatter.FormatError(err))
  188. }
  189. return nil
  190. }
  191. func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  192. if err := httputils.ParseForm(r); err != nil {
  193. return err
  194. }
  195. w.Header().Set("Content-Type", "application/x-tar")
  196. output := ioutils.NewWriteFlusher(w)
  197. defer output.Close()
  198. var names []string
  199. if name, ok := vars["name"]; ok {
  200. names = []string{name}
  201. } else {
  202. names = r.Form["names"]
  203. }
  204. if err := s.backend.ExportImage(names, output); err != nil {
  205. if !output.Flushed() {
  206. return err
  207. }
  208. output.Write(streamformatter.FormatError(err))
  209. }
  210. return nil
  211. }
  212. func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  213. if err := httputils.ParseForm(r); err != nil {
  214. return err
  215. }
  216. quiet := httputils.BoolValueOrDefault(r, "quiet", true)
  217. w.Header().Set("Content-Type", "application/json")
  218. output := ioutils.NewWriteFlusher(w)
  219. defer output.Close()
  220. if err := s.backend.LoadImage(r.Body, output, quiet); err != nil {
  221. output.Write(streamformatter.FormatError(err))
  222. }
  223. return nil
  224. }
  225. type missingImageError struct{}
  226. func (missingImageError) Error() string {
  227. return "image name cannot be blank"
  228. }
  229. func (missingImageError) InvalidParameter() {}
  230. func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  231. if err := httputils.ParseForm(r); err != nil {
  232. return err
  233. }
  234. name := vars["name"]
  235. if strings.TrimSpace(name) == "" {
  236. return missingImageError{}
  237. }
  238. force := httputils.BoolValue(r, "force")
  239. prune := !httputils.BoolValue(r, "noprune")
  240. list, err := s.backend.ImageDelete(name, force, prune)
  241. if err != nil {
  242. return err
  243. }
  244. return httputils.WriteJSON(w, http.StatusOK, list)
  245. }
  246. func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  247. imageInspect, err := s.backend.LookupImage(vars["name"])
  248. if err != nil {
  249. return err
  250. }
  251. return httputils.WriteJSON(w, http.StatusOK, imageInspect)
  252. }
  253. func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  254. if err := httputils.ParseForm(r); err != nil {
  255. return err
  256. }
  257. imageFilters, err := filters.FromParam(r.Form.Get("filters"))
  258. if err != nil {
  259. return err
  260. }
  261. filterParam := r.Form.Get("filter")
  262. // FIXME(vdemeester) This has been deprecated in 1.13, and is target for removal for v17.12
  263. if filterParam != "" {
  264. imageFilters.Add("reference", filterParam)
  265. }
  266. images, err := s.backend.Images(imageFilters, httputils.BoolValue(r, "all"), false)
  267. if err != nil {
  268. return err
  269. }
  270. return httputils.WriteJSON(w, http.StatusOK, images)
  271. }
  272. func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  273. name := vars["name"]
  274. history, err := s.backend.ImageHistory(name)
  275. if err != nil {
  276. return err
  277. }
  278. return httputils.WriteJSON(w, http.StatusOK, history)
  279. }
  280. func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  281. if err := httputils.ParseForm(r); err != nil {
  282. return err
  283. }
  284. if err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil {
  285. return err
  286. }
  287. w.WriteHeader(http.StatusCreated)
  288. return nil
  289. }
  290. func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  291. if err := httputils.ParseForm(r); err != nil {
  292. return err
  293. }
  294. var (
  295. config *types.AuthConfig
  296. authEncoded = r.Header.Get("X-Registry-Auth")
  297. headers = map[string][]string{}
  298. )
  299. if authEncoded != "" {
  300. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  301. if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
  302. // for a search it is not an error if no auth was given
  303. // to increase compatibility with the existing api it is defaulting to be empty
  304. config = &types.AuthConfig{}
  305. }
  306. }
  307. for k, v := range r.Header {
  308. if strings.HasPrefix(k, "X-Meta-") {
  309. headers[k] = v
  310. }
  311. }
  312. limit := registry.DefaultSearchLimit
  313. if r.Form.Get("limit") != "" {
  314. limitValue, err := strconv.Atoi(r.Form.Get("limit"))
  315. if err != nil {
  316. return err
  317. }
  318. limit = limitValue
  319. }
  320. query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), limit, config, headers)
  321. if err != nil {
  322. return err
  323. }
  324. return httputils.WriteJSON(w, http.StatusOK, query.Results)
  325. }
  326. func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  327. if err := httputils.ParseForm(r); err != nil {
  328. return err
  329. }
  330. pruneFilters, err := filters.FromParam(r.Form.Get("filters"))
  331. if err != nil {
  332. return err
  333. }
  334. pruneReport, err := s.backend.ImagesPrune(ctx, pruneFilters)
  335. if err != nil {
  336. return err
  337. }
  338. return httputils.WriteJSON(w, http.StatusOK, pruneReport)
  339. }