github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/api/server/router/image/image_routes.go (about)

     1  package image
     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  	"github.com/docker/docker/pkg/ioutils"
    18  	"github.com/docker/docker/pkg/streamformatter"
    19  	"github.com/docker/docker/reference"
    20  	"github.com/docker/docker/runconfig"
    21  	"github.com/docker/engine-api/types"
    22  	"github.com/docker/engine-api/types/container"
    23  	"golang.org/x/net/context"
    24  )
    25  
    26  func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    27  	if err := httputils.ParseForm(r); err != nil {
    28  		return err
    29  	}
    30  
    31  	if err := httputils.CheckForJSON(r); err != nil {
    32  		return err
    33  	}
    34  
    35  	cname := r.Form.Get("container")
    36  
    37  	pause := httputils.BoolValue(r, "pause")
    38  	version := httputils.VersionFromContext(ctx)
    39  	if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
    40  		pause = true
    41  	}
    42  
    43  	c, _, _, err := runconfig.DecodeContainerConfig(r.Body)
    44  	if err != nil && err != io.EOF { //Do not fail if body is empty.
    45  		return err
    46  	}
    47  	if c == nil {
    48  		c = &container.Config{}
    49  	}
    50  
    51  	newConfig, err := dockerfile.BuildFromConfig(c, r.Form["changes"])
    52  	if err != nil {
    53  		return err
    54  	}
    55  
    56  	commitCfg := &types.ContainerCommitConfig{
    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  		Config:       newConfig,
    63  		MergeConfigs: true,
    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.ContainerCommitResponse{
    72  		ID: string(imgID),
    73  	})
    74  }
    75  
    76  // Creates an image from Pull or from Import
    77  func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    78  	if err := httputils.ParseForm(r); err != nil {
    79  		return err
    80  	}
    81  
    82  	var (
    83  		image   = r.Form.Get("fromImage")
    84  		repo    = r.Form.Get("repo")
    85  		tag     = r.Form.Get("tag")
    86  		message = r.Form.Get("message")
    87  		err     error
    88  		output  = ioutils.NewWriteFlusher(w)
    89  	)
    90  	defer output.Close()
    91  
    92  	w.Header().Set("Content-Type", "application/json")
    93  
    94  	if image != "" { //pull
    95  		// Special case: "pull -a" may send an image name with a
    96  		// trailing :. This is ugly, but let's not break API
    97  		// compatibility.
    98  		image = strings.TrimSuffix(image, ":")
    99  
   100  		var ref reference.Named
   101  		ref, err = reference.ParseNamed(image)
   102  		if err == nil {
   103  			if tag != "" {
   104  				// The "tag" could actually be a digest.
   105  				var dgst digest.Digest
   106  				dgst, err = digest.ParseDigest(tag)
   107  				if err == nil {
   108  					ref, err = reference.WithDigest(ref, dgst)
   109  				} else {
   110  					ref, err = reference.WithTag(ref, tag)
   111  				}
   112  			}
   113  			if err == nil {
   114  				metaHeaders := map[string][]string{}
   115  				for k, v := range r.Header {
   116  					if strings.HasPrefix(k, "X-Meta-") {
   117  						metaHeaders[k] = v
   118  					}
   119  				}
   120  
   121  				authEncoded := r.Header.Get("X-Registry-Auth")
   122  				authConfig := &types.AuthConfig{}
   123  				if authEncoded != "" {
   124  					authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   125  					if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
   126  						// for a pull it is not an error if no auth was given
   127  						// to increase compatibility with the existing api it is defaulting to be empty
   128  						authConfig = &types.AuthConfig{}
   129  					}
   130  				}
   131  
   132  				err = s.backend.PullImage(ref, metaHeaders, authConfig, output)
   133  			}
   134  		}
   135  		// Check the error from pulling an image to make sure the request
   136  		// was authorized. Modify the status if the request was
   137  		// unauthorized to respond with 401 rather than 500.
   138  		if err != nil && isAuthorizedError(err) {
   139  			err = errcode.ErrorCodeUnauthorized.WithMessage(fmt.Sprintf("Authentication is required: %s", err))
   140  		}
   141  	} else { //import
   142  		var newRef reference.Named
   143  		if repo != "" {
   144  			var err error
   145  			newRef, err = reference.ParseNamed(repo)
   146  			if err != nil {
   147  				return err
   148  			}
   149  
   150  			if _, isCanonical := newRef.(reference.Canonical); isCanonical {
   151  				return errors.New("cannot import digest reference")
   152  			}
   153  
   154  			if tag != "" {
   155  				newRef, err = reference.WithTag(newRef, tag)
   156  				if err != nil {
   157  					return err
   158  				}
   159  			}
   160  		}
   161  
   162  		src := r.Form.Get("fromSrc")
   163  
   164  		// 'err' MUST NOT be defined within this block, we need any error
   165  		// generated from the download to be available to the output
   166  		// stream processing below
   167  		var newConfig *container.Config
   168  		newConfig, err = dockerfile.BuildFromConfig(&container.Config{}, r.Form["changes"])
   169  		if err != nil {
   170  			return err
   171  		}
   172  
   173  		err = s.backend.ImportImage(src, newRef, message, r.Body, output, newConfig)
   174  	}
   175  	if err != nil {
   176  		if !output.Flushed() {
   177  			return err
   178  		}
   179  		sf := streamformatter.NewJSONStreamFormatter()
   180  		output.Write(sf.FormatError(err))
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   187  	metaHeaders := map[string][]string{}
   188  	for k, v := range r.Header {
   189  		if strings.HasPrefix(k, "X-Meta-") {
   190  			metaHeaders[k] = v
   191  		}
   192  	}
   193  	if err := httputils.ParseForm(r); err != nil {
   194  		return err
   195  	}
   196  	authConfig := &types.AuthConfig{}
   197  
   198  	authEncoded := r.Header.Get("X-Registry-Auth")
   199  	if authEncoded != "" {
   200  		// the new format is to handle the authConfig as a header
   201  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   202  		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
   203  			// to increase compatibility to existing api it is defaulting to be empty
   204  			authConfig = &types.AuthConfig{}
   205  		}
   206  	} else {
   207  		// the old format is supported for compatibility if there was no authConfig header
   208  		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
   209  			return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
   210  		}
   211  	}
   212  
   213  	ref, err := reference.ParseNamed(vars["name"])
   214  	if err != nil {
   215  		return err
   216  	}
   217  	tag := r.Form.Get("tag")
   218  	if tag != "" {
   219  		// Push by digest is not supported, so only tags are supported.
   220  		ref, err = reference.WithTag(ref, tag)
   221  		if err != nil {
   222  			return err
   223  		}
   224  	}
   225  
   226  	output := ioutils.NewWriteFlusher(w)
   227  	defer output.Close()
   228  
   229  	w.Header().Set("Content-Type", "application/json")
   230  
   231  	if err := s.backend.PushImage(ref, metaHeaders, authConfig, 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 *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   242  	if err := httputils.ParseForm(r); err != nil {
   243  		return err
   244  	}
   245  
   246  	w.Header().Set("Content-Type", "application/x-tar")
   247  
   248  	output := ioutils.NewWriteFlusher(w)
   249  	defer output.Close()
   250  	var names []string
   251  	if name, ok := vars["name"]; ok {
   252  		names = []string{name}
   253  	} else {
   254  		names = r.Form["names"]
   255  	}
   256  
   257  	if err := s.backend.ExportImage(names, output); err != nil {
   258  		if !output.Flushed() {
   259  			return err
   260  		}
   261  		sf := streamformatter.NewJSONStreamFormatter()
   262  		output.Write(sf.FormatError(err))
   263  	}
   264  	return nil
   265  }
   266  
   267  func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   268  	if err := httputils.ParseForm(r); err != nil {
   269  		return err
   270  	}
   271  	quiet := httputils.BoolValueOrDefault(r, "quiet", true)
   272  	w.Header().Set("Content-Type", "application/json")
   273  	return s.backend.LoadImage(r.Body, w, quiet)
   274  }
   275  
   276  func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   277  	if err := httputils.ParseForm(r); err != nil {
   278  		return err
   279  	}
   280  
   281  	name := vars["name"]
   282  
   283  	if strings.TrimSpace(name) == "" {
   284  		return fmt.Errorf("image name cannot be blank")
   285  	}
   286  
   287  	force := httputils.BoolValue(r, "force")
   288  	prune := !httputils.BoolValue(r, "noprune")
   289  
   290  	list, err := s.backend.ImageDelete(name, force, prune)
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	return httputils.WriteJSON(w, http.StatusOK, list)
   296  }
   297  
   298  func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   299  	imageInspect, err := s.backend.LookupImage(vars["name"])
   300  	if err != nil {
   301  		return err
   302  	}
   303  
   304  	return httputils.WriteJSON(w, http.StatusOK, imageInspect)
   305  }
   306  
   307  func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   308  	if err := httputils.ParseForm(r); err != nil {
   309  		return err
   310  	}
   311  
   312  	// FIXME: The filter parameter could just be a match filter
   313  	images, err := s.backend.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
   314  	if err != nil {
   315  		return err
   316  	}
   317  
   318  	return httputils.WriteJSON(w, http.StatusOK, images)
   319  }
   320  
   321  func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   322  	name := vars["name"]
   323  	history, err := s.backend.ImageHistory(name)
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	return httputils.WriteJSON(w, http.StatusOK, history)
   329  }
   330  
   331  func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   332  	if err := httputils.ParseForm(r); err != nil {
   333  		return err
   334  	}
   335  	repo := r.Form.Get("repo")
   336  	tag := r.Form.Get("tag")
   337  	newTag, err := reference.WithName(repo)
   338  	if err != nil {
   339  		return err
   340  	}
   341  	if tag != "" {
   342  		if newTag, err = reference.WithTag(newTag, tag); err != nil {
   343  			return err
   344  		}
   345  	}
   346  	if err := s.backend.TagImage(newTag, vars["name"]); err != nil {
   347  		return err
   348  	}
   349  	w.WriteHeader(http.StatusCreated)
   350  	return nil
   351  }
   352  
   353  func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   354  	if err := httputils.ParseForm(r); err != nil {
   355  		return err
   356  	}
   357  	var (
   358  		config      *types.AuthConfig
   359  		authEncoded = r.Header.Get("X-Registry-Auth")
   360  		headers     = map[string][]string{}
   361  	)
   362  
   363  	if authEncoded != "" {
   364  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
   365  		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
   366  			// for a search it is not an error if no auth was given
   367  			// to increase compatibility with the existing api it is defaulting to be empty
   368  			config = &types.AuthConfig{}
   369  		}
   370  	}
   371  	for k, v := range r.Header {
   372  		if strings.HasPrefix(k, "X-Meta-") {
   373  			headers[k] = v
   374  		}
   375  	}
   376  	query, err := s.backend.SearchRegistryForImages(r.Form.Get("term"), config, headers)
   377  	if err != nil {
   378  		return err
   379  	}
   380  	return httputils.WriteJSON(w, http.StatusOK, query.Results)
   381  }
   382  
   383  func isAuthorizedError(err error) bool {
   384  	if urlError, ok := err.(*url.Error); ok {
   385  		err = urlError.Err
   386  	}
   387  
   388  	if dError, ok := err.(errcode.Error); ok {
   389  		if dError.ErrorCode() == errcode.ErrorCodeUnauthorized {
   390  			return true
   391  		}
   392  	}
   393  	return false
   394  }