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