image_routes.go 11 KB

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