From 2825296deb853f09785952796a9e0edb5092089e Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 12 Dec 2016 15:05:53 -0800 Subject: [PATCH] Implement content addressability for plugins Move plugins to shared distribution stack with images. Create immutable plugin config that matches schema2 requirements. Ensure data being pushed is same as pulled/created. Store distribution artifacts in a blobstore. Run init layer setup for every plugin start. Fix breakouts from unsafe file accesses. Add support for `docker plugin install --alias` Uses normalized references for default names to avoid collisions when using default hosts/tags. Some refactoring of the plugin manager to support the change, like removing the singleton manager and adding manager config struct. Signed-off-by: Tonis Tiigi Signed-off-by: Derek McGowan --- command/plugin/create.go | 4 +-- command/plugin/disable.go | 14 +------- command/plugin/enable.go | 15 +-------- command/plugin/install.go | 70 ++++++++++++++++++++++++++++++--------- command/plugin/list.go | 4 +-- command/plugin/push.go | 8 ++++- command/plugin/remove.go | 16 +-------- command/plugin/set.go | 20 +---------- 8 files changed, 70 insertions(+), 81 deletions(-) diff --git a/command/plugin/create.go b/command/plugin/create.go index e0041c1b88..2aab1e9e4a 100644 --- a/command/plugin/create.go +++ b/command/plugin/create.go @@ -64,8 +64,8 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { options := pluginCreateOptions{} cmd := &cobra.Command{ - Use: "create [OPTIONS] PLUGIN[:tag] PATH-TO-ROOTFS(rootfs + config.json)", - Short: "Create a plugin from a rootfs and config", + Use: "create [OPTIONS] PLUGIN PLUGIN-DATA-DIR", + Short: "Create a plugin from a rootfs and configuration. Plugin data directory must contain config.json and rootfs directory.", Args: cli.RequiresMinArgs(2), RunE: func(cmd *cobra.Command, args []string) error { options.repoName = args[0] diff --git a/command/plugin/disable.go b/command/plugin/disable.go index 5399e61f1b..c3d36e20af 100644 --- a/command/plugin/disable.go +++ b/command/plugin/disable.go @@ -6,7 +6,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" - "github.com/docker/docker/reference" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -29,18 +28,7 @@ func newDisableCommand(dockerCli *command.DockerCli) *cobra.Command { } func runDisable(dockerCli *command.DockerCli, name string, force bool) error { - named, err := reference.ParseNamed(name) // FIXME: validate - if err != nil { - return err - } - if reference.IsNameOnly(named) { - named = reference.WithDefaultTag(named) - } - ref, ok := named.(reference.NamedTagged) - if !ok { - return fmt.Errorf("invalid name: %s", named.String()) - } - if err := dockerCli.Client().PluginDisable(context.Background(), ref.String(), types.PluginDisableOptions{Force: force}); err != nil { + if err := dockerCli.Client().PluginDisable(context.Background(), name, types.PluginDisableOptions{Force: force}); err != nil { return err } fmt.Fprintln(dockerCli.Out(), name) diff --git a/command/plugin/enable.go b/command/plugin/enable.go index 9201e38e11..77762f4024 100644 --- a/command/plugin/enable.go +++ b/command/plugin/enable.go @@ -6,7 +6,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" - "github.com/docker/docker/reference" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -36,23 +35,11 @@ func newEnableCommand(dockerCli *command.DockerCli) *cobra.Command { func runEnable(dockerCli *command.DockerCli, opts *enableOpts) error { name := opts.name - - named, err := reference.ParseNamed(name) // FIXME: validate - if err != nil { - return err - } - if reference.IsNameOnly(named) { - named = reference.WithDefaultTag(named) - } - ref, ok := named.(reference.NamedTagged) - if !ok { - return fmt.Errorf("invalid name: %s", named.String()) - } if opts.timeout < 0 { return fmt.Errorf("negative timeout %d is invalid", opts.timeout) } - if err := dockerCli.Client().PluginEnable(context.Background(), ref.String(), types.PluginEnableOptions{Timeout: opts.timeout}); err != nil { + if err := dockerCli.Client().PluginEnable(context.Background(), name, types.PluginEnableOptions{Timeout: opts.timeout}); err != nil { return err } fmt.Fprintln(dockerCli.Out(), name) diff --git a/command/plugin/install.go b/command/plugin/install.go index eae0183671..71bdeeff22 100644 --- a/command/plugin/install.go +++ b/command/plugin/install.go @@ -2,12 +2,16 @@ 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/pkg/jsonmessage" "github.com/docker/docker/reference" "github.com/docker/docker/registry" "github.com/spf13/cobra" @@ -16,6 +20,7 @@ import ( type pluginOptions struct { name string + alias string grantPerms bool disable bool args []string @@ -39,41 +44,67 @@ func newInstallCommand(dockerCli *command.DockerCli) *cobra.Command { flags := cmd.Flags() flags.BoolVar(&options.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin") flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install") + flags.StringVar(&options.alias, "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 +} + func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error { - named, err := reference.ParseNamed(opts.name) // FIXME: validate + // 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.name) if err != nil { return err } - if reference.IsNameOnly(named) { - named = reference.WithDefaultTag(named) + + alias := "" + if opts.alias != "" { + aref, err := reference.ParseNamed(opts.alias) + if err != nil { + return err + } + aref = reference.WithDefaultTag(aref) + if _, ok := aref.(reference.NamedTagged); !ok { + return fmt.Errorf("invalid name: %s", opts.alias) + } + alias = aref.String() } - ref, ok := named.(reference.NamedTagged) - if !ok { - return fmt.Errorf("invalid name: %s", named.String()) + + index, err := getRepoIndexFromUnnormalizedRef(ref) + if err != nil { + return err } ctx := context.Background() - repoInfo, err := registry.ParseRepositoryInfo(named) - if err != nil { - return err - } - - authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) + authConfig := command.ResolveAuthConfig(ctx, dockerCli, index) encodedAuth, err := command.EncodeAuthToBase64(authConfig) if err != nil { return err } - registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "plugin install") + registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, index, "plugin install") options := types.PluginInstallOptions{ RegistryAuth: encodedAuth, + RemoteRef: ref.String(), Disabled: opts.disable, AcceptAllPermissions: opts.grantPerms, AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name), @@ -81,10 +112,19 @@ func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error { PrivilegeFunc: registryAuthFunc, Args: opts.args, } - if err := dockerCli.Client().PluginInstall(ctx, ref.String(), options); err != nil { + + responseBody, err := dockerCli.Client().PluginInstall(ctx, alias, options) + if err != nil { + if strings.Contains(err.Error(), "target is image") { + return errors.New(err.Error() + " - Use `docker image pull`") + } return err } - fmt.Fprintln(dockerCli.Out(), opts.name) + defer responseBody.Close() + if err := jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil); err != nil { + return err + } + fmt.Fprintf(dockerCli.Out(), "Installed plugin %s\n", opts.name) // todo: return proper values from the API for this result return nil } diff --git a/command/plugin/list.go b/command/plugin/list.go index 4f800d7ec1..8fd16dae3f 100644 --- a/command/plugin/list.go +++ b/command/plugin/list.go @@ -44,7 +44,7 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error { } w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0) - fmt.Fprintf(w, "ID \tNAME \tTAG \tDESCRIPTION\tENABLED") + fmt.Fprintf(w, "ID \tNAME \tDESCRIPTION\tENABLED") fmt.Fprintf(w, "\n") for _, p := range plugins { @@ -56,7 +56,7 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error { desc = stringutils.Ellipsis(desc, 45) } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%v\n", id, p.Name, p.Tag, desc, p.Enabled) + fmt.Fprintf(w, "%s\t%s\t%s\t%v\n", id, p.Name, desc, p.Enabled) } w.Flush() return nil diff --git a/command/plugin/push.go b/command/plugin/push.go index add4a2b0a6..667379cdd2 100644 --- a/command/plugin/push.go +++ b/command/plugin/push.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" + "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/reference" "github.com/docker/docker/registry" "github.com/spf13/cobra" @@ -49,5 +50,10 @@ func runPush(dockerCli *command.DockerCli, name string) error { if err != nil { return err } - return dockerCli.Client().PluginPush(ctx, ref.String(), encodedAuth) + responseBody, err := dockerCli.Client().PluginPush(ctx, ref.String(), encodedAuth) + if err != nil { + return err + } + defer responseBody.Close() + return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil) } diff --git a/command/plugin/remove.go b/command/plugin/remove.go index 7a51dce06d..9f3aba9a01 100644 --- a/command/plugin/remove.go +++ b/command/plugin/remove.go @@ -6,7 +6,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" - "github.com/docker/docker/reference" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -41,21 +40,8 @@ func runRemove(dockerCli *command.DockerCli, opts *rmOptions) error { var errs cli.Errors for _, name := range opts.plugins { - named, err := reference.ParseNamed(name) // FIXME: validate - if err != nil { - errs = append(errs, err) - continue - } - if reference.IsNameOnly(named) { - named = reference.WithDefaultTag(named) - } - ref, ok := named.(reference.NamedTagged) - if !ok { - errs = append(errs, fmt.Errorf("invalid name: %s", named.String())) - continue - } // TODO: pass names to api instead of making multiple api calls - if err := dockerCli.Client().PluginRemove(ctx, ref.String(), types.PluginRemoveOptions{Force: opts.force}); err != nil { + if err := dockerCli.Client().PluginRemove(ctx, name, types.PluginRemoveOptions{Force: opts.force}); err != nil { errs = append(errs, err) continue } diff --git a/command/plugin/set.go b/command/plugin/set.go index 5660523ed9..52b09fb500 100644 --- a/command/plugin/set.go +++ b/command/plugin/set.go @@ -1,13 +1,10 @@ package plugin import ( - "fmt" - "golang.org/x/net/context" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" - "github.com/docker/docker/reference" "github.com/spf13/cobra" ) @@ -17,24 +14,9 @@ func newSetCommand(dockerCli *command.DockerCli) *cobra.Command { Short: "Change settings for a plugin", Args: cli.RequiresMinArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - return runSet(dockerCli, args[0], args[1:]) + return dockerCli.Client().PluginSet(context.Background(), args[0], args[1:]) }, } return cmd } - -func runSet(dockerCli *command.DockerCli, name string, args []string) error { - named, err := reference.ParseNamed(name) // FIXME: validate - if err != nil { - return err - } - if reference.IsNameOnly(named) { - named = reference.WithDefaultTag(named) - } - ref, ok := named.(reference.NamedTagged) - if !ok { - return fmt.Errorf("invalid name: %s", named.String()) - } - return dockerCli.Client().PluginSet(context.Background(), ref.String(), args) -}