2018-02-05 16:05:59 -05:00
|
|
|
package image // import "github.com/docker/docker/api/server/router/image"
|
2015-07-28 14:35:24 -04:00
|
|
|
|
|
|
|
import (
|
2018-04-19 15:30:59 -07:00
|
|
|
"context"
|
2015-07-28 14:35:24 -04:00
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
2016-06-01 13:38:14 -07:00
|
|
|
"strconv"
|
2015-07-28 14:35:24 -04:00
|
|
|
"strings"
|
|
|
|
|
2018-06-26 15:39:25 +08:00
|
|
|
"github.com/containerd/containerd/platforms"
|
2015-09-23 19:42:08 -04:00
|
|
|
"github.com/docker/docker/api/server/httputils"
|
2016-09-06 11:46:37 -07:00
|
|
|
"github.com/docker/docker/api/types"
|
2016-11-11 15:34:01 +01:00
|
|
|
"github.com/docker/docker/api/types/filters"
|
2016-09-06 11:46:37 -07:00
|
|
|
"github.com/docker/docker/api/types/versions"
|
2018-01-11 14:53:06 -05:00
|
|
|
"github.com/docker/docker/errdefs"
|
2015-07-28 14:35:24 -04:00
|
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
|
|
"github.com/docker/docker/pkg/streamformatter"
|
2017-08-08 12:43:48 -07:00
|
|
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
2017-07-19 10:20:13 -04:00
|
|
|
"github.com/pkg/errors"
|
2015-07-28 14:35:24 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// Creates an image from Pull or from Import
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2017-09-13 12:49:04 -07:00
|
|
|
|
2015-09-23 19:42:08 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2021-12-22 14:07:15 +01:00
|
|
|
image = r.Form.Get("fromImage")
|
|
|
|
repo = r.Form.Get("repo")
|
|
|
|
tag = r.Form.Get("tag")
|
|
|
|
message = r.Form.Get("message")
|
|
|
|
progressErr error
|
|
|
|
output = ioutils.NewWriteFlusher(w)
|
|
|
|
platform *specs.Platform
|
2015-07-28 14:35:24 -04:00
|
|
|
)
|
2015-11-02 16:11:28 -08:00
|
|
|
defer output.Close()
|
2015-07-28 14:35:24 -04:00
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
2017-09-13 12:49:04 -07:00
|
|
|
version := httputils.VersionFromContext(ctx)
|
|
|
|
if versions.GreaterThanOrEqualTo(version, "1.32") {
|
2021-12-22 14:12:00 +01:00
|
|
|
if p := r.FormValue("platform"); p != "" {
|
|
|
|
sp, err := platforms.Parse(p)
|
2018-06-26 15:39:25 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
platform = &sp
|
2017-09-13 12:49:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-27 15:37:30 +01:00
|
|
|
if image != "" { // pull
|
2019-04-08 12:28:43 +08:00
|
|
|
metaHeaders := map[string][]string{}
|
|
|
|
for k, v := range r.Header {
|
|
|
|
if strings.HasPrefix(k, "X-Meta-") {
|
|
|
|
metaHeaders[k] = v
|
2015-11-18 14:20:54 -08:00
|
|
|
}
|
2019-04-08 12:28:43 +08:00
|
|
|
}
|
2016-04-07 14:29:18 -07:00
|
|
|
|
2019-04-08 12:28:43 +08:00
|
|
|
authEncoded := r.Header.Get("X-Registry-Auth")
|
|
|
|
authConfig := &types.AuthConfig{}
|
|
|
|
if authEncoded != "" {
|
|
|
|
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
|
|
|
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
|
|
|
|
// for a pull it is not an error if no auth was given
|
|
|
|
// to increase compatibility with the existing api it is defaulting to be empty
|
|
|
|
authConfig = &types.AuthConfig{}
|
2018-06-26 14:49:33 -07:00
|
|
|
}
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
2021-12-22 14:07:15 +01:00
|
|
|
progressErr = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output)
|
2019-11-27 15:37:30 +01:00
|
|
|
} else { // import
|
2019-04-08 12:28:43 +08:00
|
|
|
src := r.Form.Get("fromSrc")
|
2021-12-22 17:21:27 +01:00
|
|
|
progressErr = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"])
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
2021-12-22 14:07:15 +01:00
|
|
|
if progressErr != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
if !output.Flushed() {
|
2021-12-22 14:07:15 +01:00
|
|
|
return progressErr
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
2021-12-22 14:07:15 +01:00
|
|
|
_, _ = output.Write(streamformatter.FormatError(progressErr))
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2015-07-28 14:35:24 -04:00
|
|
|
metaHeaders := map[string][]string{}
|
|
|
|
for k, v := range r.Header {
|
|
|
|
if strings.HasPrefix(k, "X-Meta-") {
|
|
|
|
metaHeaders[k] = v
|
|
|
|
}
|
|
|
|
}
|
2015-09-23 19:42:08 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
2015-12-11 20:11:42 -08:00
|
|
|
authConfig := &types.AuthConfig{}
|
2015-07-28 14:35:24 -04:00
|
|
|
|
|
|
|
authEncoded := r.Header.Get("X-Registry-Auth")
|
|
|
|
if authEncoded != "" {
|
|
|
|
// the new format is to handle the authConfig as a header
|
|
|
|
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
|
|
|
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
|
|
|
|
// to increase compatibility to existing api it is defaulting to be empty
|
2015-12-11 20:11:42 -08:00
|
|
|
authConfig = &types.AuthConfig{}
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// the old format is supported for compatibility if there was no authConfig header
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
2017-11-28 23:09:37 -05:00
|
|
|
return errors.Wrap(errdefs.InvalidParameter(err), "Bad parameters and missing X-Registry-Auth")
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-07 14:29:18 -07:00
|
|
|
image := vars["name"]
|
2015-11-18 14:20:54 -08:00
|
|
|
tag := r.Form.Get("tag")
|
|
|
|
|
2015-07-28 14:35:24 -04:00
|
|
|
output := ioutils.NewWriteFlusher(w)
|
2015-11-02 16:11:28 -08:00
|
|
|
defer output.Close()
|
2015-07-28 14:35:24 -04:00
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
2016-04-07 14:29:18 -07:00
|
|
|
if err := s.backend.PushImage(ctx, image, tag, metaHeaders, authConfig, output); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
if !output.Flushed() {
|
|
|
|
return err
|
|
|
|
}
|
2019-08-28 17:38:51 +02:00
|
|
|
_, _ = output.Write(streamformatter.FormatError(err))
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2015-09-23 19:42:08 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/x-tar")
|
|
|
|
|
|
|
|
output := ioutils.NewWriteFlusher(w)
|
2015-11-02 16:11:28 -08:00
|
|
|
defer output.Close()
|
2015-07-28 14:35:24 -04:00
|
|
|
var names []string
|
|
|
|
if name, ok := vars["name"]; ok {
|
|
|
|
names = []string{name}
|
|
|
|
} else {
|
|
|
|
names = r.Form["names"]
|
|
|
|
}
|
|
|
|
|
2016-02-22 10:53:47 -08:00
|
|
|
if err := s.backend.ExportImage(names, output); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
if !output.Flushed() {
|
|
|
|
return err
|
|
|
|
}
|
2019-08-28 17:38:51 +02:00
|
|
|
_, _ = output.Write(streamformatter.FormatError(err))
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-01-27 17:09:42 -05:00
|
|
|
func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2016-02-03 21:31:47 -05:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
quiet := httputils.BoolValueOrDefault(r, "quiet", true)
|
2016-04-12 22:45:42 -04:00
|
|
|
|
2016-08-09 17:27:32 -07:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2016-04-12 22:45:42 -04:00
|
|
|
|
2016-08-09 17:27:32 -07:00
|
|
|
output := ioutils.NewWriteFlusher(w)
|
|
|
|
defer output.Close()
|
|
|
|
if err := s.backend.LoadImage(r.Body, output, quiet); err != nil {
|
2019-08-28 17:38:51 +02:00
|
|
|
_, _ = output.Write(streamformatter.FormatError(err))
|
2016-04-12 22:45:42 -04:00
|
|
|
}
|
2016-08-09 17:27:32 -07:00
|
|
|
return nil
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2017-07-19 10:20:13 -04:00
|
|
|
type missingImageError struct{}
|
|
|
|
|
|
|
|
func (missingImageError) Error() string {
|
|
|
|
return "image name cannot be blank"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (missingImageError) InvalidParameter() {}
|
|
|
|
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2015-09-23 19:42:08 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
name := vars["name"]
|
2015-08-15 00:30:25 -07:00
|
|
|
|
2015-11-03 17:19:18 -08:00
|
|
|
if strings.TrimSpace(name) == "" {
|
2017-07-19 10:20:13 -04:00
|
|
|
return missingImageError{}
|
2015-08-15 00:30:25 -07:00
|
|
|
}
|
|
|
|
|
2015-09-23 19:42:08 -04:00
|
|
|
force := httputils.BoolValue(r, "force")
|
|
|
|
prune := !httputils.BoolValue(r, "noprune")
|
2015-07-28 14:35:24 -04:00
|
|
|
|
2016-02-22 10:53:47 -08:00
|
|
|
list, err := s.backend.ImageDelete(name, force, prune)
|
2015-07-28 14:35:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-23 19:42:08 -04:00
|
|
|
return httputils.WriteJSON(w, http.StatusOK, list)
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2016-02-22 10:53:47 -08:00
|
|
|
imageInspect, err := s.backend.LookupImage(vars["name"])
|
2015-07-28 14:35:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-23 19:42:08 -04:00
|
|
|
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2015-09-23 19:42:08 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-09-26 13:59:45 +02:00
|
|
|
imageFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
2016-11-11 15:34:01 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-02-10 17:17:07 +01:00
|
|
|
version := httputils.VersionFromContext(ctx)
|
|
|
|
if versions.LessThan(version, "1.41") {
|
2021-06-22 19:09:58 +02:00
|
|
|
// NOTE: filter is a shell glob string applied to repository names.
|
2020-02-10 17:17:07 +01:00
|
|
|
filterParam := r.Form.Get("filter")
|
|
|
|
if filterParam != "" {
|
|
|
|
imageFilters.Add("reference", filterParam)
|
|
|
|
}
|
2016-11-11 15:34:01 +01:00
|
|
|
}
|
|
|
|
|
2021-06-28 17:30:53 +02:00
|
|
|
var sharedSize bool
|
|
|
|
if versions.GreaterThanOrEqualTo(version, "1.42") {
|
|
|
|
// NOTE: Support for the "shared-size" parameter was added in API 1.42.
|
|
|
|
sharedSize = httputils.BoolValue(r, "shared-size")
|
|
|
|
}
|
|
|
|
|
2021-06-22 19:09:58 +02:00
|
|
|
images, err := s.backend.Images(ctx, types.ImageListOptions{
|
2021-06-28 17:30:53 +02:00
|
|
|
All: httputils.BoolValue(r, "all"),
|
|
|
|
Filters: imageFilters,
|
|
|
|
SharedSize: sharedSize,
|
2021-06-22 19:09:58 +02:00
|
|
|
})
|
2015-07-28 14:35:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-23 19:42:08 -04:00
|
|
|
return httputils.WriteJSON(w, http.StatusOK, images)
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2015-07-28 14:35:24 -04:00
|
|
|
name := vars["name"]
|
2016-02-22 10:53:47 -08:00
|
|
|
history, err := s.backend.ImageHistory(name)
|
2015-07-28 14:35:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-23 19:42:08 -04:00
|
|
|
return httputils.WriteJSON(w, http.StatusOK, history)
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2015-09-23 19:42:08 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
2018-02-09 18:24:57 -05:00
|
|
|
if _, err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-12-30 18:20:41 +01:00
|
|
|
func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2015-09-23 19:42:08 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2015-07-28 14:35:24 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
var (
|
2015-12-11 20:11:42 -08:00
|
|
|
config *types.AuthConfig
|
2015-07-28 14:35:24 -04:00
|
|
|
authEncoded = r.Header.Get("X-Registry-Auth")
|
|
|
|
headers = map[string][]string{}
|
|
|
|
)
|
|
|
|
|
|
|
|
if authEncoded != "" {
|
|
|
|
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
|
|
|
if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
|
|
|
|
// for a search it is not an error if no auth was given
|
|
|
|
// to increase compatibility with the existing api it is defaulting to be empty
|
2015-12-11 20:11:42 -08:00
|
|
|
config = &types.AuthConfig{}
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for k, v := range r.Header {
|
|
|
|
if strings.HasPrefix(k, "X-Meta-") {
|
|
|
|
headers[k] = v
|
|
|
|
}
|
|
|
|
}
|
2022-03-02 13:29:47 +01:00
|
|
|
|
|
|
|
var limit int
|
2016-06-01 13:38:14 -07:00
|
|
|
if r.Form.Get("limit") != "" {
|
2022-03-02 13:29:47 +01:00
|
|
|
var err error
|
|
|
|
limit, err = strconv.Atoi(r.Form.Get("limit"))
|
|
|
|
if err != nil || limit < 0 {
|
|
|
|
return errdefs.InvalidParameter(errors.Wrap(err, "invalid limit specified"))
|
2016-06-01 13:38:14 -07:00
|
|
|
}
|
|
|
|
}
|
2022-03-02 16:03:38 +01:00
|
|
|
searchFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
query, err := s.backend.SearchRegistryForImages(ctx, searchFilters, r.Form.Get("term"), limit, config, headers)
|
2015-07-28 14:35:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-09-23 19:42:08 -04:00
|
|
|
return httputils.WriteJSON(w, http.StatusOK, query.Results)
|
2015-07-28 14:35:24 -04:00
|
|
|
}
|
2016-08-23 16:25:43 -07:00
|
|
|
|
|
|
|
func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-09-26 13:59:45 +02:00
|
|
|
pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
2016-11-16 21:46:37 -08:00
|
|
|
if err != nil {
|
2016-08-23 16:25:43 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-11 12:52:33 -07:00
|
|
|
pruneReport, err := s.backend.ImagesPrune(ctx, pruneFilters)
|
2016-08-23 16:25:43 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, pruneReport)
|
|
|
|
}
|