image_routes.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. package image // import "github.com/docker/docker/api/server/router/image"
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "net/url"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/containerd/containerd/platforms"
  11. "github.com/docker/distribution/reference"
  12. "github.com/docker/docker/api/server/httputils"
  13. "github.com/docker/docker/api/types"
  14. "github.com/docker/docker/api/types/filters"
  15. opts "github.com/docker/docker/api/types/image"
  16. "github.com/docker/docker/api/types/registry"
  17. "github.com/docker/docker/api/types/versions"
  18. "github.com/docker/docker/builder/remotecontext"
  19. "github.com/docker/docker/dockerversion"
  20. "github.com/docker/docker/errdefs"
  21. "github.com/docker/docker/image"
  22. "github.com/docker/docker/pkg/ioutils"
  23. "github.com/docker/docker/pkg/progress"
  24. "github.com/docker/docker/pkg/streamformatter"
  25. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  26. "github.com/pkg/errors"
  27. )
  28. // Creates an image from Pull or from Import
  29. func (ir *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  30. if err := httputils.ParseForm(r); err != nil {
  31. return err
  32. }
  33. var (
  34. img = r.Form.Get("fromImage")
  35. repo = r.Form.Get("repo")
  36. tag = r.Form.Get("tag")
  37. comment = r.Form.Get("message")
  38. progressErr error
  39. output = ioutils.NewWriteFlusher(w)
  40. platform *ocispec.Platform
  41. )
  42. defer output.Close()
  43. w.Header().Set("Content-Type", "application/json")
  44. version := httputils.VersionFromContext(ctx)
  45. if versions.GreaterThanOrEqualTo(version, "1.32") {
  46. if p := r.FormValue("platform"); p != "" {
  47. sp, err := platforms.Parse(p)
  48. if err != nil {
  49. return err
  50. }
  51. platform = &sp
  52. }
  53. }
  54. if img != "" { // pull
  55. metaHeaders := map[string][]string{}
  56. for k, v := range r.Header {
  57. if strings.HasPrefix(k, "X-Meta-") {
  58. metaHeaders[k] = v
  59. }
  60. }
  61. // For a pull it is not an error if no auth was given. Ignore invalid
  62. // AuthConfig to increase compatibility with the existing API.
  63. authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
  64. progressErr = ir.backend.PullImage(ctx, img, tag, platform, metaHeaders, authConfig, output)
  65. } else { // import
  66. src := r.Form.Get("fromSrc")
  67. tagRef, err := httputils.RepoTagReference(repo, tag)
  68. if err != nil {
  69. return errdefs.InvalidParameter(err)
  70. }
  71. if len(comment) == 0 {
  72. comment = "Imported from " + src
  73. }
  74. var layerReader io.ReadCloser
  75. defer r.Body.Close()
  76. if src == "-" {
  77. layerReader = r.Body
  78. } else {
  79. if len(strings.Split(src, "://")) == 1 {
  80. src = "http://" + src
  81. }
  82. u, err := url.Parse(src)
  83. if err != nil {
  84. return errdefs.InvalidParameter(err)
  85. }
  86. resp, err := remotecontext.GetWithStatusError(u.String())
  87. if err != nil {
  88. return err
  89. }
  90. output.Write(streamformatter.FormatStatus("", "Downloading from %s", u))
  91. progressOutput := streamformatter.NewJSONProgressOutput(output, true)
  92. layerReader = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing")
  93. defer layerReader.Close()
  94. }
  95. var id image.ID
  96. id, progressErr = ir.backend.ImportImage(ctx, tagRef, platform, comment, layerReader, r.Form["changes"])
  97. if progressErr == nil {
  98. output.Write(streamformatter.FormatStatus("", id.String()))
  99. }
  100. }
  101. if progressErr != nil {
  102. if !output.Flushed() {
  103. return progressErr
  104. }
  105. _, _ = output.Write(streamformatter.FormatError(progressErr))
  106. }
  107. return nil
  108. }
  109. func (ir *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. var authConfig *registry.AuthConfig
  120. if authEncoded := r.Header.Get(registry.AuthHeader); authEncoded != "" {
  121. // the new format is to handle the authConfig as a header. Ignore invalid
  122. // AuthConfig to increase compatibility with the existing API.
  123. authConfig, _ = registry.DecodeAuthConfig(authEncoded)
  124. } else {
  125. // the old format is supported for compatibility if there was no authConfig header
  126. var err error
  127. authConfig, err = registry.DecodeAuthConfigBody(r.Body)
  128. if err != nil {
  129. return errors.Wrap(err, "bad parameters and missing X-Registry-Auth")
  130. }
  131. }
  132. output := ioutils.NewWriteFlusher(w)
  133. defer output.Close()
  134. w.Header().Set("Content-Type", "application/json")
  135. img := vars["name"]
  136. tag := r.Form.Get("tag")
  137. var ref reference.Named
  138. // Tag is empty only in case ImagePushOptions.All is true.
  139. if tag != "" {
  140. r, err := httputils.RepoTagReference(img, tag)
  141. if err != nil {
  142. return errdefs.InvalidParameter(err)
  143. }
  144. ref = r
  145. } else {
  146. r, err := reference.ParseNormalizedNamed(img)
  147. if err != nil {
  148. return errdefs.InvalidParameter(err)
  149. }
  150. ref = r
  151. }
  152. if err := ir.backend.PushImage(ctx, ref, metaHeaders, authConfig, output); err != nil {
  153. if !output.Flushed() {
  154. return err
  155. }
  156. _, _ = output.Write(streamformatter.FormatError(err))
  157. }
  158. return nil
  159. }
  160. func (ir *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  161. if err := httputils.ParseForm(r); err != nil {
  162. return err
  163. }
  164. w.Header().Set("Content-Type", "application/x-tar")
  165. output := ioutils.NewWriteFlusher(w)
  166. defer output.Close()
  167. var names []string
  168. if name, ok := vars["name"]; ok {
  169. names = []string{name}
  170. } else {
  171. names = r.Form["names"]
  172. }
  173. if err := ir.backend.ExportImage(ctx, names, output); err != nil {
  174. if !output.Flushed() {
  175. return err
  176. }
  177. _, _ = output.Write(streamformatter.FormatError(err))
  178. }
  179. return nil
  180. }
  181. func (ir *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  182. if err := httputils.ParseForm(r); err != nil {
  183. return err
  184. }
  185. quiet := httputils.BoolValueOrDefault(r, "quiet", true)
  186. w.Header().Set("Content-Type", "application/json")
  187. output := ioutils.NewWriteFlusher(w)
  188. defer output.Close()
  189. if err := ir.backend.LoadImage(ctx, r.Body, output, quiet); err != nil {
  190. _, _ = output.Write(streamformatter.FormatError(err))
  191. }
  192. return nil
  193. }
  194. type missingImageError struct{}
  195. func (missingImageError) Error() string {
  196. return "image name cannot be blank"
  197. }
  198. func (missingImageError) InvalidParameter() {}
  199. func (ir *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  200. if err := httputils.ParseForm(r); err != nil {
  201. return err
  202. }
  203. name := vars["name"]
  204. if strings.TrimSpace(name) == "" {
  205. return missingImageError{}
  206. }
  207. force := httputils.BoolValue(r, "force")
  208. prune := !httputils.BoolValue(r, "noprune")
  209. list, err := ir.backend.ImageDelete(ctx, name, force, prune)
  210. if err != nil {
  211. return err
  212. }
  213. return httputils.WriteJSON(w, http.StatusOK, list)
  214. }
  215. func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  216. img, err := ir.backend.GetImage(ctx, vars["name"], opts.GetImageOpts{Details: true})
  217. if err != nil {
  218. return err
  219. }
  220. imageInspect, err := ir.toImageInspect(img)
  221. if err != nil {
  222. return err
  223. }
  224. version := httputils.VersionFromContext(ctx)
  225. if versions.LessThan(version, "1.44") {
  226. imageInspect.VirtualSize = imageInspect.Size //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
  227. }
  228. return httputils.WriteJSON(w, http.StatusOK, imageInspect)
  229. }
  230. func (ir *imageRouter) toImageInspect(img *image.Image) (*types.ImageInspect, error) {
  231. var repoTags, repoDigests []string
  232. for _, ref := range img.Details.References {
  233. switch ref.(type) {
  234. case reference.NamedTagged:
  235. repoTags = append(repoTags, reference.FamiliarString(ref))
  236. case reference.Canonical:
  237. repoDigests = append(repoDigests, reference.FamiliarString(ref))
  238. }
  239. }
  240. comment := img.Comment
  241. if len(comment) == 0 && len(img.History) > 0 {
  242. comment = img.History[len(img.History)-1].Comment
  243. }
  244. // Make sure we output empty arrays instead of nil.
  245. if repoTags == nil {
  246. repoTags = []string{}
  247. }
  248. if repoDigests == nil {
  249. repoDigests = []string{}
  250. }
  251. var created string
  252. if img.Created != nil {
  253. created = img.Created.Format(time.RFC3339Nano)
  254. }
  255. return &types.ImageInspect{
  256. ID: img.ID().String(),
  257. RepoTags: repoTags,
  258. RepoDigests: repoDigests,
  259. Parent: img.Parent.String(),
  260. Comment: comment,
  261. Created: created,
  262. Container: img.Container,
  263. ContainerConfig: &img.ContainerConfig,
  264. DockerVersion: img.DockerVersion,
  265. Author: img.Author,
  266. Config: img.Config,
  267. Architecture: img.Architecture,
  268. Variant: img.Variant,
  269. Os: img.OperatingSystem(),
  270. OsVersion: img.OSVersion,
  271. Size: img.Details.Size,
  272. GraphDriver: types.GraphDriverData{
  273. Name: img.Details.Driver,
  274. Data: img.Details.Metadata,
  275. },
  276. RootFS: rootFSToAPIType(img.RootFS),
  277. Metadata: types.ImageMetadata{
  278. LastTagTime: img.Details.LastUpdated,
  279. },
  280. }, nil
  281. }
  282. func rootFSToAPIType(rootfs *image.RootFS) types.RootFS {
  283. var layers []string
  284. for _, l := range rootfs.DiffIDs {
  285. layers = append(layers, l.String())
  286. }
  287. return types.RootFS{
  288. Type: rootfs.Type,
  289. Layers: layers,
  290. }
  291. }
  292. func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  293. if err := httputils.ParseForm(r); err != nil {
  294. return err
  295. }
  296. imageFilters, err := filters.FromJSON(r.Form.Get("filters"))
  297. if err != nil {
  298. return err
  299. }
  300. version := httputils.VersionFromContext(ctx)
  301. if versions.LessThan(version, "1.41") {
  302. // NOTE: filter is a shell glob string applied to repository names.
  303. filterParam := r.Form.Get("filter")
  304. if filterParam != "" {
  305. imageFilters.Add("reference", filterParam)
  306. }
  307. }
  308. var sharedSize bool
  309. if versions.GreaterThanOrEqualTo(version, "1.42") {
  310. // NOTE: Support for the "shared-size" parameter was added in API 1.42.
  311. sharedSize = httputils.BoolValue(r, "shared-size")
  312. }
  313. images, err := ir.backend.Images(ctx, types.ImageListOptions{
  314. All: httputils.BoolValue(r, "all"),
  315. Filters: imageFilters,
  316. SharedSize: sharedSize,
  317. })
  318. if err != nil {
  319. return err
  320. }
  321. useNone := versions.LessThan(version, "1.43")
  322. withVirtualSize := versions.LessThan(version, "1.44")
  323. for _, img := range images {
  324. if useNone {
  325. if len(img.RepoTags) == 0 && len(img.RepoDigests) == 0 {
  326. img.RepoTags = append(img.RepoTags, "<none>:<none>")
  327. img.RepoDigests = append(img.RepoDigests, "<none>@<none>")
  328. }
  329. } else {
  330. if img.RepoTags == nil {
  331. img.RepoTags = []string{}
  332. }
  333. if img.RepoDigests == nil {
  334. img.RepoDigests = []string{}
  335. }
  336. }
  337. if withVirtualSize {
  338. img.VirtualSize = img.Size //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
  339. }
  340. }
  341. return httputils.WriteJSON(w, http.StatusOK, images)
  342. }
  343. func (ir *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  344. history, err := ir.backend.ImageHistory(ctx, vars["name"])
  345. if err != nil {
  346. return err
  347. }
  348. return httputils.WriteJSON(w, http.StatusOK, history)
  349. }
  350. func (ir *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  351. if err := httputils.ParseForm(r); err != nil {
  352. return err
  353. }
  354. ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag"))
  355. if ref == nil || err != nil {
  356. return errdefs.InvalidParameter(err)
  357. }
  358. img, err := ir.backend.GetImage(ctx, vars["name"], opts.GetImageOpts{})
  359. if err != nil {
  360. return errdefs.NotFound(err)
  361. }
  362. if err := ir.backend.TagImage(ctx, img.ID(), ref); err != nil {
  363. return err
  364. }
  365. w.WriteHeader(http.StatusCreated)
  366. return nil
  367. }
  368. func (ir *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  369. if err := httputils.ParseForm(r); err != nil {
  370. return err
  371. }
  372. var limit int
  373. if r.Form.Get("limit") != "" {
  374. var err error
  375. limit, err = strconv.Atoi(r.Form.Get("limit"))
  376. if err != nil || limit < 0 {
  377. return errdefs.InvalidParameter(errors.Wrap(err, "invalid limit specified"))
  378. }
  379. }
  380. searchFilters, err := filters.FromJSON(r.Form.Get("filters"))
  381. if err != nil {
  382. return err
  383. }
  384. // For a search it is not an error if no auth was given. Ignore invalid
  385. // AuthConfig to increase compatibility with the existing API.
  386. authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
  387. headers := http.Header{}
  388. for k, v := range r.Header {
  389. k = http.CanonicalHeaderKey(k)
  390. if strings.HasPrefix(k, "X-Meta-") {
  391. headers[k] = v
  392. }
  393. }
  394. headers.Set("User-Agent", dockerversion.DockerUserAgent(ctx))
  395. res, err := ir.searcher.Search(ctx, searchFilters, r.Form.Get("term"), limit, authConfig, headers)
  396. if err != nil {
  397. return err
  398. }
  399. return httputils.WriteJSON(w, http.StatusOK, res)
  400. }
  401. func (ir *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  402. if err := httputils.ParseForm(r); err != nil {
  403. return err
  404. }
  405. pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
  406. if err != nil {
  407. return err
  408. }
  409. pruneReport, err := ir.backend.ImagesPrune(ctx, pruneFilters)
  410. if err != nil {
  411. return err
  412. }
  413. return httputils.WriteJSON(w, http.StatusOK, pruneReport)
  414. }