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