github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/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  		filterParam := r.Form.Get("filter")
   233  		if filterParam != "" {
   234  			imageFilters.Add("reference", filterParam)
   235  		}
   236  	}
   237  
   238  	images, err := s.backend.Images(imageFilters, httputils.BoolValue(r, "all"), false)
   239  	if err != nil {
   240  		return err
   241  	}
   242  
   243  	return httputils.WriteJSON(w, http.StatusOK, images)
   244  }
   245  
   246  func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   247  	name := vars["name"]
   248  	history, err := s.backend.ImageHistory(name)
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	return httputils.WriteJSON(w, http.StatusOK, history)
   254  }
   255  
   256  func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   257  	if err := httputils.ParseForm(r); err != nil {
   258  		return err
   259  	}
   260  	if _, err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil {
   261  		return err
   262  	}
   263  	w.WriteHeader(http.StatusCreated)
   264  	return nil
   265  }
   266  
   267  func (s *imageRouter) getImagesSearch(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  	var (
   272  		config      *types.AuthConfig
   273  		authEncoded = r.Header.Get("X-Registry-Auth")
   274  		headers     = map[string][]string{}
   275  	)
   276  
   277  	if authEncoded != "" {
   278  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   279  		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
   280  			// for a search it is not an error if no auth was given
   281  			// to increase compatibility with the existing api it is defaulting to be empty
   282  			config = &types.AuthConfig{}
   283  		}
   284  	}
   285  	for k, v := range r.Header {
   286  		if strings.HasPrefix(k, "X-Meta-") {
   287  			headers[k] = v
   288  		}
   289  	}
   290  	limit := registry.DefaultSearchLimit
   291  	if r.Form.Get("limit") != "" {
   292  		limitValue, err := strconv.Atoi(r.Form.Get("limit"))
   293  		if err != nil {
   294  			return err
   295  		}
   296  		limit = limitValue
   297  	}
   298  	query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), limit, config, headers)
   299  	if err != nil {
   300  		return err
   301  	}
   302  	return httputils.WriteJSON(w, http.StatusOK, query.Results)
   303  }
   304  
   305  func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   306  	if err := httputils.ParseForm(r); err != nil {
   307  		return err
   308  	}
   309  
   310  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   311  	if err != nil {
   312  		return err
   313  	}
   314  
   315  	pruneReport, err := s.backend.ImagesPrune(ctx, pruneFilters)
   316  	if err != nil {
   317  		return err
   318  	}
   319  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   320  }