github.com/olljanat/moby@v1.13.1/cli/command/plugin/install.go (about)

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