github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/plugin/install.go (about)

     1  package plugin
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/docker/cli/cli"
     9  	"github.com/docker/cli/cli/command"
    10  	"github.com/docker/cli/cli/command/image"
    11  	"github.com/docker/distribution/reference"
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/pkg/jsonmessage"
    14  	"github.com/docker/docker/registry"
    15  	"github.com/pkg/errors"
    16  	"github.com/spf13/cobra"
    17  	"github.com/spf13/pflag"
    18  )
    19  
    20  type pluginOptions struct {
    21  	remote          string
    22  	localName       string
    23  	grantPerms      bool
    24  	disable         bool
    25  	args            []string
    26  	skipRemoteCheck bool
    27  	untrusted       bool
    28  }
    29  
    30  func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) {
    31  	flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin")
    32  	command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
    33  }
    34  
    35  func newInstallCommand(dockerCli command.Cli) *cobra.Command {
    36  	var options pluginOptions
    37  	cmd := &cobra.Command{
    38  		Use:   "install [OPTIONS] PLUGIN [KEY=VALUE...]",
    39  		Short: "Install a plugin",
    40  		Args:  cli.RequiresMinArgs(1),
    41  		RunE: func(cmd *cobra.Command, args []string) error {
    42  			options.remote = args[0]
    43  			if len(args) > 1 {
    44  				options.args = args[1:]
    45  			}
    46  			return runInstall(dockerCli, options)
    47  		},
    48  	}
    49  
    50  	flags := cmd.Flags()
    51  	loadPullFlags(dockerCli, &options, flags)
    52  	flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
    53  	flags.StringVar(&options.localName, "alias", "", "Local name for plugin")
    54  	return cmd
    55  }
    56  
    57  type pluginRegistryService struct {
    58  	registry.Service
    59  }
    60  
    61  func (s pluginRegistryService) ResolveRepository(name reference.Named) (*registry.RepositoryInfo, error) {
    62  	repoInfo, err := s.Service.ResolveRepository(name)
    63  	if repoInfo != nil {
    64  		repoInfo.Class = "plugin"
    65  	}
    66  	return repoInfo, err
    67  }
    68  
    69  func newRegistryService() (registry.Service, error) {
    70  	svc, err := registry.NewService(registry.ServiceOptions{})
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	return pluginRegistryService{Service: svc}, nil
    75  }
    76  
    77  func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOptions, cmdName string) (types.PluginInstallOptions, error) {
    78  	// Names with both tag and digest will be treated by the daemon
    79  	// as a pull by digest with a local name for the tag
    80  	// (if no local name is provided).
    81  	ref, err := reference.ParseNormalizedNamed(opts.remote)
    82  	if err != nil {
    83  		return types.PluginInstallOptions{}, err
    84  	}
    85  
    86  	repoInfo, err := registry.ParseRepositoryInfo(ref)
    87  	if err != nil {
    88  		return types.PluginInstallOptions{}, err
    89  	}
    90  
    91  	remote := ref.String()
    92  
    93  	_, isCanonical := ref.(reference.Canonical)
    94  	if !opts.untrusted && !isCanonical {
    95  		ref = reference.TagNameOnly(ref)
    96  		nt, ok := ref.(reference.NamedTagged)
    97  		if !ok {
    98  			return types.PluginInstallOptions{}, errors.Errorf("invalid name: %s", ref.String())
    99  		}
   100  
   101  		ctx := context.Background()
   102  		svc, err := newRegistryService()
   103  		if err != nil {
   104  			return types.PluginInstallOptions{}, err
   105  		}
   106  		trusted, err := image.TrustedReference(ctx, dockerCli, nt, svc)
   107  		if err != nil {
   108  			return types.PluginInstallOptions{}, err
   109  		}
   110  		remote = reference.FamiliarString(trusted)
   111  	}
   112  
   113  	authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
   114  
   115  	encodedAuth, err := command.EncodeAuthToBase64(authConfig)
   116  	if err != nil {
   117  		return types.PluginInstallOptions{}, err
   118  	}
   119  	registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, cmdName)
   120  
   121  	options := types.PluginInstallOptions{
   122  		RegistryAuth:          encodedAuth,
   123  		RemoteRef:             remote,
   124  		Disabled:              opts.disable,
   125  		AcceptAllPermissions:  opts.grantPerms,
   126  		AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.remote),
   127  		PrivilegeFunc:         registryAuthFunc,
   128  		Args:                  opts.args,
   129  	}
   130  	return options, nil
   131  }
   132  
   133  func runInstall(dockerCli command.Cli, opts pluginOptions) error {
   134  	var localName string
   135  	if opts.localName != "" {
   136  		aref, err := reference.ParseNormalizedNamed(opts.localName)
   137  		if err != nil {
   138  			return err
   139  		}
   140  		if _, ok := aref.(reference.Canonical); ok {
   141  			return errors.Errorf("invalid name: %s", opts.localName)
   142  		}
   143  		localName = reference.FamiliarString(reference.TagNameOnly(aref))
   144  	}
   145  
   146  	ctx := context.Background()
   147  	options, err := buildPullConfig(ctx, dockerCli, opts, "plugin install")
   148  	if err != nil {
   149  		return err
   150  	}
   151  	responseBody, err := dockerCli.Client().PluginInstall(ctx, localName, options)
   152  	if err != nil {
   153  		if strings.Contains(err.Error(), "(image) when fetching") {
   154  			return errors.New(err.Error() + " - Use \"docker image pull\"")
   155  		}
   156  		return err
   157  	}
   158  	defer responseBody.Close()
   159  	if err := jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil); err != nil {
   160  		return err
   161  	}
   162  	fmt.Fprintf(dockerCli.Out(), "Installed plugin %s\n", opts.remote) // todo: return proper values from the API for this result
   163  	return nil
   164  }
   165  
   166  func acceptPrivileges(dockerCli command.Cli, name string) func(privileges types.PluginPrivileges) (bool, error) {
   167  	return func(privileges types.PluginPrivileges) (bool, error) {
   168  		fmt.Fprintf(dockerCli.Out(), "Plugin %q is requesting the following privileges:\n", name)
   169  		for _, privilege := range privileges {
   170  			fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value)
   171  		}
   172  		return command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), "Do you grant the above permissions?"), nil
   173  	}
   174  }