Skip to content

Commit

Permalink
Merge pull request #29487 from tonistiigi/plugins-rework
Browse files Browse the repository at this point in the history
Plugins content addressability
  • Loading branch information
tonistiigi committed Dec 24, 2016
2 parents d1dfc1a + 3d86b0c commit a9fa38b
Show file tree
Hide file tree
Showing 61 changed files with 1,672 additions and 1,345 deletions.
11 changes: 6 additions & 5 deletions api/server/router/plugin/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"

enginetypes "github.com/docker/docker/api/types"
"github.com/docker/docker/reference"
"golang.org/x/net/context"
)

Expand All @@ -13,11 +14,11 @@ type Backend interface {
Disable(name string, config *enginetypes.PluginDisableConfig) error
Enable(name string, config *enginetypes.PluginEnableConfig) error
List() ([]enginetypes.Plugin, error)
Inspect(name string) (enginetypes.Plugin, error)
Inspect(name string) (*enginetypes.Plugin, error)
Remove(name string, config *enginetypes.PluginRmConfig) error
Set(name string, args []string) error
Privileges(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, privileges enginetypes.PluginPrivileges) error
Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
CreateFromContext(ctx context.Context, tarCtx io.Reader, options *enginetypes.PluginCreateOptions) error
Privileges(ctx context.Context, ref reference.Named, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
Pull(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, privileges enginetypes.PluginPrivileges, outStream io.Writer) error
Push(ctx context.Context, name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, outStream io.Writer) error
CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *enginetypes.PluginCreateOptions) error
}
4 changes: 2 additions & 2 deletions api/server/router/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func (r *pluginRouter) initRoutes() {
router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin),
router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH?
router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin),
router.NewPostRoute("/plugins/pull", r.pullPlugin),
router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin),
router.Cancellable(router.NewPostRoute("/plugins/pull", r.pullPlugin)),
router.Cancellable(router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin)),
router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin),
router.NewPostRoute("/plugins/create", r.createPlugin),
}
Expand Down
123 changes: 115 additions & 8 deletions api/server/router/plugin/plugin_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import (
"strconv"
"strings"

distreference "github.com/docker/distribution/reference"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/reference"
"github.com/pkg/errors"
"golang.org/x/net/context"
)

Expand All @@ -34,14 +39,61 @@ func parseHeaders(headers http.Header) (map[string][]string, *types.AuthConfig)
return metaHeaders, authConfig
}

// 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
}

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)

privileges, err := pr.backend.Privileges(r.FormValue("name"), metaHeaders, authConfig)
ref, _, err := parseRemoteRef(r.FormValue("remote"))
if err != nil {
return err
}

privileges, err := pr.backend.Privileges(ctx, ref, metaHeaders, authConfig)
if err != nil {
return err
}
Expand All @@ -50,20 +102,66 @@ func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter

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 err
return errors.Wrap(err, "failed to parse form")
}

var privileges types.PluginPrivileges
if err := json.NewDecoder(r.Body).Decode(&privileges); err != nil {
return err
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)

if err := pr.backend.Pull(r.FormValue("name"), metaHeaders, authConfig, privileges); err != nil {
ref, tag, err := parseRemoteRef(r.FormValue("remote"))
if err != nil {
return err
}
w.WriteHeader(http.StatusCreated)

name := r.FormValue("name")
if name == "" {
if _, ok := ref.(reference.Canonical); ok {
trimmed := reference.TrimNamed(ref)
if tag != "" {
nt, err := reference.WithTag(trimmed, tag)
if err != nil {
return err
}
name = nt.String()
} else {
name = reference.WithDefaultTag(trimmed).String()
}
} else {
name = ref.String()
}
} else {
localRef, err := reference.ParseNamed(name)
if err != nil {
return err
}
if _, ok := localRef.(reference.Canonical); ok {
return errors.New("cannot use digest in plugin tag")
}
if distreference.IsNameOnly(localRef) {
// TODO: log change in name to out stream
name = reference.WithDefaultTag(localRef).String()
}
}
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
}

Expand Down Expand Up @@ -125,12 +223,21 @@ func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter,

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 {
return err
return errors.Wrap(err, "failed to parse form")
}

metaHeaders, authConfig := parseHeaders(r.Header)

return pr.backend.Push(vars["name"], metaHeaders, authConfig)
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
}

func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
Expand Down
34 changes: 24 additions & 10 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1347,16 +1347,13 @@ definitions:
Plugin:
description: "A plugin for the Engine API"
type: "object"
required: [Settings, Enabled, Config, Name, Tag]
required: [Settings, Enabled, Config, Name]
properties:
Id:
type: "string"
Name:
type: "string"
x-nullable: false
Tag:
type: "string"
x-nullable: false
Enabled:
description: "True when the plugin is running. False when the plugin is not running, only installed."
type: "boolean"
Expand Down Expand Up @@ -1392,7 +1389,7 @@ definitions:
- Documentation
- Interface
- Entrypoint
- Workdir
- WorkDir
- Network
- Linux
- PropagatedMount
Expand Down Expand Up @@ -1423,7 +1420,7 @@ definitions:
type: "array"
items:
type: "string"
Workdir:
WorkDir:
type: "string"
x-nullable: false
User:
Expand Down Expand Up @@ -1490,6 +1487,15 @@ definitions:
type: "array"
items:
type: "string"
rootfs:
type: "object"
properties:
type:
type: "string"
diff_ids:
type: "array"
items:
type: "string"
example:
Id: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078"
Name: "tiborvass/no-remove"
Expand Down Expand Up @@ -1528,7 +1534,7 @@ definitions:
Entrypoint:
- "plugin-no-remove"
- "/data"
Workdir: ""
WorkDir: ""
User: {}
Network:
Type: "host"
Expand Down Expand Up @@ -6397,7 +6403,7 @@ paths:
Entrypoint:
- "plugin-no-remove"
- "/data"
Workdir: ""
WorkDir: ""
User: {}
Network:
Type: "host"
Expand Down Expand Up @@ -6503,14 +6509,22 @@ paths:
schema:
$ref: "#/definitions/ErrorResponse"
parameters:
- name: "name"
- name: "remote"
in: "query"
description: |
The plugin to install.
Remote reference for plugin to install.
The `:latest` tag is optional, and is used as the default if omitted.
required: true
type: "string"
- name: "name"
in: "query"
description: |
Local name for the pulled plugin.
The `:latest` tag is optional, and is used as the default if omitted.
required: false
type: "string"
- name: "X-Registry-Auth"
in: "header"
description: "A base64-encoded auth configuration to use when pulling a plugin from a registry. [See the authentication section for details.](#section/Authentication)"
Expand Down
1 change: 1 addition & 0 deletions api/types/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ type PluginInstallOptions struct {
Disabled bool
AcceptAllPermissions bool
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
RemoteRef string // RemoteRef is the plugin name on the registry
PrivilegeFunc RequestPrivilegeFunc
AcceptPermissionsFunc func(PluginPrivileges) (bool, error)
Args []string
Expand Down
22 changes: 16 additions & 6 deletions api/types/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ type Plugin struct {
// settings
// Required: true
Settings PluginSettings `json:"Settings"`

// tag
// Required: true
Tag string `json:"Tag"`
}

// PluginConfig The config of a plugin.
Expand Down Expand Up @@ -78,9 +74,12 @@ type PluginConfig struct {
// user
User PluginConfigUser `json:"User,omitempty"`

// workdir
// work dir
// Required: true
Workdir string `json:"Workdir"`
WorkDir string `json:"WorkDir"`

// rootfs
Rootfs *PluginConfigRootfs `json:"rootfs,omitempty"`
}

// PluginConfigArgs plugin config args
Expand Down Expand Up @@ -143,6 +142,17 @@ type PluginConfigNetwork struct {
Type string `json:"Type"`
}

// PluginConfigRootfs plugin config rootfs
// swagger:model PluginConfigRootfs
type PluginConfigRootfs struct {

// diff ids
DiffIds []string `json:"diff_ids"`

// type
Type string `json:"type,omitempty"`
}

// PluginConfigUser plugin config user
// swagger:model PluginConfigUser
type PluginConfigUser struct {
Expand Down
4 changes: 2 additions & 2 deletions cli/command/plugin/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
options := pluginCreateOptions{}

cmd := &cobra.Command{
Use: "create [OPTIONS] PLUGIN[:tag] PATH-TO-ROOTFS(rootfs + config.json)",
Short: "Create a plugin from a rootfs and config",
Use: "create [OPTIONS] PLUGIN PLUGIN-DATA-DIR",
Short: "Create a plugin from a rootfs and configuration. Plugin data directory must contain config.json and rootfs directory.",
Args: cli.RequiresMinArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
options.repoName = args[0]
Expand Down
14 changes: 1 addition & 13 deletions cli/command/plugin/disable.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/reference"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
Expand All @@ -29,18 +28,7 @@ func newDisableCommand(dockerCli *command.DockerCli) *cobra.Command {
}

func runDisable(dockerCli *command.DockerCli, name string, force bool) error {
named, err := reference.ParseNamed(name) // FIXME: validate
if err != nil {
return err
}
if reference.IsNameOnly(named) {
named = reference.WithDefaultTag(named)
}
ref, ok := named.(reference.NamedTagged)
if !ok {
return fmt.Errorf("invalid name: %s", named.String())
}
if err := dockerCli.Client().PluginDisable(context.Background(), ref.String(), types.PluginDisableOptions{Force: force}); err != nil {
if err := dockerCli.Client().PluginDisable(context.Background(), name, types.PluginDisableOptions{Force: force}); err != nil {
return err
}
fmt.Fprintln(dockerCli.Out(), name)
Expand Down

0 comments on commit a9fa38b

Please sign in to comment.