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