github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/cli/command/plugin/install.go (about)

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