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