1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/cli/command/plugin/install.go
Derek McGowan 3a1279393f
Use distribution reference
Remove forked reference package. Use normalized named values
everywhere and familiar functions to convert back to familiar
strings for UX and storage compatibility.

Enforce that the source repository in the distribution metadata
is always a normalized string, ignore invalid values which are not.
Update distribution tests to use normalized values.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
2017-02-07 11:08:37 -08:00

168 lines
5.2 KiB
Go

package plugin
import (
"errors"
"fmt"
"strings"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/image"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/net/context"
)
type pluginOptions struct {
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)
}
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.remote = args[0]
if len(args) > 1 {
options.args = args[1:]
}
return runInstall(dockerCli, options)
},
}
flags := cmd.Flags()
loadPullFlags(&options, flags)
flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
flags.StringVar(&options.localName, "alias", "", "Local name for plugin")
return cmd
}
type pluginRegistryService struct {
registry.Service
}
func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) {
repoInfo, err = s.Service.ResolveRepository(name)
if repoInfo != nil {
repoInfo.Class = "plugin"
}
return
}
func newRegistryService() registry.Service {
return pluginRegistryService{
Service: registry.NewService(registry.ServiceOptions{V2Only: true}),
}
}
func buildPullConfig(ctx context.Context, dockerCli *command.DockerCli, opts pluginOptions, cmdName string) (types.PluginInstallOptions, error) {
// Names with both tag and digest will be treated by the daemon
// as a pull by digest with a local name for the tag
// (if no local name is provided).
ref, err := reference.ParseNormalizedNamed(opts.remote)
if err != nil {
return types.PluginInstallOptions{}, err
}
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return types.PluginInstallOptions{}, err
}
remote := ref.String()
_, isCanonical := ref.(reference.Canonical)
if command.IsTrusted() && !isCanonical {
ref = reference.TagNameOnly(ref)
nt, ok := ref.(reference.NamedTagged)
if !ok {
return types.PluginInstallOptions{}, fmt.Errorf("invalid name: %s", ref.String())
}
ctx := context.Background()
trusted, err := image.TrustedReference(ctx, dockerCli, nt, newRegistryService())
if err != nil {
return types.PluginInstallOptions{}, err
}
remote = reference.FamiliarString(trusted)
}
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil {
return types.PluginInstallOptions{}, err
}
registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, cmdName)
options := types.PluginInstallOptions{
RegistryAuth: encodedAuth,
RemoteRef: remote,
Disabled: opts.disable,
AcceptAllPermissions: opts.grantPerms,
AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.remote),
// TODO: Rename PrivilegeFunc, it has nothing to do with privileges
PrivilegeFunc: registryAuthFunc,
Args: opts.args,
}
return options, nil
}
func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
var localName string
if opts.localName != "" {
aref, err := reference.ParseNormalizedNamed(opts.localName)
if err != nil {
return err
}
if _, ok := aref.(reference.Canonical); ok {
return fmt.Errorf("invalid name: %s", opts.localName)
}
localName = reference.FamiliarString(reference.TagNameOnly(aref))
}
ctx := context.Background()
options, err := buildPullConfig(ctx, dockerCli, opts, "plugin install")
if err != nil {
return err
}
responseBody, err := dockerCli.Client().PluginInstall(ctx, localName, 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.remote) // 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)
}
return command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), "Do you grant the above permissions?"), nil
}
}