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