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 }