github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/api/server/router/image/image_routes.go (about)

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