2016-05-16 11:50:55 -04:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
2016-11-21 12:24:01 -05:00
|
|
|
"strconv"
|
2016-05-16 11:50:55 -04:00
|
|
|
"strings"
|
|
|
|
|
2016-12-12 18:05:53 -05:00
|
|
|
distreference "github.com/docker/distribution/reference"
|
2016-05-16 11:50:55 -04:00
|
|
|
"github.com/docker/docker/api/server/httputils"
|
2016-09-06 14:46:37 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
2016-11-23 07:58:15 -05:00
|
|
|
"github.com/docker/docker/api/types/filters"
|
2016-12-12 18:05:53 -05:00
|
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
|
|
"github.com/docker/docker/pkg/streamformatter"
|
|
|
|
"github.com/docker/docker/reference"
|
|
|
|
"github.com/pkg/errors"
|
2016-05-16 11:50:55 -04:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
)
|
|
|
|
|
2016-11-23 20:29:21 -05:00
|
|
|
func parseHeaders(headers http.Header) (map[string][]string, *types.AuthConfig) {
|
2016-05-16 11:50:55 -04:00
|
|
|
|
|
|
|
metaHeaders := map[string][]string{}
|
2016-11-23 20:29:21 -05:00
|
|
|
for k, v := range headers {
|
2016-05-16 11:50:55 -04:00
|
|
|
if strings.HasPrefix(k, "X-Meta-") {
|
|
|
|
metaHeaders[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get X-Registry-Auth
|
2016-11-23 20:29:21 -05:00
|
|
|
authEncoded := headers.Get("X-Registry-Auth")
|
2016-05-16 11:50:55 -04:00
|
|
|
authConfig := &types.AuthConfig{}
|
|
|
|
if authEncoded != "" {
|
|
|
|
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
|
|
|
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
|
|
|
|
authConfig = &types.AuthConfig{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-23 20:29:21 -05:00
|
|
|
return metaHeaders, authConfig
|
|
|
|
}
|
|
|
|
|
2016-12-12 18:05:53 -05:00
|
|
|
// parseRemoteRef parses the remote reference into a reference.Named
|
|
|
|
// returning the tag associated with the reference. In the case the
|
|
|
|
// given reference string includes both digest and tag, the returned
|
|
|
|
// reference will have the digest without the tag, but the tag will
|
|
|
|
// be returned.
|
|
|
|
func parseRemoteRef(remote string) (reference.Named, string, error) {
|
|
|
|
// Parse remote reference, supporting remotes with name and tag
|
|
|
|
// NOTE: Using distribution reference to handle references
|
|
|
|
// containing both a name and digest
|
|
|
|
remoteRef, err := distreference.ParseNamed(remote)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
var tag string
|
|
|
|
if t, ok := remoteRef.(distreference.Tagged); ok {
|
|
|
|
tag = t.Tag()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert distribution reference to docker reference
|
|
|
|
// TODO: remove when docker reference changes reconciled upstream
|
|
|
|
ref, err := reference.WithName(remoteRef.Name())
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
if d, ok := remoteRef.(distreference.Digested); ok {
|
|
|
|
ref, err = reference.WithDigest(ref, d.Digest())
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
} else if tag != "" {
|
|
|
|
ref, err = reference.WithTag(ref, tag)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ref = reference.WithDefaultTag(ref)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ref, tag, nil
|
|
|
|
}
|
|
|
|
|
2016-11-23 20:29:21 -05:00
|
|
|
func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
metaHeaders, authConfig := parseHeaders(r.Header)
|
|
|
|
|
2016-12-12 18:05:53 -05:00
|
|
|
ref, _, err := parseRemoteRef(r.FormValue("remote"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
privileges, err := pr.backend.Privileges(ctx, ref, metaHeaders, authConfig)
|
2016-05-16 11:50:55 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, privileges)
|
|
|
|
}
|
|
|
|
|
2017-01-28 19:54:32 -05:00
|
|
|
func (pr *pluginRouter) upgradePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2016-11-23 20:29:21 -05:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2016-12-12 18:05:53 -05:00
|
|
|
return errors.Wrap(err, "failed to parse form")
|
2016-11-23 20:29:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
var privileges types.PluginPrivileges
|
2016-12-12 18:05:53 -05:00
|
|
|
dec := json.NewDecoder(r.Body)
|
|
|
|
if err := dec.Decode(&privileges); err != nil {
|
|
|
|
return errors.Wrap(err, "failed to parse privileges")
|
|
|
|
}
|
|
|
|
if dec.More() {
|
|
|
|
return errors.New("invalid privileges")
|
2016-11-23 20:29:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
metaHeaders, authConfig := parseHeaders(r.Header)
|
2017-01-28 19:54:32 -05:00
|
|
|
ref, tag, err := parseRemoteRef(r.FormValue("remote"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
name, err := getName(ref, tag, vars["name"])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
w.Header().Set("Docker-Plugin-Name", name)
|
2016-11-23 20:29:21 -05:00
|
|
|
|
2017-01-28 19:54:32 -05:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
output := ioutils.NewWriteFlusher(w)
|
|
|
|
|
|
|
|
if err := pr.backend.Upgrade(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil {
|
|
|
|
if !output.Flushed() {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return errors.Wrap(err, "failed to parse form")
|
|
|
|
}
|
|
|
|
|
|
|
|
var privileges types.PluginPrivileges
|
|
|
|
dec := json.NewDecoder(r.Body)
|
|
|
|
if err := dec.Decode(&privileges); err != nil {
|
|
|
|
return errors.Wrap(err, "failed to parse privileges")
|
|
|
|
}
|
|
|
|
if dec.More() {
|
|
|
|
return errors.New("invalid privileges")
|
|
|
|
}
|
|
|
|
|
|
|
|
metaHeaders, authConfig := parseHeaders(r.Header)
|
2016-12-12 18:05:53 -05:00
|
|
|
ref, tag, err := parseRemoteRef(r.FormValue("remote"))
|
|
|
|
if err != nil {
|
2016-11-23 20:29:21 -05:00
|
|
|
return err
|
|
|
|
}
|
2016-12-12 18:05:53 -05:00
|
|
|
|
2017-01-28 19:54:32 -05:00
|
|
|
name, err := getName(ref, tag, r.FormValue("name"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
w.Header().Set("Docker-Plugin-Name", name)
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
output := ioutils.NewWriteFlusher(w)
|
|
|
|
|
|
|
|
if err := pr.backend.Pull(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil {
|
|
|
|
if !output.Flushed() {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getName(ref reference.Named, tag, name string) (string, error) {
|
2016-12-12 18:05:53 -05:00
|
|
|
if name == "" {
|
|
|
|
if _, ok := ref.(reference.Canonical); ok {
|
|
|
|
trimmed := reference.TrimNamed(ref)
|
|
|
|
if tag != "" {
|
|
|
|
nt, err := reference.WithTag(trimmed, tag)
|
|
|
|
if err != nil {
|
2017-01-28 19:54:32 -05:00
|
|
|
return "", err
|
2016-12-12 18:05:53 -05:00
|
|
|
}
|
|
|
|
name = nt.String()
|
|
|
|
} else {
|
|
|
|
name = reference.WithDefaultTag(trimmed).String()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
name = ref.String()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
localRef, err := reference.ParseNamed(name)
|
|
|
|
if err != nil {
|
2017-01-28 19:54:32 -05:00
|
|
|
return "", err
|
2016-12-12 18:05:53 -05:00
|
|
|
}
|
|
|
|
if _, ok := localRef.(reference.Canonical); ok {
|
2017-01-28 19:54:32 -05:00
|
|
|
return "", errors.New("cannot use digest in plugin tag")
|
2016-12-12 18:05:53 -05:00
|
|
|
}
|
|
|
|
if distreference.IsNameOnly(localRef) {
|
|
|
|
// TODO: log change in name to out stream
|
|
|
|
name = reference.WithDefaultTag(localRef).String()
|
|
|
|
}
|
|
|
|
}
|
2017-01-28 19:54:32 -05:00
|
|
|
return name, nil
|
2016-11-23 20:29:21 -05:00
|
|
|
}
|
|
|
|
|
2016-10-04 15:01:19 -04:00
|
|
|
func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
options := &types.PluginCreateOptions{
|
|
|
|
RepoName: r.FormValue("name")}
|
|
|
|
|
|
|
|
if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-11-23 20:29:21 -05:00
|
|
|
//TODO: send progress bar
|
2016-10-04 15:01:19 -04:00
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-05-16 11:50:55 -04:00
|
|
|
func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2016-11-21 12:24:01 -05:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
name := vars["name"]
|
|
|
|
timeout, err := strconv.Atoi(r.Form.Get("timeout"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
config := &types.PluginEnableConfig{Timeout: timeout}
|
|
|
|
|
|
|
|
return pr.backend.Enable(name, config)
|
2016-05-16 11:50:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2016-12-20 11:26:58 -05:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
name := vars["name"]
|
|
|
|
config := &types.PluginDisableConfig{
|
|
|
|
ForceDisable: httputils.BoolValue(r, "force"),
|
|
|
|
}
|
|
|
|
|
|
|
|
return pr.backend.Disable(name, config)
|
2016-05-16 11:50:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2016-07-22 11:24:54 -04:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
name := vars["name"]
|
|
|
|
config := &types.PluginRmConfig{
|
|
|
|
ForceRemove: httputils.BoolValue(r, "force"),
|
|
|
|
}
|
|
|
|
return pr.backend.Remove(name, config)
|
2016-05-16 11:50:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
2016-12-12 18:05:53 -05:00
|
|
|
return errors.Wrap(err, "failed to parse form")
|
2016-05-16 11:50:55 -04:00
|
|
|
}
|
|
|
|
|
2016-11-23 20:29:21 -05:00
|
|
|
metaHeaders, authConfig := parseHeaders(r.Header)
|
2016-05-16 11:50:55 -04:00
|
|
|
|
2016-12-12 18:05:53 -05:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
output := ioutils.NewWriteFlusher(w)
|
|
|
|
|
|
|
|
if err := pr.backend.Push(ctx, vars["name"], metaHeaders, authConfig, output); err != nil {
|
|
|
|
if !output.Flushed() {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err))
|
|
|
|
}
|
|
|
|
return nil
|
2016-05-16 11:50:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
var args []string
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-10-31 20:07:05 -04:00
|
|
|
if err := pr.backend.Set(vars["name"], args); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
return nil
|
2016-05-16 11:50:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2016-11-23 07:58:15 -05:00
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
pluginFilters, err := filters.FromParam(r.Form.Get("filters"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
l, err := pr.backend.List(pluginFilters)
|
2016-05-16 11:50:55 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, l)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *pluginRouter) inspectPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
|
result, err := pr.backend.Inspect(vars["name"])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, result)
|
|
|
|
}
|