image_routes.go 9.3 KB

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