mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
![Brian Goff](/assets/img/avatar_default.png)
This allows a plugin to be upgraded without requiring to
uninstall/reinstall a plugin.
Since plugin resources (e.g. volumes) are tied to a plugin ID, this is
important to ensure resources aren't lost.
The plugin must be disabled while upgrading (errors out if enabled).
This does not add any convenience flags for automatically
disabling/re-enabling the plugin during before/after upgrade.
Since an upgrade may change requested permissions, the user is required
to accept permissions just like `docker plugin install`.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit 03c6949739
)
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
208 lines
6.1 KiB
Go
208 lines
6.1 KiB
Go
package plugin
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
distreference "github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/api/types"
|
|
registrytypes "github.com/docker/docker/api/types/registry"
|
|
"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/reference"
|
|
"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.AddTrustedFlags(flags, true)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func getRepoIndexFromUnnormalizedRef(ref distreference.Named) (*registrytypes.IndexInfo, error) {
|
|
named, err := reference.ParseNamed(ref.Name())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repoInfo, err := registry.ParseRepositoryInfo(named)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return repoInfo.Index, nil
|
|
}
|
|
|
|
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) {
|
|
// Parse name using distribution reference package to support name
|
|
// containing both tag and digest. Names with both tag and digest
|
|
// will be treated by the daemon as a pull by digest with
|
|
// an alias for the tag (if no alias is provided).
|
|
ref, err := distreference.ParseNamed(opts.remote)
|
|
if err != nil {
|
|
return types.PluginInstallOptions{}, err
|
|
}
|
|
|
|
index, err := getRepoIndexFromUnnormalizedRef(ref)
|
|
if err != nil {
|
|
return types.PluginInstallOptions{}, err
|
|
}
|
|
|
|
repoInfoIndex, err := getRepoIndexFromUnnormalizedRef(ref)
|
|
if err != nil {
|
|
return types.PluginInstallOptions{}, err
|
|
}
|
|
remote := ref.String()
|
|
|
|
_, isCanonical := ref.(distreference.Canonical)
|
|
if command.IsTrusted() && !isCanonical {
|
|
var nt reference.NamedTagged
|
|
named, err := reference.ParseNamed(ref.Name())
|
|
if err != nil {
|
|
return types.PluginInstallOptions{}, err
|
|
}
|
|
if tagged, ok := ref.(distreference.Tagged); ok {
|
|
nt, err = reference.WithTag(named, tagged.Tag())
|
|
if err != nil {
|
|
return types.PluginInstallOptions{}, err
|
|
}
|
|
} else {
|
|
named = reference.WithDefaultTag(named)
|
|
nt = named.(reference.NamedTagged)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
trusted, err := image.TrustedReference(ctx, dockerCli, nt, newRegistryService())
|
|
if err != nil {
|
|
return types.PluginInstallOptions{}, err
|
|
}
|
|
remote = trusted.String()
|
|
}
|
|
|
|
authConfig := command.ResolveAuthConfig(ctx, dockerCli, index)
|
|
|
|
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
|
|
if err != nil {
|
|
return types.PluginInstallOptions{}, err
|
|
}
|
|
|
|
registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfoIndex, 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.ParseNamed(opts.localName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aref = reference.WithDefaultTag(aref)
|
|
if _, ok := aref.(reference.NamedTagged); !ok {
|
|
return fmt.Errorf("invalid name: %s", opts.localName)
|
|
}
|
|
localName = aref.String()
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
fmt.Fprint(dockerCli.Out(), "Do you grant the above permissions? [y/N] ")
|
|
reader := bufio.NewReader(dockerCli.In())
|
|
line, _, err := reader.ReadLine()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return strings.ToLower(string(line)) == "y", nil
|
|
}
|
|
}
|