2016-09-08 13:11:39 -04:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
|
|
|
"github.com/docker/cli/cli/command/image"
|
2017-01-11 16:54:52 -05:00
|
|
|
"github.com/docker/distribution/reference"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
2016-12-12 18:05:53 -05:00
|
|
|
"github.com/docker/docker/pkg/jsonmessage"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/docker/docker/registry"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
2017-01-28 19:54:32 -05:00
|
|
|
"github.com/spf13/pflag"
|
2016-09-08 13:11:39 -04:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
)
|
|
|
|
|
|
|
|
type pluginOptions struct {
|
2017-01-28 19:54:32 -05:00
|
|
|
remote string
|
|
|
|
localName string
|
|
|
|
grantPerms bool
|
|
|
|
disable bool
|
|
|
|
args []string
|
|
|
|
skipRemoteCheck bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadPullFlags(opts *pluginOptions, flags *pflag.FlagSet) {
|
|
|
|
flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin")
|
|
|
|
command.AddTrustVerificationFlags(flags)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-10-11 12:18:27 -04:00
|
|
|
func newInstallCommand(dockerCli command.Cli) *cobra.Command {
|
2016-09-08 13:11:39 -04:00
|
|
|
var options pluginOptions
|
|
|
|
cmd := &cobra.Command{
|
2016-11-07 20:43:11 -05:00
|
|
|
Use: "install [OPTIONS] PLUGIN [KEY=VALUE...]",
|
2016-09-08 13:11:39 -04:00
|
|
|
Short: "Install a plugin",
|
2016-11-07 20:43:11 -05:00
|
|
|
Args: cli.RequiresMinArgs(1),
|
2016-09-08 13:11:39 -04:00
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2017-01-28 19:54:32 -05:00
|
|
|
options.remote = args[0]
|
2016-11-07 20:43:11 -05:00
|
|
|
if len(args) > 1 {
|
|
|
|
options.args = args[1:]
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
return runInstall(dockerCli, options)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
2017-01-28 19:54:32 -05:00
|
|
|
loadPullFlags(&options, flags)
|
2016-09-08 13:11:39 -04:00
|
|
|
flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
|
2017-01-28 19:54:32 -05:00
|
|
|
flags.StringVar(&options.localName, "alias", "", "Local name for plugin")
|
2016-09-08 13:11:39 -04:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2016-12-27 15:51:00 -05:00
|
|
|
type pluginRegistryService struct {
|
|
|
|
registry.Service
|
|
|
|
}
|
|
|
|
|
2017-10-12 11:44:03 -04:00
|
|
|
func (s pluginRegistryService) ResolveRepository(name reference.Named) (*registry.RepositoryInfo, error) {
|
|
|
|
repoInfo, err := s.Service.ResolveRepository(name)
|
2016-12-27 15:51:00 -05:00
|
|
|
if repoInfo != nil {
|
|
|
|
repoInfo.Class = "plugin"
|
|
|
|
}
|
2017-10-12 11:44:03 -04:00
|
|
|
return repoInfo, err
|
2016-12-27 15:51:00 -05:00
|
|
|
}
|
|
|
|
|
2017-09-29 08:18:19 -04:00
|
|
|
func newRegistryService() (registry.Service, error) {
|
|
|
|
svc, err := registry.NewService(registry.ServiceOptions{V2Only: true})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2016-12-27 15:51:00 -05:00
|
|
|
}
|
2017-09-29 08:18:19 -04:00
|
|
|
return pluginRegistryService{Service: svc}, nil
|
2016-12-27 15:51:00 -05:00
|
|
|
}
|
|
|
|
|
2017-10-11 12:18:27 -04:00
|
|
|
func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOptions, cmdName string) (types.PluginInstallOptions, error) {
|
2017-01-11 16:54:52 -05:00
|
|
|
// Names with both tag and digest will be treated by the daemon
|
2017-01-28 19:54:32 -05:00
|
|
|
// as a pull by digest with a local name for the tag
|
|
|
|
// (if no local name is provided).
|
|
|
|
ref, err := reference.ParseNormalizedNamed(opts.remote)
|
2016-12-12 18:05:53 -05:00
|
|
|
if err != nil {
|
2017-01-28 19:54:32 -05:00
|
|
|
return types.PluginInstallOptions{}, err
|
2016-12-12 18:05:53 -05:00
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-01-11 16:54:52 -05:00
|
|
|
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2017-01-28 19:54:32 -05:00
|
|
|
return types.PluginInstallOptions{}, err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2016-12-27 15:51:00 -05:00
|
|
|
remote := ref.String()
|
|
|
|
|
2017-01-11 16:54:52 -05:00
|
|
|
_, isCanonical := ref.(reference.Canonical)
|
2016-12-27 15:51:00 -05:00
|
|
|
if command.IsTrusted() && !isCanonical {
|
2017-01-25 19:54:18 -05:00
|
|
|
ref = reference.TagNameOnly(ref)
|
2017-01-11 16:54:52 -05:00
|
|
|
nt, ok := ref.(reference.NamedTagged)
|
|
|
|
if !ok {
|
2017-03-09 13:23:45 -05:00
|
|
|
return types.PluginInstallOptions{}, errors.Errorf("invalid name: %s", ref.String())
|
2016-12-27 15:51:00 -05:00
|
|
|
}
|
|
|
|
|
2017-01-28 19:54:32 -05:00
|
|
|
ctx := context.Background()
|
2017-09-29 08:18:19 -04:00
|
|
|
svc, err := newRegistryService()
|
|
|
|
if err != nil {
|
|
|
|
return types.PluginInstallOptions{}, err
|
|
|
|
}
|
|
|
|
trusted, err := image.TrustedReference(ctx, dockerCli, nt, svc)
|
2016-12-27 15:51:00 -05:00
|
|
|
if err != nil {
|
2017-01-28 19:54:32 -05:00
|
|
|
return types.PluginInstallOptions{}, err
|
2016-12-27 15:51:00 -05:00
|
|
|
}
|
2017-01-11 16:54:52 -05:00
|
|
|
remote = reference.FamiliarString(trusted)
|
2016-12-27 15:51:00 -05:00
|
|
|
}
|
2016-12-12 18:05:53 -05:00
|
|
|
|
2017-01-11 16:54:52 -05:00
|
|
|
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2016-09-08 14:54:01 -04:00
|
|
|
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2017-01-28 19:54:32 -05:00
|
|
|
return types.PluginInstallOptions{}, err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2017-01-28 19:54:32 -05:00
|
|
|
registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, cmdName)
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
options := types.PluginInstallOptions{
|
|
|
|
RegistryAuth: encodedAuth,
|
2016-12-27 15:51:00 -05:00
|
|
|
RemoteRef: remote,
|
2016-09-08 13:11:39 -04:00
|
|
|
Disabled: opts.disable,
|
|
|
|
AcceptAllPermissions: opts.grantPerms,
|
2017-01-28 19:54:32 -05:00
|
|
|
AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.remote),
|
2016-09-08 13:11:39 -04:00
|
|
|
// TODO: Rename PrivilegeFunc, it has nothing to do with privileges
|
|
|
|
PrivilegeFunc: registryAuthFunc,
|
2016-11-07 20:43:11 -05:00
|
|
|
Args: opts.args,
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2017-01-28 19:54:32 -05:00
|
|
|
return options, nil
|
|
|
|
}
|
2016-12-12 18:05:53 -05:00
|
|
|
|
2017-10-11 12:18:27 -04:00
|
|
|
func runInstall(dockerCli command.Cli, opts pluginOptions) error {
|
2017-01-28 19:54:32 -05:00
|
|
|
var localName string
|
|
|
|
if opts.localName != "" {
|
|
|
|
aref, err := reference.ParseNormalizedNamed(opts.localName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, ok := aref.(reference.Canonical); ok {
|
2017-03-09 13:23:45 -05:00
|
|
|
return errors.Errorf("invalid name: %s", opts.localName)
|
2017-01-28 19:54:32 -05:00
|
|
|
}
|
2017-01-25 19:54:18 -05:00
|
|
|
localName = reference.FamiliarString(reference.TagNameOnly(aref))
|
2017-01-28 19:54:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
options, err := buildPullConfig(ctx, dockerCli, opts, "plugin install")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
responseBody, err := dockerCli.Client().PluginInstall(ctx, localName, options)
|
2016-12-12 18:05:53 -05:00
|
|
|
if err != nil {
|
2017-01-10 22:27:55 -05:00
|
|
|
if strings.Contains(err.Error(), "(image) when fetching") {
|
2016-12-12 18:05:53 -05:00
|
|
|
return errors.New(err.Error() + " - Use `docker image pull`")
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer responseBody.Close()
|
|
|
|
if err := jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil); err != nil {
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
2017-01-28 19:54:32 -05:00
|
|
|
fmt.Fprintf(dockerCli.Out(), "Installed plugin %s\n", opts.remote) // todo: return proper values from the API for this result
|
2016-09-08 13:11:39 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-10-11 12:18:27 -04:00
|
|
|
func acceptPrivileges(dockerCli command.Cli, name string) func(privileges types.PluginPrivileges) (bool, error) {
|
2016-09-08 13:11:39 -04:00
|
|
|
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)
|
|
|
|
}
|
2017-02-03 19:07:04 -05:00
|
|
|
return command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), "Do you grant the above permissions?"), nil
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|