Files
docker-cli/command/plugin/install.go
Tonis Tiigi 2825296deb Implement content addressability for plugins
Move plugins to shared distribution stack with images.

Create immutable plugin config that matches schema2 requirements.

Ensure data being pushed is same as pulled/created.

Store distribution artifacts in a blobstore.

Run init layer setup for every plugin start.

Fix breakouts from unsafe file accesses.

Add support for `docker plugin install --alias`

Uses normalized references for default names to avoid collisions when using default hosts/tags.

Some refactoring of the plugin manager to support the change, like removing the singleton manager and adding manager config struct.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Signed-off-by: Derek McGowan <derek@mcgstyle.net>
2016-12-23 13:29:58 -08:00

147 lines
4.2 KiB
Go

package plugin
import (
"bufio"
"errors"
"fmt"
"strings"
distreference "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/reference"
"github.com/docker/docker/registry"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type pluginOptions struct {
name string
alias string
grantPerms bool
disable bool
args []string
}
func newInstallCommand(dockerCli *command.DockerCli) *cobra.Command {
var options pluginOptions
cmd := &cobra.Command{
Use: "install [OPTIONS] PLUGIN [KEY=VALUE...]",
Short: "Install a plugin",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
options.name = args[0]
if len(args) > 1 {
options.args = args[1:]
}
return runInstall(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVar(&options.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin")
flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
flags.StringVar(&options.alias, "alias", "", "Local name for plugin")
return cmd
}
func getRepoIndexFromUnnormalizedRef(ref distreference.Named) (*registrytypes.IndexInfo, error) {
named, err := reference.ParseNamed(ref.Name())
if err != nil {
return nil, err
}
repoInfo, err := registry.ParseRepositoryInfo(named)
if err != nil {
return nil, err
}
return repoInfo.Index, nil
}
func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
// Parse name using distribution reference package to support name
// containing both tag and digest. Names with both tag and digest
// will be treated by the daemon as a pull by digest with
// an alias for the tag (if no alias is provided).
ref, err := distreference.ParseNamed(opts.name)
if err != nil {
return err
}
alias := ""
if opts.alias != "" {
aref, err := reference.ParseNamed(opts.alias)
if err != nil {
return err
}
aref = reference.WithDefaultTag(aref)
if _, ok := aref.(reference.NamedTagged); !ok {
return fmt.Errorf("invalid name: %s", opts.alias)
}
alias = aref.String()
}
index, err := getRepoIndexFromUnnormalizedRef(ref)
if err != nil {
return err
}
ctx := context.Background()
authConfig := command.ResolveAuthConfig(ctx, dockerCli, index)
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil {
return err
}
registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, index, "plugin install")
options := types.PluginInstallOptions{
RegistryAuth: encodedAuth,
RemoteRef: ref.String(),
Disabled: opts.disable,
AcceptAllPermissions: opts.grantPerms,
AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name),
// TODO: Rename PrivilegeFunc, it has nothing to do with privileges
PrivilegeFunc: registryAuthFunc,
Args: opts.args,
}
responseBody, err := dockerCli.Client().PluginInstall(ctx, alias, options)
if err != nil {
if strings.Contains(err.Error(), "target is image") {
return errors.New(err.Error() + " - Use `docker image pull`")
}
return err
}
defer responseBody.Close()
if err := jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil); err != nil {
return err
}
fmt.Fprintf(dockerCli.Out(), "Installed plugin %s\n", opts.name) // todo: return proper values from the API for this result
return nil
}
func acceptPrivileges(dockerCli *command.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) {
return func(privileges types.PluginPrivileges) (bool, error) {
fmt.Fprintf(dockerCli.Out(), "Plugin %q is requesting the following privileges:\n", name)
for _, privilege := range privileges {
fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value)
}
fmt.Fprint(dockerCli.Out(), "Do you grant the above permissions? [y/N] ")
reader := bufio.NewReader(dockerCli.In())
line, _, err := reader.ReadLine()
if err != nil {
return false, err
}
return strings.ToLower(string(line)) == "y", nil
}
}