github.com/cookieai-jar/moby@v17.12.1-ce-rc2+incompatible/api/server/router/image/image_routes.go (about)

     1  package image
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/docker/docker/api/server/httputils"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/backend"
    16  	"github.com/docker/docker/api/types/container"
    17  	"github.com/docker/docker/api/types/filters"
    18  	"github.com/docker/docker/api/types/versions"
    19  	"github.com/docker/docker/pkg/ioutils"
    20  	"github.com/docker/docker/pkg/streamformatter"
    21  	"github.com/docker/docker/pkg/system"
    22  	"github.com/docker/docker/registry"
    23  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    24  	"github.com/pkg/errors"
    25  	"golang.org/x/net/context"
    26  )
    27  
    28  func (s *imageRouter) postCommit(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  	if err := httputils.CheckForJSON(r); err != nil {
    34  		return err
    35  	}
    36  
    37  	cname := r.Form.Get("container")
    38  
    39  	pause := httputils.BoolValue(r, "pause")
    40  	version := httputils.VersionFromContext(ctx)
    41  	if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") {
    42  		pause = true
    43  	}
    44  
    45  	c, _, _, err := s.decoder.DecodeConfig(r.Body)
    46  	if err != nil && err != io.EOF { //Do not fail if body is empty.
    47  		return err
    48  	}
    49  	if c == nil {
    50  		c = &container.Config{}
    51  	}
    52  
    53  	commitCfg := &backend.ContainerCommitConfig{
    54  		ContainerCommitConfig: types.ContainerCommitConfig{
    55  			Pause:        pause,
    56  			Repo:         r.Form.Get("repo"),
    57  			Tag:          r.Form.Get("tag"),
    58  			Author:       r.Form.Get("author"),
    59  			Comment:      r.Form.Get("comment"),
    60  			Config:       c,
    61  			MergeConfigs: true,
    62  		},
    63  		Changes: r.Form["changes"],
    64  	}
    65  
    66  	imgID, err := s.backend.Commit(cname, commitCfg)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID})
    72  }
    73  
    74  // Creates an image from Pull or from Import
    75  func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    76  
    77  	if err := httputils.ParseForm(r); err != nil {
    78  		return err
    79  	}
    80  
    81  	var (
    82  		image    = r.Form.Get("fromImage")
    83  		repo     = r.Form.Get("repo")
    84  		tag      = r.Form.Get("tag")
    85  		message  = r.Form.Get("message")
    86  		err      error
    87  		output   = ioutils.NewWriteFlusher(w)
    88  		platform = &specs.Platform{}
    89  	)
    90  	defer output.Close()
    91  
    92  	w.Header().Set("Content-Type", "application/json")
    93  
    94  	version := httputils.VersionFromContext(ctx)
    95  	if versions.GreaterThanOrEqualTo(version, "1.32") {
    96  		// TODO @jhowardmsft. The following environment variable is an interim
    97  		// measure to allow the daemon to have a default platform if omitted by
    98  		// the client. This allows LCOW and WCOW to work with a down-level CLI
    99  		// for a short period of time, as the CLI changes can't be merged
   100  		// until after the daemon changes have been merged. Once the CLI is
   101  		// updated, this can be removed. PR for CLI is currently in
   102  		// https://github.com/docker/cli/pull/474.
   103  		apiPlatform := r.FormValue("platform")
   104  		if system.LCOWSupported() && apiPlatform == "" {
   105  			apiPlatform = os.Getenv("LCOW_API_PLATFORM_IF_OMITTED")
   106  		}
   107  		platform = system.ParsePlatform(apiPlatform)
   108  		if err = system.ValidatePlatform(platform); err != nil {
   109  			err = fmt.Errorf("invalid platform: %s", err)
   110  		}
   111  	}
   112  
   113  	if err == nil {
   114  		if image != "" { //pull
   115  			metaHeaders := map[string][]string{}
   116  			for k, v := range r.Header {
   117  				if strings.HasPrefix(k, "X-Meta-") {
   118  					metaHeaders[k] = v
   119  				}
   120  			}
   121  
   122  			authEncoded := r.Header.Get("X-Registry-Auth")
   123  			authConfig := &types.AuthConfig{}
   124  			if authEncoded != "" {
   125  				authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   126  				if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
   127  					// for a pull it is not an error if no auth was given
   128  					// to increase compatibility with the existing api it is defaulting to be empty
   129  					authConfig = &types.AuthConfig{}
   130  				}
   131  			}
   132  			err = s.backend.PullImage(ctx, image, tag, platform.OS, metaHeaders, authConfig, output)
   133  		} else { //import
   134  			src := r.Form.Get("fromSrc")
   135  			// 'err' MUST NOT be defined within this block, we need any error
   136  			// generated from the download to be available to the output
   137  			// stream processing below
   138  			err = s.backend.ImportImage(src, repo, platform.OS, tag, message, r.Body, output, r.Form["changes"])
   139  		}
   140  	}
   141  	if err != nil {
   142  		if !output.Flushed() {
   143  			return err
   144  		}
   145  		output.Write(streamformatter.FormatError(err))
   146  	}
   147  
   148  	return nil
   149  }
   150  
   151  type validationError struct {
   152  	cause error
   153  }
   154  
   155  func (e validationError) Error() string {
   156  	return e.cause.Error()
   157  }
   158  
   159  func (e validationError) Cause() error {
   160  	return e.cause
   161  }
   162  
   163  func (validationError) InvalidParameter() {}
   164  
   165  func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   166  	metaHeaders := map[string][]string{}
   167  	for k, v := range r.Header {
   168  		if strings.HasPrefix(k, "X-Meta-") {
   169  			metaHeaders[k] = v
   170  		}
   171  	}
   172  	if err := httputils.ParseForm(r); err != nil {
   173  		return err
   174  	}
   175  	authConfig := &types.AuthConfig{}
   176  
   177  	authEncoded := r.Header.Get("X-Registry-Auth")
   178  	if authEncoded != "" {
   179  		// the new format is to handle the authConfig as a header
   180  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   181  		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
   182  			// to increase compatibility to existing api it is defaulting to be empty
   183  			authConfig = &types.AuthConfig{}
   184  		}
   185  	} else {
   186  		// the old format is supported for compatibility if there was no authConfig header
   187  		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
   188  			return errors.Wrap(validationError{err}, "Bad parameters and missing X-Registry-Auth")
   189  		}
   190  	}
   191  
   192  	image := vars["name"]
   193  	tag := r.Form.Get("tag")
   194  
   195  	output := ioutils.NewWriteFlusher(w)
   196  	defer output.Close()
   197  
   198  	w.Header().Set("Content-Type", "application/json")
   199  
   200  	if err := s.backend.PushImage(ctx, image, tag, metaHeaders, authConfig, 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 (s *imageRouter) getImagesGet(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  
   214  	w.Header().Set("Content-Type", "application/x-tar")
   215  
   216  	output := ioutils.NewWriteFlusher(w)
   217  	defer output.Close()
   218  	var names []string
   219  	if name, ok := vars["name"]; ok {
   220  		names = []string{name}
   221  	} else {
   222  		names = r.Form["names"]
   223  	}
   224  
   225  	if err := s.backend.ExportImage(names, output); err != nil {
   226  		if !output.Flushed() {
   227  			return err
   228  		}
   229  		output.Write(streamformatter.FormatError(err))
   230  	}
   231  	return nil
   232  }
   233  
   234  func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   235  	if err := httputils.ParseForm(r); err != nil {
   236  		return err
   237  	}
   238  	quiet := httputils.BoolValueOrDefault(r, "quiet", true)
   239  
   240  	w.Header().Set("Content-Type", "application/json")
   241  
   242  	output := ioutils.NewWriteFlusher(w)
   243  	defer output.Close()
   244  	if err := s.backend.LoadImage(r.Body, output, quiet); err != nil {
   245  		output.Write(streamformatter.FormatError(err))
   246  	}
   247  	return nil
   248  }
   249  
   250  type missingImageError struct{}
   251  
   252  func (missingImageError) Error() string {
   253  	return "image name cannot be blank"
   254  }
   255  
   256  func (missingImageError) InvalidParameter() {}
   257  
   258  func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   259  	if err := httputils.ParseForm(r); err != nil {
   260  		return err
   261  	}
   262  
   263  	name := vars["name"]
   264  
   265  	if strings.TrimSpace(name) == "" {
   266  		return missingImageError{}
   267  	}
   268  
   269  	force := httputils.BoolValue(r, "force")
   270  	prune := !httputils.BoolValue(r, "noprune")
   271  
   272  	list, err := s.backend.ImageDelete(name, force, prune)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	return httputils.WriteJSON(w, http.StatusOK, list)
   278  }
   279  
   280  func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   281  	imageInspect, err := s.backend.LookupImage(vars["name"])
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	return httputils.WriteJSON(w, http.StatusOK, imageInspect)
   287  }
   288  
   289  func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   290  	if err := httputils.ParseForm(r); err != nil {
   291  		return err
   292  	}
   293  
   294  	imageFilters, err := filters.FromJSON(r.Form.Get("filters"))
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	filterParam := r.Form.Get("filter")
   300  	// FIXME(vdemeester) This has been deprecated in 1.13, and is target for removal for v17.12
   301  	if filterParam != "" {
   302  		imageFilters.Add("reference", filterParam)
   303  	}
   304  
   305  	images, err := s.backend.Images(imageFilters, httputils.BoolValue(r, "all"), false)
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	return httputils.WriteJSON(w, http.StatusOK, images)
   311  }
   312  
   313  func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   314  	name := vars["name"]
   315  	history, err := s.backend.ImageHistory(name)
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	return httputils.WriteJSON(w, http.StatusOK, history)
   321  }
   322  
   323  func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   324  	if err := httputils.ParseForm(r); err != nil {
   325  		return err
   326  	}
   327  	if err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil {
   328  		return err
   329  	}
   330  	w.WriteHeader(http.StatusCreated)
   331  	return nil
   332  }
   333  
   334  func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   335  	if err := httputils.ParseForm(r); err != nil {
   336  		return err
   337  	}
   338  	var (
   339  		config      *types.AuthConfig
   340  		authEncoded = r.Header.Get("X-Registry-Auth")
   341  		headers     = map[string][]string{}
   342  	)
   343  
   344  	if authEncoded != "" {
   345  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   346  		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
   347  			// for a search it is not an error if no auth was given
   348  			// to increase compatibility with the existing api it is defaulting to be empty
   349  			config = &types.AuthConfig{}
   350  		}
   351  	}
   352  	for k, v := range r.Header {
   353  		if strings.HasPrefix(k, "X-Meta-") {
   354  			headers[k] = v
   355  		}
   356  	}
   357  	limit := registry.DefaultSearchLimit
   358  	if r.Form.Get("limit") != "" {
   359  		limitValue, err := strconv.Atoi(r.Form.Get("limit"))
   360  		if err != nil {
   361  			return err
   362  		}
   363  		limit = limitValue
   364  	}
   365  	query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), limit, config, headers)
   366  	if err != nil {
   367  		return err
   368  	}
   369  	return httputils.WriteJSON(w, http.StatusOK, query.Results)
   370  }
   371  
   372  func (s *imageRouter) postImagesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   373  	if err := httputils.ParseForm(r); err != nil {
   374  		return err
   375  	}
   376  
   377  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	pruneReport, err := s.backend.ImagesPrune(ctx, pruneFilters)
   383  	if err != nil {
   384  		return err
   385  	}
   386  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   387  }