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