github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/api/server/router/plugin/plugin_routes.go (about)

     1  package plugin // import "github.com/docker/docker/api/server/router/plugin"
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"io"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/docker/distribution/reference"
    13  	"github.com/docker/docker/api/server/httputils"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/filters"
    16  	"github.com/docker/docker/errdefs"
    17  	"github.com/docker/docker/pkg/ioutils"
    18  	"github.com/docker/docker/pkg/streamformatter"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  func parseHeaders(headers http.Header) (map[string][]string, *types.AuthConfig) {
    23  
    24  	metaHeaders := map[string][]string{}
    25  	for k, v := range headers {
    26  		if strings.HasPrefix(k, "X-Meta-") {
    27  			metaHeaders[k] = v
    28  		}
    29  	}
    30  
    31  	// Get X-Registry-Auth
    32  	authEncoded := headers.Get("X-Registry-Auth")
    33  	authConfig := &types.AuthConfig{}
    34  	if authEncoded != "" {
    35  		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
    36  		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
    37  			authConfig = &types.AuthConfig{}
    38  		}
    39  	}
    40  
    41  	return metaHeaders, authConfig
    42  }
    43  
    44  // parseRemoteRef parses the remote reference into a reference.Named
    45  // returning the tag associated with the reference. In the case the
    46  // given reference string includes both digest and tag, the returned
    47  // reference will have the digest without the tag, but the tag will
    48  // be returned.
    49  func parseRemoteRef(remote string) (reference.Named, string, error) {
    50  	// Parse remote reference, supporting remotes with name and tag
    51  	remoteRef, err := reference.ParseNormalizedNamed(remote)
    52  	if err != nil {
    53  		return nil, "", err
    54  	}
    55  
    56  	type canonicalWithTag interface {
    57  		reference.Canonical
    58  		Tag() string
    59  	}
    60  
    61  	if canonical, ok := remoteRef.(canonicalWithTag); ok {
    62  		remoteRef, err = reference.WithDigest(reference.TrimNamed(remoteRef), canonical.Digest())
    63  		if err != nil {
    64  			return nil, "", err
    65  		}
    66  		return remoteRef, canonical.Tag(), nil
    67  	}
    68  
    69  	remoteRef = reference.TagNameOnly(remoteRef)
    70  
    71  	return remoteRef, "", nil
    72  }
    73  
    74  func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    75  	if err := httputils.ParseForm(r); err != nil {
    76  		return err
    77  	}
    78  
    79  	metaHeaders, authConfig := parseHeaders(r.Header)
    80  
    81  	ref, _, err := parseRemoteRef(r.FormValue("remote"))
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	privileges, err := pr.backend.Privileges(ctx, ref, metaHeaders, authConfig)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	return httputils.WriteJSON(w, http.StatusOK, privileges)
    91  }
    92  
    93  func (pr *pluginRouter) upgradePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    94  	if err := httputils.ParseForm(r); err != nil {
    95  		return errors.Wrap(err, "failed to parse form")
    96  	}
    97  
    98  	var privileges types.PluginPrivileges
    99  	dec := json.NewDecoder(r.Body)
   100  	if err := dec.Decode(&privileges); err != nil {
   101  		return errors.Wrap(err, "failed to parse privileges")
   102  	}
   103  	if dec.More() {
   104  		return errors.New("invalid privileges")
   105  	}
   106  
   107  	metaHeaders, authConfig := parseHeaders(r.Header)
   108  	ref, tag, err := parseRemoteRef(r.FormValue("remote"))
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	name, err := getName(ref, tag, vars["name"])
   114  	if err != nil {
   115  		return err
   116  	}
   117  	w.Header().Set("Docker-Plugin-Name", name)
   118  
   119  	w.Header().Set("Content-Type", "application/json")
   120  	output := ioutils.NewWriteFlusher(w)
   121  
   122  	if err := pr.backend.Upgrade(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil {
   123  		if !output.Flushed() {
   124  			return err
   125  		}
   126  		_, _ = output.Write(streamformatter.FormatError(err))
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   133  	if err := httputils.ParseForm(r); err != nil {
   134  		return errors.Wrap(err, "failed to parse form")
   135  	}
   136  
   137  	var privileges types.PluginPrivileges
   138  	dec := json.NewDecoder(r.Body)
   139  	if err := dec.Decode(&privileges); err != nil {
   140  		return errors.Wrap(err, "failed to parse privileges")
   141  	}
   142  	if dec.More() {
   143  		return errors.New("invalid privileges")
   144  	}
   145  
   146  	metaHeaders, authConfig := parseHeaders(r.Header)
   147  	ref, tag, err := parseRemoteRef(r.FormValue("remote"))
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	name, err := getName(ref, tag, r.FormValue("name"))
   153  	if err != nil {
   154  		return err
   155  	}
   156  	w.Header().Set("Docker-Plugin-Name", name)
   157  
   158  	w.Header().Set("Content-Type", "application/json")
   159  	output := ioutils.NewWriteFlusher(w)
   160  
   161  	if err := pr.backend.Pull(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil {
   162  		if !output.Flushed() {
   163  			return err
   164  		}
   165  		_, _ = output.Write(streamformatter.FormatError(err))
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func getName(ref reference.Named, tag, name string) (string, error) {
   172  	if name == "" {
   173  		if _, ok := ref.(reference.Canonical); ok {
   174  			trimmed := reference.TrimNamed(ref)
   175  			if tag != "" {
   176  				nt, err := reference.WithTag(trimmed, tag)
   177  				if err != nil {
   178  					return "", err
   179  				}
   180  				name = reference.FamiliarString(nt)
   181  			} else {
   182  				name = reference.FamiliarString(reference.TagNameOnly(trimmed))
   183  			}
   184  		} else {
   185  			name = reference.FamiliarString(ref)
   186  		}
   187  	} else {
   188  		localRef, err := reference.ParseNormalizedNamed(name)
   189  		if err != nil {
   190  			return "", err
   191  		}
   192  		if _, ok := localRef.(reference.Canonical); ok {
   193  			return "", errors.New("cannot use digest in plugin tag")
   194  		}
   195  		if reference.IsNameOnly(localRef) {
   196  			// TODO: log change in name to out stream
   197  			name = reference.FamiliarString(reference.TagNameOnly(localRef))
   198  		}
   199  	}
   200  	return name, nil
   201  }
   202  
   203  func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   204  	if err := httputils.ParseForm(r); err != nil {
   205  		return err
   206  	}
   207  
   208  	options := &types.PluginCreateOptions{
   209  		RepoName: r.FormValue("name")}
   210  
   211  	if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
   212  		return err
   213  	}
   214  	// TODO: send progress bar
   215  	w.WriteHeader(http.StatusNoContent)
   216  	return nil
   217  }
   218  
   219  func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   220  	if err := httputils.ParseForm(r); err != nil {
   221  		return err
   222  	}
   223  
   224  	name := vars["name"]
   225  	timeout, err := strconv.Atoi(r.Form.Get("timeout"))
   226  	if err != nil {
   227  		return err
   228  	}
   229  	config := &types.PluginEnableConfig{Timeout: timeout}
   230  
   231  	return pr.backend.Enable(name, config)
   232  }
   233  
   234  func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   235  	if err := httputils.ParseForm(r); err != nil {
   236  		return err
   237  	}
   238  
   239  	name := vars["name"]
   240  	config := &types.PluginDisableConfig{
   241  		ForceDisable: httputils.BoolValue(r, "force"),
   242  	}
   243  
   244  	return pr.backend.Disable(name, config)
   245  }
   246  
   247  func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   248  	if err := httputils.ParseForm(r); err != nil {
   249  		return err
   250  	}
   251  
   252  	name := vars["name"]
   253  	config := &types.PluginRmConfig{
   254  		ForceRemove: httputils.BoolValue(r, "force"),
   255  	}
   256  	return pr.backend.Remove(name, config)
   257  }
   258  
   259  func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   260  	if err := httputils.ParseForm(r); err != nil {
   261  		return errors.Wrap(err, "failed to parse form")
   262  	}
   263  
   264  	metaHeaders, authConfig := parseHeaders(r.Header)
   265  
   266  	w.Header().Set("Content-Type", "application/json")
   267  	output := ioutils.NewWriteFlusher(w)
   268  
   269  	if err := pr.backend.Push(ctx, vars["name"], metaHeaders, authConfig, output); err != nil {
   270  		if !output.Flushed() {
   271  			return err
   272  		}
   273  		_, _ = output.Write(streamformatter.FormatError(err))
   274  	}
   275  	return nil
   276  }
   277  
   278  func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   279  	var args []string
   280  	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
   281  		if err == io.EOF {
   282  			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
   283  		}
   284  		return errdefs.InvalidParameter(err)
   285  	}
   286  	if err := pr.backend.Set(vars["name"], args); err != nil {
   287  		return err
   288  	}
   289  	w.WriteHeader(http.StatusNoContent)
   290  	return nil
   291  }
   292  
   293  func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   294  	if err := httputils.ParseForm(r); err != nil {
   295  		return err
   296  	}
   297  
   298  	pluginFilters, err := filters.FromJSON(r.Form.Get("filters"))
   299  	if err != nil {
   300  		return err
   301  	}
   302  	l, err := pr.backend.List(pluginFilters)
   303  	if err != nil {
   304  		return err
   305  	}
   306  	return httputils.WriteJSON(w, http.StatusOK, l)
   307  }
   308  
   309  func (pr *pluginRouter) inspectPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   310  	result, err := pr.backend.Inspect(vars["name"])
   311  	if err != nil {
   312  		return err
   313  	}
   314  	return httputils.WriteJSON(w, http.StatusOK, result)
   315  }