github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/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  	"net/url"
    11  	"strings"
    12  
    13  	"github.com/docker/distribution/digest"
    14  	"github.com/docker/distribution/registry/api/errcode"
    15  	"github.com/docker/docker/api/server/httputils"
    16  	"github.com/docker/docker/builder/dockerfile"
    17  	derr "github.com/docker/docker/errors"
    18  	"github.com/docker/docker/pkg/ioutils"
    19  	"github.com/docker/docker/pkg/streamformatter"
    20  	"github.com/docker/docker/reference"
    21  	"github.com/docker/docker/runconfig"
    22  	"github.com/docker/engine-api/types"
    23  	"github.com/docker/engine-api/types/container"
    24  	"golang.org/x/net/context"
    25  )
    26  
    27  func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    28  	if err := httputils.ParseForm(r); err != nil {
    29  		return err
    30  	}
    31  
    32  	if err := httputils.CheckForJSON(r); err != nil {
    33  		return err
    34  	}
    35  
    36  	cname := r.Form.Get("container")
    37  
    38  	pause := httputils.BoolValue(r, "pause")
    39  	version := httputils.VersionFromContext(ctx)
    40  	if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
    41  		pause = true
    42  	}
    43  
    44  	c, _, _, err := runconfig.DecodeContainerConfig(r.Body)
    45  	if err != nil && err != io.EOF { //Do not fail if body is empty.
    46  		return err
    47  	}
    48  	if c == nil {
    49  		c = &container.Config{}
    50  	}
    51  
    52  	if !s.daemon.Exists(cname) {
    53  		return derr.ErrorCodeNoSuchContainer.WithArgs(cname)
    54  	}
    55  
    56  	newConfig, err := dockerfile.BuildFromConfig(c, r.Form["changes"])
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	commitCfg := &types.ContainerCommitConfig{
    62  		Pause:        pause,
    63  		Repo:         r.Form.Get("repo"),
    64  		Tag:          r.Form.Get("tag"),
    65  		Author:       r.Form.Get("author"),
    66  		Comment:      r.Form.Get("comment"),
    67  		Config:       newConfig,
    68  		MergeConfigs: true,
    69  	}
    70  
    71  	imgID, err := s.daemon.Commit(cname, 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 := &types.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 = &types.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  		// Special case: "pull -a" may send an image name with a
   114  		// trailing :. This is ugly, but let's not break API
   115  		// compatibility.
   116  		image = strings.TrimSuffix(image, ":")
   117  
   118  		var ref reference.Named
   119  		ref, err = reference.ParseNamed(image)
   120  		if err == nil {
   121  			if tag != "" {
   122  				// The "tag" could actually be a digest.
   123  				var dgst digest.Digest
   124  				dgst, err = digest.ParseDigest(tag)
   125  				if err == nil {
   126  					ref, err = reference.WithDigest(ref, dgst)
   127  				} else {
   128  					ref, err = reference.WithTag(ref, tag)
   129  				}
   130  			}
   131  			if err == nil {
   132  				metaHeaders := map[string][]string{}
   133  				for k, v := range r.Header {
   134  					if strings.HasPrefix(k, "X-Meta-") {
   135  						metaHeaders[k] = v
   136  					}
   137  				}
   138  
   139  				err = s.daemon.PullImage(ref, metaHeaders, authConfig, output)
   140  			}
   141  		}
   142  		// Check the error from pulling an image to make sure the request
   143  		// was authorized. Modify the status if the request was
   144  		// unauthorized to respond with 401 rather than 500.
   145  		if err != nil && isAuthorizedError(err) {
   146  			err = errcode.ErrorCodeUnauthorized.WithMessage(fmt.Sprintf("Authentication is required: %s", err))
   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 *container.Config
   175  		newConfig, err = dockerfile.BuildFromConfig(&container.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) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   310  	if err := httputils.ParseForm(r); err != nil {
   311  		return err
   312  	}
   313  
   314  	// FIXME: The filter parameter could just be a match filter
   315  	images, err := s.daemon.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	return httputils.WriteJSON(w, http.StatusOK, images)
   321  }
   322  
   323  func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   324  	name := vars["name"]
   325  	history, err := s.daemon.ImageHistory(name)
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	return httputils.WriteJSON(w, http.StatusOK, history)
   331  }
   332  
   333  func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   334  	if err := httputils.ParseForm(r); err != nil {
   335  		return err
   336  	}
   337  	repo := r.Form.Get("repo")
   338  	tag := r.Form.Get("tag")
   339  	newTag, err := reference.WithName(repo)
   340  	if err != nil {
   341  		return err
   342  	}
   343  	if tag != "" {
   344  		if newTag, err = reference.WithTag(newTag, tag); err != nil {
   345  			return err
   346  		}
   347  	}
   348  	if err := s.daemon.TagImage(newTag, vars["name"]); err != nil {
   349  		return err
   350  	}
   351  	w.WriteHeader(http.StatusCreated)
   352  	return nil
   353  }
   354  
   355  func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   356  	if err := httputils.ParseForm(r); err != nil {
   357  		return err
   358  	}
   359  	var (
   360  		config      *types.AuthConfig
   361  		authEncoded = r.Header.Get("X-Registry-Auth")
   362  		headers     = map[string][]string{}
   363  	)
   364  
   365  	if authEncoded != "" {
   366  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   367  		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
   368  			// for a search it is not an error if no auth was given
   369  			// to increase compatibility with the existing api it is defaulting to be empty
   370  			config = &types.AuthConfig{}
   371  		}
   372  	}
   373  	for k, v := range r.Header {
   374  		if strings.HasPrefix(k, "X-Meta-") {
   375  			headers[k] = v
   376  		}
   377  	}
   378  	query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers)
   379  	if err != nil {
   380  		return err
   381  	}
   382  	return httputils.WriteJSON(w, http.StatusOK, query.Results)
   383  }
   384  
   385  func isAuthorizedError(err error) bool {
   386  	if urlError, ok := err.(*url.Error); ok {
   387  		err = urlError.Err
   388  	}
   389  
   390  	if dError, ok := err.(errcode.Error); ok {
   391  		if dError.ErrorCode() == errcode.ErrorCodeUnauthorized {
   392  			return true
   393  		}
   394  	}
   395  	return false
   396  }