github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/api/server/router/local/image.go (about)

     1  package local
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/distribution/digest"
    15  	"github.com/docker/docker/api/server/httputils"
    16  	"github.com/docker/docker/api/types"
    17  	"github.com/docker/docker/builder"
    18  	"github.com/docker/docker/builder/dockerfile"
    19  	"github.com/docker/docker/daemon/daemonbuilder"
    20  	derr "github.com/docker/docker/errors"
    21  	"github.com/docker/docker/pkg/archive"
    22  	"github.com/docker/docker/pkg/chrootarchive"
    23  	"github.com/docker/docker/pkg/ioutils"
    24  	"github.com/docker/docker/pkg/progress"
    25  	"github.com/docker/docker/pkg/streamformatter"
    26  	"github.com/docker/docker/pkg/ulimit"
    27  	"github.com/docker/docker/reference"
    28  	"github.com/docker/docker/runconfig"
    29  	"github.com/docker/docker/utils"
    30  	"golang.org/x/net/context"
    31  )
    32  
    33  func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    34  	if err := httputils.ParseForm(r); err != nil {
    35  		return err
    36  	}
    37  
    38  	if err := httputils.CheckForJSON(r); err != nil {
    39  		return err
    40  	}
    41  
    42  	cname := r.Form.Get("container")
    43  
    44  	pause := httputils.BoolValue(r, "pause")
    45  	version := httputils.VersionFromContext(ctx)
    46  	if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
    47  		pause = true
    48  	}
    49  
    50  	c, _, err := runconfig.DecodeContainerConfig(r.Body)
    51  	if err != nil && err != io.EOF { //Do not fail if body is empty.
    52  		return err
    53  	}
    54  	if c == nil {
    55  		c = &runconfig.Config{}
    56  	}
    57  
    58  	if !s.daemon.Exists(cname) {
    59  		return derr.ErrorCodeNoSuchContainer.WithArgs(cname)
    60  	}
    61  
    62  	newConfig, err := dockerfile.BuildFromConfig(c, r.Form["changes"])
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	commitCfg := &types.ContainerCommitConfig{
    68  		Pause:        pause,
    69  		Repo:         r.Form.Get("repo"),
    70  		Tag:          r.Form.Get("tag"),
    71  		Author:       r.Form.Get("author"),
    72  		Comment:      r.Form.Get("comment"),
    73  		Config:       newConfig,
    74  		MergeConfigs: true,
    75  	}
    76  
    77  	imgID, err := s.daemon.Commit(cname, commitCfg)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
    83  		ID: string(imgID),
    84  	})
    85  }
    86  
    87  // Creates an image from Pull or from Import
    88  func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    89  	if err := httputils.ParseForm(r); err != nil {
    90  		return err
    91  	}
    92  
    93  	var (
    94  		image   = r.Form.Get("fromImage")
    95  		repo    = r.Form.Get("repo")
    96  		tag     = r.Form.Get("tag")
    97  		message = r.Form.Get("message")
    98  	)
    99  	authEncoded := r.Header.Get("X-Registry-Auth")
   100  	authConfig := &types.AuthConfig{}
   101  	if authEncoded != "" {
   102  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   103  		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
   104  			// for a pull it is not an error if no auth was given
   105  			// to increase compatibility with the existing api it is defaulting to be empty
   106  			authConfig = &types.AuthConfig{}
   107  		}
   108  	}
   109  
   110  	var (
   111  		err    error
   112  		output = ioutils.NewWriteFlusher(w)
   113  	)
   114  	defer output.Close()
   115  
   116  	w.Header().Set("Content-Type", "application/json")
   117  
   118  	if image != "" { //pull
   119  		// Special case: "pull -a" may send an image name with a
   120  		// trailing :. This is ugly, but let's not break API
   121  		// compatibility.
   122  		image = strings.TrimSuffix(image, ":")
   123  
   124  		var ref reference.Named
   125  		ref, err = reference.ParseNamed(image)
   126  		if err == nil {
   127  			if tag != "" {
   128  				// The "tag" could actually be a digest.
   129  				var dgst digest.Digest
   130  				dgst, err = digest.ParseDigest(tag)
   131  				if err == nil {
   132  					ref, err = reference.WithDigest(ref, dgst)
   133  				} else {
   134  					ref, err = reference.WithTag(ref, tag)
   135  				}
   136  			}
   137  			if err == nil {
   138  				metaHeaders := map[string][]string{}
   139  				for k, v := range r.Header {
   140  					if strings.HasPrefix(k, "X-Meta-") {
   141  						metaHeaders[k] = v
   142  					}
   143  				}
   144  
   145  				err = s.daemon.PullImage(ref, metaHeaders, authConfig, output)
   146  			}
   147  		}
   148  	} else { //import
   149  		var newRef reference.Named
   150  		if repo != "" {
   151  			var err error
   152  			newRef, err = reference.ParseNamed(repo)
   153  			if err != nil {
   154  				return err
   155  			}
   156  
   157  			if _, isCanonical := newRef.(reference.Canonical); isCanonical {
   158  				return errors.New("cannot import digest reference")
   159  			}
   160  
   161  			if tag != "" {
   162  				newRef, err = reference.WithTag(newRef, tag)
   163  				if err != nil {
   164  					return err
   165  				}
   166  			}
   167  		}
   168  
   169  		src := r.Form.Get("fromSrc")
   170  
   171  		// 'err' MUST NOT be defined within this block, we need any error
   172  		// generated from the download to be available to the output
   173  		// stream processing below
   174  		var newConfig *runconfig.Config
   175  		newConfig, err = dockerfile.BuildFromConfig(&runconfig.Config{}, r.Form["changes"])
   176  		if err != nil {
   177  			return err
   178  		}
   179  
   180  		err = s.daemon.ImportImage(src, newRef, message, r.Body, output, newConfig)
   181  	}
   182  	if err != nil {
   183  		if !output.Flushed() {
   184  			return err
   185  		}
   186  		sf := streamformatter.NewJSONStreamFormatter()
   187  		output.Write(sf.FormatError(err))
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   194  	metaHeaders := map[string][]string{}
   195  	for k, v := range r.Header {
   196  		if strings.HasPrefix(k, "X-Meta-") {
   197  			metaHeaders[k] = v
   198  		}
   199  	}
   200  	if err := httputils.ParseForm(r); err != nil {
   201  		return err
   202  	}
   203  	authConfig := &types.AuthConfig{}
   204  
   205  	authEncoded := r.Header.Get("X-Registry-Auth")
   206  	if authEncoded != "" {
   207  		// the new format is to handle the authConfig as a header
   208  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   209  		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
   210  			// to increase compatibility to existing api it is defaulting to be empty
   211  			authConfig = &types.AuthConfig{}
   212  		}
   213  	} else {
   214  		// the old format is supported for compatibility if there was no authConfig header
   215  		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
   216  			return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
   217  		}
   218  	}
   219  
   220  	ref, err := reference.ParseNamed(vars["name"])
   221  	if err != nil {
   222  		return err
   223  	}
   224  	tag := r.Form.Get("tag")
   225  	if tag != "" {
   226  		// Push by digest is not supported, so only tags are supported.
   227  		ref, err = reference.WithTag(ref, tag)
   228  		if err != nil {
   229  			return err
   230  		}
   231  	}
   232  
   233  	output := ioutils.NewWriteFlusher(w)
   234  	defer output.Close()
   235  
   236  	w.Header().Set("Content-Type", "application/json")
   237  
   238  	if err := s.daemon.PushImage(ref, metaHeaders, authConfig, output); err != nil {
   239  		if !output.Flushed() {
   240  			return err
   241  		}
   242  		sf := streamformatter.NewJSONStreamFormatter()
   243  		output.Write(sf.FormatError(err))
   244  	}
   245  	return nil
   246  }
   247  
   248  func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   249  	if err := httputils.ParseForm(r); err != nil {
   250  		return err
   251  	}
   252  
   253  	w.Header().Set("Content-Type", "application/x-tar")
   254  
   255  	output := ioutils.NewWriteFlusher(w)
   256  	defer output.Close()
   257  	var names []string
   258  	if name, ok := vars["name"]; ok {
   259  		names = []string{name}
   260  	} else {
   261  		names = r.Form["names"]
   262  	}
   263  
   264  	if err := s.daemon.ExportImage(names, output); err != nil {
   265  		if !output.Flushed() {
   266  			return err
   267  		}
   268  		sf := streamformatter.NewJSONStreamFormatter()
   269  		output.Write(sf.FormatError(err))
   270  	}
   271  	return nil
   272  }
   273  
   274  func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   275  	return s.daemon.LoadImage(r.Body, w)
   276  }
   277  
   278  func (s *router) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   279  	if err := httputils.ParseForm(r); err != nil {
   280  		return err
   281  	}
   282  
   283  	name := vars["name"]
   284  
   285  	if strings.TrimSpace(name) == "" {
   286  		return fmt.Errorf("image name cannot be blank")
   287  	}
   288  
   289  	force := httputils.BoolValue(r, "force")
   290  	prune := !httputils.BoolValue(r, "noprune")
   291  
   292  	list, err := s.daemon.ImageDelete(name, force, prune)
   293  	if err != nil {
   294  		return err
   295  	}
   296  
   297  	return httputils.WriteJSON(w, http.StatusOK, list)
   298  }
   299  
   300  func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   301  	imageInspect, err := s.daemon.LookupImage(vars["name"])
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	return httputils.WriteJSON(w, http.StatusOK, imageInspect)
   307  }
   308  
   309  func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   310  	var (
   311  		authConfigs        = map[string]types.AuthConfig{}
   312  		authConfigsEncoded = r.Header.Get("X-Registry-Config")
   313  		buildConfig        = &dockerfile.Config{}
   314  	)
   315  
   316  	if authConfigsEncoded != "" {
   317  		authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
   318  		if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
   319  			// for a pull it is not an error if no auth was given
   320  			// to increase compatibility with the existing api it is defaulting
   321  			// to be empty.
   322  		}
   323  	}
   324  
   325  	w.Header().Set("Content-Type", "application/json")
   326  
   327  	version := httputils.VersionFromContext(ctx)
   328  	output := ioutils.NewWriteFlusher(w)
   329  	defer output.Close()
   330  	sf := streamformatter.NewJSONStreamFormatter()
   331  	errf := func(err error) error {
   332  		// Do not write the error in the http output if it's still empty.
   333  		// This prevents from writing a 200(OK) when there is an internal error.
   334  		if !output.Flushed() {
   335  			return err
   336  		}
   337  		_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
   338  		if err != nil {
   339  			logrus.Warnf("could not write error response: %v", err)
   340  		}
   341  		return nil
   342  	}
   343  
   344  	if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
   345  		buildConfig.Remove = true
   346  	} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
   347  		buildConfig.Remove = true
   348  	} else {
   349  		buildConfig.Remove = httputils.BoolValue(r, "rm")
   350  	}
   351  	if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
   352  		buildConfig.Pull = true
   353  	}
   354  
   355  	repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
   356  	if err != nil {
   357  		return errf(err)
   358  	}
   359  
   360  	buildConfig.DockerfileName = r.FormValue("dockerfile")
   361  	buildConfig.Verbose = !httputils.BoolValue(r, "q")
   362  	buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
   363  	buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
   364  	buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
   365  	buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
   366  	buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
   367  	buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
   368  	buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
   369  	buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
   370  	buildConfig.CPUSetMems = r.FormValue("cpusetmems")
   371  	buildConfig.CgroupParent = r.FormValue("cgroupparent")
   372  
   373  	if r.Form.Get("shmsize") != "" {
   374  		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
   375  		if err != nil {
   376  			return errf(err)
   377  		}
   378  		buildConfig.ShmSize = &shmSize
   379  	}
   380  
   381  	if i := runconfig.IsolationLevel(r.FormValue("isolation")); i != "" {
   382  		if !runconfig.IsolationLevel.IsValid(i) {
   383  			return errf(fmt.Errorf("Unsupported isolation: %q", i))
   384  		}
   385  		buildConfig.Isolation = i
   386  	}
   387  
   388  	var buildUlimits = []*ulimit.Ulimit{}
   389  	ulimitsJSON := r.FormValue("ulimits")
   390  	if ulimitsJSON != "" {
   391  		if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
   392  			return errf(err)
   393  		}
   394  		buildConfig.Ulimits = buildUlimits
   395  	}
   396  
   397  	var buildArgs = map[string]string{}
   398  	buildArgsJSON := r.FormValue("buildargs")
   399  	if buildArgsJSON != "" {
   400  		if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
   401  			return errf(err)
   402  		}
   403  		buildConfig.BuildArgs = buildArgs
   404  	}
   405  
   406  	remoteURL := r.FormValue("remote")
   407  
   408  	// Currently, only used if context is from a remote url.
   409  	// Look at code in DetectContextFromRemoteURL for more information.
   410  	createProgressReader := func(in io.ReadCloser) io.ReadCloser {
   411  		progressOutput := sf.NewProgressOutput(output, true)
   412  		return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
   413  	}
   414  
   415  	var (
   416  		context        builder.ModifiableContext
   417  		dockerfileName string
   418  	)
   419  	context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
   420  	if err != nil {
   421  		return errf(err)
   422  	}
   423  	defer func() {
   424  		if err := context.Close(); err != nil {
   425  			logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
   426  		}
   427  	}()
   428  
   429  	uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
   430  	defaultArchiver := &archive.Archiver{
   431  		Untar:   chrootarchive.Untar,
   432  		UIDMaps: uidMaps,
   433  		GIDMaps: gidMaps,
   434  	}
   435  	docker := &daemonbuilder.Docker{
   436  		Daemon:      s.daemon,
   437  		OutOld:      output,
   438  		AuthConfigs: authConfigs,
   439  		Archiver:    defaultArchiver,
   440  	}
   441  
   442  	b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil)
   443  	if err != nil {
   444  		return errf(err)
   445  	}
   446  	b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
   447  	b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
   448  
   449  	if closeNotifier, ok := w.(http.CloseNotifier); ok {
   450  		finished := make(chan struct{})
   451  		defer close(finished)
   452  		go func() {
   453  			select {
   454  			case <-finished:
   455  			case <-closeNotifier.CloseNotify():
   456  				logrus.Infof("Client disconnected, cancelling job: build")
   457  				b.Cancel()
   458  			}
   459  		}()
   460  	}
   461  
   462  	if len(dockerfileName) > 0 {
   463  		b.DockerfileName = dockerfileName
   464  	}
   465  
   466  	imgID, err := b.Build()
   467  	if err != nil {
   468  		return errf(err)
   469  	}
   470  
   471  	for _, rt := range repoAndTags {
   472  		if err := s.daemon.TagImage(rt, imgID); err != nil {
   473  			return errf(err)
   474  		}
   475  	}
   476  
   477  	return nil
   478  }
   479  
   480  // sanitizeRepoAndTags parses the raw "t" parameter received from the client
   481  // to a slice of repoAndTag.
   482  // It also validates each repoName and tag.
   483  func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
   484  	var (
   485  		repoAndTags []reference.Named
   486  		// This map is used for deduplicating the "-t" parameter.
   487  		uniqNames = make(map[string]struct{})
   488  	)
   489  	for _, repo := range names {
   490  		if repo == "" {
   491  			continue
   492  		}
   493  
   494  		ref, err := reference.ParseNamed(repo)
   495  		if err != nil {
   496  			return nil, err
   497  		}
   498  		ref = reference.WithDefaultTag(ref)
   499  
   500  		if _, isCanonical := ref.(reference.Canonical); isCanonical {
   501  			return nil, errors.New("build tag cannot contain a digest")
   502  		}
   503  
   504  		nameWithTag := ref.String()
   505  
   506  		if _, exists := uniqNames[nameWithTag]; !exists {
   507  			uniqNames[nameWithTag] = struct{}{}
   508  			repoAndTags = append(repoAndTags, ref)
   509  		}
   510  	}
   511  	return repoAndTags, nil
   512  }
   513  
   514  func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   515  	if err := httputils.ParseForm(r); err != nil {
   516  		return err
   517  	}
   518  
   519  	// FIXME: The filter parameter could just be a match filter
   520  	images, err := s.daemon.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
   521  	if err != nil {
   522  		return err
   523  	}
   524  
   525  	return httputils.WriteJSON(w, http.StatusOK, images)
   526  }
   527  
   528  func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   529  	name := vars["name"]
   530  	history, err := s.daemon.ImageHistory(name)
   531  	if err != nil {
   532  		return err
   533  	}
   534  
   535  	return httputils.WriteJSON(w, http.StatusOK, history)
   536  }
   537  
   538  func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   539  	if err := httputils.ParseForm(r); err != nil {
   540  		return err
   541  	}
   542  	repo := r.Form.Get("repo")
   543  	tag := r.Form.Get("tag")
   544  	newTag, err := reference.WithName(repo)
   545  	if err != nil {
   546  		return err
   547  	}
   548  	if tag != "" {
   549  		if newTag, err = reference.WithTag(newTag, tag); err != nil {
   550  			return err
   551  		}
   552  	}
   553  	if err := s.daemon.TagImage(newTag, vars["name"]); err != nil {
   554  		return err
   555  	}
   556  	w.WriteHeader(http.StatusCreated)
   557  	return nil
   558  }
   559  
   560  func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   561  	if err := httputils.ParseForm(r); err != nil {
   562  		return err
   563  	}
   564  	var (
   565  		config      *types.AuthConfig
   566  		authEncoded = r.Header.Get("X-Registry-Auth")
   567  		headers     = map[string][]string{}
   568  	)
   569  
   570  	if authEncoded != "" {
   571  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   572  		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
   573  			// for a search it is not an error if no auth was given
   574  			// to increase compatibility with the existing api it is defaulting to be empty
   575  			config = &types.AuthConfig{}
   576  		}
   577  	}
   578  	for k, v := range r.Header {
   579  		if strings.HasPrefix(k, "X-Meta-") {
   580  			headers[k] = v
   581  		}
   582  	}
   583  	query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers)
   584  	if err != nil {
   585  		return err
   586  	}
   587  	return httputils.WriteJSON(w, http.StatusOK, query.Results)
   588  }