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