Merge pull request #5902 from thaJeztah/cli_plugin_metadata

move cli-plugins metadata types/consts to a separate package
This commit is contained in:
Sebastiaan van Stijn 2025-03-07 19:13:02 +01:00 committed by GitHub
commit e201b4e8a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 174 additions and 103 deletions

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -97,7 +97,7 @@ func main() {
cmd.AddCommand(goodbye, apiversion, exitStatus2) cmd.AddCommand(goodbye, apiversion, exitStatus2)
return cmd return cmd
}, },
manager.Metadata{ metadata.Metadata{
SchemaVersion: "0.1.0", SchemaVersion: "0.1.0",
Vendor: "Docker Inc.", Vendor: "Docker Inc.",
Version: "testing", Version: "testing",

View File

@ -0,0 +1,30 @@
package manager
import "github.com/docker/cli/cli-plugins/metadata"
const (
// CommandAnnotationPlugin is added to every stub command added by
// AddPluginCommandStubs with the value "true" and so can be
// used to distinguish plugin stubs from regular commands.
CommandAnnotationPlugin = metadata.CommandAnnotationPlugin
// CommandAnnotationPluginVendor is added to every stub command
// added by AddPluginCommandStubs and contains the vendor of
// that plugin.
CommandAnnotationPluginVendor = metadata.CommandAnnotationPluginVendor
// CommandAnnotationPluginVersion is added to every stub command
// added by AddPluginCommandStubs and contains the version of
// that plugin.
CommandAnnotationPluginVersion = metadata.CommandAnnotationPluginVersion
// CommandAnnotationPluginInvalid is added to any stub command
// added by AddPluginCommandStubs for an invalid command (that
// is, one which failed it's candidate test) and contains the
// reason for the failure.
CommandAnnotationPluginInvalid = metadata.CommandAnnotationPluginInvalid
// CommandAnnotationPluginCommandPath is added to overwrite the
// command path for a plugin invocation.
CommandAnnotationPluginCommandPath = metadata.CommandAnnotationPluginCommandPath
)

View File

@ -1,6 +1,10 @@
package manager package manager
import "os/exec" import (
"os/exec"
"github.com/docker/cli/cli-plugins/metadata"
)
// Candidate represents a possible plugin candidate, for mocking purposes // Candidate represents a possible plugin candidate, for mocking purposes
type Candidate interface { type Candidate interface {
@ -17,5 +21,5 @@ func (c *candidate) Path() string {
} }
func (c *candidate) Metadata() ([]byte, error) { func (c *candidate) Metadata() ([]byte, error) {
return exec.Command(c.path, MetadataSubcommandName).Output() // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments" return exec.Command(c.path, metadata.MetadataSubcommandName).Output() // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
} }

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
"gotest.tools/v3/assert/cmp" "gotest.tools/v3/assert/cmp"
@ -30,10 +31,10 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
func TestValidateCandidate(t *testing.T) { func TestValidateCandidate(t *testing.T) {
const ( const (
goodPluginName = NamePrefix + "goodplugin" goodPluginName = metadata.NamePrefix + "goodplugin"
builtinName = NamePrefix + "builtin" builtinName = metadata.NamePrefix + "builtin"
builtinAlias = NamePrefix + "alias" builtinAlias = metadata.NamePrefix + "alias"
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble" badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456" badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
@ -43,9 +44,9 @@ func TestValidateCandidate(t *testing.T) {
fakeroot := &cobra.Command{Use: "docker"} fakeroot := &cobra.Command{Use: "docker"}
fakeroot.AddCommand(&cobra.Command{ fakeroot.AddCommand(&cobra.Command{
Use: strings.TrimPrefix(builtinName, NamePrefix), Use: strings.TrimPrefix(builtinName, metadata.NamePrefix),
Aliases: []string{ Aliases: []string{
strings.TrimPrefix(builtinAlias, NamePrefix), strings.TrimPrefix(builtinAlias, metadata.NamePrefix),
}, },
}) })
@ -59,7 +60,7 @@ func TestValidateCandidate(t *testing.T) {
}{ }{
/* Each failing one of the tests */ /* Each failing one of the tests */
{name: "empty path", c: &fakeCandidate{path: ""}, err: "plugin candidate path cannot be empty"}, {name: "empty path", c: &fakeCandidate{path: ""}, err: "plugin candidate path cannot be empty"},
{name: "bad prefix", c: &fakeCandidate{path: badPrefixPath}, err: fmt.Sprintf("does not have %q prefix", NamePrefix)}, {name: "bad prefix", c: &fakeCandidate{path: badPrefixPath}, err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix)},
{name: "bad path", c: &fakeCandidate{path: badNamePath}, invalid: "did not match"}, {name: "bad path", c: &fakeCandidate{path: badNamePath}, invalid: "did not match"},
{name: "builtin command", c: &fakeCandidate{path: builtinName}, invalid: `plugin "builtin" duplicates builtin command`}, {name: "builtin command", c: &fakeCandidate{path: builtinName}, invalid: `plugin "builtin" duplicates builtin command`},
{name: "builtin alias", c: &fakeCandidate{path: builtinAlias}, invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`}, {name: "builtin alias", c: &fakeCandidate{path: builtinAlias}, invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`},
@ -84,7 +85,7 @@ func TestValidateCandidate(t *testing.T) {
assert.ErrorContains(t, p.Err, tc.invalid) assert.ErrorContains(t, p.Err, tc.invalid)
default: default:
assert.NilError(t, err) assert.NilError(t, err)
assert.Equal(t, NamePrefix+p.Name, goodPluginName) assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName)
assert.Equal(t, p.SchemaVersion, "0.1.0") assert.Equal(t, p.SchemaVersion, "0.1.0")
assert.Equal(t, p.Vendor, "e2e-testing") assert.Equal(t, p.Vendor, "e2e-testing")
} }

View File

@ -5,37 +5,11 @@ import (
"os" "os"
"sync" "sync"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const (
// CommandAnnotationPlugin is added to every stub command added by
// AddPluginCommandStubs with the value "true" and so can be
// used to distinguish plugin stubs from regular commands.
CommandAnnotationPlugin = "com.docker.cli.plugin"
// CommandAnnotationPluginVendor is added to every stub command
// added by AddPluginCommandStubs and contains the vendor of
// that plugin.
CommandAnnotationPluginVendor = "com.docker.cli.plugin.vendor"
// CommandAnnotationPluginVersion is added to every stub command
// added by AddPluginCommandStubs and contains the version of
// that plugin.
CommandAnnotationPluginVersion = "com.docker.cli.plugin.version"
// CommandAnnotationPluginInvalid is added to any stub command
// added by AddPluginCommandStubs for an invalid command (that
// is, one which failed it's candidate test) and contains the
// reason for the failure.
CommandAnnotationPluginInvalid = "com.docker.cli.plugin-invalid"
// CommandAnnotationPluginCommandPath is added to overwrite the
// command path for a plugin invocation.
CommandAnnotationPluginCommandPath = "com.docker.cli.plugin.command_path"
)
var pluginCommandStubsOnce sync.Once var pluginCommandStubsOnce sync.Once
// AddPluginCommandStubs adds a stub cobra.Commands for each valid and invalid // AddPluginCommandStubs adds a stub cobra.Commands for each valid and invalid
@ -54,12 +28,12 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
vendor = "unknown" vendor = "unknown"
} }
annotations := map[string]string{ annotations := map[string]string{
CommandAnnotationPlugin: "true", metadata.CommandAnnotationPlugin: "true",
CommandAnnotationPluginVendor: vendor, metadata.CommandAnnotationPluginVendor: vendor,
CommandAnnotationPluginVersion: p.Version, metadata.CommandAnnotationPluginVersion: p.Version,
} }
if p.Err != nil { if p.Err != nil {
annotations[CommandAnnotationPluginInvalid] = p.Err.Error() annotations[metadata.CommandAnnotationPluginInvalid] = p.Err.Error()
} }
rootCmd.AddCommand(&cobra.Command{ rootCmd.AddCommand(&cobra.Command{
Use: p.Name, Use: p.Name,

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/configfile"
"github.com/fvbommel/sortorder" "github.com/fvbommel/sortorder"
@ -21,7 +22,7 @@ const (
// used to originally invoke the docker CLI when executing a // used to originally invoke the docker CLI when executing a
// plugin. Assuming $PATH and $CWD remain unchanged this should allow // plugin. Assuming $PATH and $CWD remain unchanged this should allow
// the plugin to re-execute the original CLI. // the plugin to re-execute the original CLI.
ReexecEnvvar = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND" ReexecEnvvar = metadata.ReexecEnvvar
// ResourceAttributesEnvvar is the name of the envvar that includes additional // ResourceAttributesEnvvar is the name of the envvar that includes additional
// resource attributes for OTEL. // resource attributes for OTEL.
@ -92,10 +93,10 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
continue continue
} }
name := dentry.Name() name := dentry.Name()
if !strings.HasPrefix(name, NamePrefix) { if !strings.HasPrefix(name, metadata.NamePrefix) {
continue continue
} }
name = strings.TrimPrefix(name, NamePrefix) name = strings.TrimPrefix(name, metadata.NamePrefix)
var err error var err error
if name, err = trimExeSuffix(name); err != nil { if name, err = trimExeSuffix(name); err != nil {
continue continue
@ -200,7 +201,7 @@ func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Com
// fallback to their "invalid" command path. // fallback to their "invalid" command path.
return nil, errPluginNotFound(name) return nil, errPluginNotFound(name)
} }
exename := addExeSuffix(NamePrefix + name) exename := addExeSuffix(metadata.NamePrefix + name)
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile()) pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
if err != nil { if err != nil {
return nil, err return nil, err
@ -237,7 +238,7 @@ func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Com
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = append(cmd.Environ(), ReexecEnvvar+"="+os.Args[0]) cmd.Env = append(cmd.Environ(), metadata.ReexecEnvvar+"="+os.Args[0])
cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin) cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin)
return cmd, nil return cmd, nil
@ -247,5 +248,5 @@ func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Com
// IsPluginCommand checks if the given cmd is a plugin-stub. // IsPluginCommand checks if the given cmd is a plugin-stub.
func IsPluginCommand(cmd *cobra.Command) bool { func IsPluginCommand(cmd *cobra.Command) bool {
return cmd.Annotations[CommandAnnotationPlugin] == "true" return cmd.Annotations[metadata.CommandAnnotationPlugin] == "true"
} }

View File

@ -1,30 +1,23 @@
package manager package manager
import (
"github.com/docker/cli/cli-plugins/metadata"
)
const ( const (
// NamePrefix is the prefix required on all plugin binary names // NamePrefix is the prefix required on all plugin binary names
NamePrefix = "docker-" NamePrefix = metadata.NamePrefix
// MetadataSubcommandName is the name of the plugin subcommand // MetadataSubcommandName is the name of the plugin subcommand
// which must be supported by every plugin and returns the // which must be supported by every plugin and returns the
// plugin metadata. // plugin metadata.
MetadataSubcommandName = "docker-cli-plugin-metadata" MetadataSubcommandName = metadata.MetadataSubcommandName
// HookSubcommandName is the name of the plugin subcommand // HookSubcommandName is the name of the plugin subcommand
// which must be implemented by plugins declaring support // which must be implemented by plugins declaring support
// for hooks in their metadata. // for hooks in their metadata.
HookSubcommandName = "docker-cli-plugin-hooks" HookSubcommandName = metadata.HookSubcommandName
) )
// Metadata provided by the plugin. // Metadata provided by the plugin.
type Metadata struct { type Metadata = metadata.Metadata
// SchemaVersion describes the version of this struct. Mandatory, must be "0.1.0"
SchemaVersion string `json:",omitempty"`
// Vendor is the name of the plugin vendor. Mandatory
Vendor string `json:",omitempty"`
// Version is the optional version of this plugin.
Version string `json:",omitempty"`
// ShortDescription should be suitable for a single line help message.
ShortDescription string `json:",omitempty"`
// URL is a pointer to the plugin's homepage.
URL string `json:",omitempty"`
}

View File

@ -9,6 +9,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -17,7 +18,7 @@ var pluginNameRe = regexp.MustCompile("^[a-z][a-z0-9]*$")
// Plugin represents a potential plugin with all it's metadata. // Plugin represents a potential plugin with all it's metadata.
type Plugin struct { type Plugin struct {
Metadata metadata.Metadata
Name string `json:",omitempty"` Name string `json:",omitempty"`
Path string `json:",omitempty"` Path string `json:",omitempty"`
@ -50,12 +51,12 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
if fullname, err = trimExeSuffix(fullname); err != nil { if fullname, err = trimExeSuffix(fullname); err != nil {
return Plugin{}, errors.Wrapf(err, "plugin candidate %q", path) return Plugin{}, errors.Wrapf(err, "plugin candidate %q", path)
} }
if !strings.HasPrefix(fullname, NamePrefix) { if !strings.HasPrefix(fullname, metadata.NamePrefix) {
return Plugin{}, errors.Errorf("plugin candidate %q: does not have %q prefix", path, NamePrefix) return Plugin{}, errors.Errorf("plugin candidate %q: does not have %q prefix", path, metadata.NamePrefix)
} }
p := Plugin{ p := Plugin{
Name: strings.TrimPrefix(fullname, NamePrefix), Name: strings.TrimPrefix(fullname, metadata.NamePrefix),
Path: path, Path: path,
} }
@ -112,9 +113,9 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
return nil, wrapAsPluginError(err, "failed to marshall hook data") return nil, wrapAsPluginError(err, "failed to marshall hook data")
} }
pCmd := exec.CommandContext(ctx, p.Path, p.Name, HookSubcommandName, string(hDataBytes)) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments" pCmd := exec.CommandContext(ctx, p.Path, p.Name, metadata.HookSubcommandName, string(hDataBytes)) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
pCmd.Env = os.Environ() pCmd.Env = os.Environ()
pCmd.Env = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0]) pCmd.Env = append(pCmd.Env, metadata.ReexecEnvvar+"="+os.Args[0])
hookCmdOutput, err := pCmd.Output() hookCmdOutput, err := pCmd.Output()
if err != nil { if err != nil {
return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand") return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand")

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
@ -26,7 +27,7 @@ const (
) )
func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set { func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set {
commandPath := cmd.Annotations[CommandAnnotationPluginCommandPath] commandPath := cmd.Annotations[metadata.CommandAnnotationPluginCommandPath]
if commandPath == "" { if commandPath == "" {
commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name) commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name)
} }

View File

@ -0,0 +1,28 @@
package metadata
const (
// CommandAnnotationPlugin is added to every stub command added by
// AddPluginCommandStubs with the value "true" and so can be
// used to distinguish plugin stubs from regular commands.
CommandAnnotationPlugin = "com.docker.cli.plugin"
// CommandAnnotationPluginVendor is added to every stub command
// added by AddPluginCommandStubs and contains the vendor of
// that plugin.
CommandAnnotationPluginVendor = "com.docker.cli.plugin.vendor"
// CommandAnnotationPluginVersion is added to every stub command
// added by AddPluginCommandStubs and contains the version of
// that plugin.
CommandAnnotationPluginVersion = "com.docker.cli.plugin.version"
// CommandAnnotationPluginInvalid is added to any stub command
// added by AddPluginCommandStubs for an invalid command (that
// is, one which failed it's candidate test) and contains the
// reason for the failure.
CommandAnnotationPluginInvalid = "com.docker.cli.plugin-invalid"
// CommandAnnotationPluginCommandPath is added to overwrite the
// command path for a plugin invocation.
CommandAnnotationPluginCommandPath = "com.docker.cli.plugin.command_path"
)

View File

@ -0,0 +1,36 @@
package metadata
const (
// NamePrefix is the prefix required on all plugin binary names
NamePrefix = "docker-"
// MetadataSubcommandName is the name of the plugin subcommand
// which must be supported by every plugin and returns the
// plugin metadata.
MetadataSubcommandName = "docker-cli-plugin-metadata"
// HookSubcommandName is the name of the plugin subcommand
// which must be implemented by plugins declaring support
// for hooks in their metadata.
HookSubcommandName = "docker-cli-plugin-hooks"
// ReexecEnvvar is the name of an ennvar which is set to the command
// used to originally invoke the docker CLI when executing a
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
// the plugin to re-execute the original CLI.
ReexecEnvvar = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"
)
// Metadata provided by the plugin.
type Metadata struct {
// SchemaVersion describes the version of this struct. Mandatory, must be "0.1.0"
SchemaVersion string `json:",omitempty"`
// Vendor is the name of the plugin vendor. Mandatory
Vendor string `json:",omitempty"`
// Version is the optional version of this plugin.
Version string `json:",omitempty"`
// ShortDescription should be suitable for a single line help message.
ShortDescription string `json:",omitempty"`
// URL is a pointer to the plugin's homepage.
URL string `json:",omitempty"`
}

View File

@ -9,7 +9,7 @@ import (
"sync" "sync"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli-plugins/socket" "github.com/docker/cli/cli-plugins/socket"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/connhelper" "github.com/docker/cli/cli/connhelper"
@ -30,7 +30,7 @@ import (
var PersistentPreRunE func(*cobra.Command, []string) error var PersistentPreRunE func(*cobra.Command, []string) error
// RunPlugin executes the specified plugin command // RunPlugin executes the specified plugin command
func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) error { func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadata.Metadata) error {
tcmd := newPluginCommand(dockerCli, plugin, meta) tcmd := newPluginCommand(dockerCli, plugin, meta)
var persistentPreRunOnce sync.Once var persistentPreRunOnce sync.Once
@ -81,7 +81,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
} }
// Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function. // Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function.
func Run(makeCmd func(command.Cli) *cobra.Command, meta manager.Metadata) { func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
otel.SetErrorHandler(debug.OTELErrorHandler) otel.SetErrorHandler(debug.OTELErrorHandler)
dockerCli, err := command.NewDockerCli() dockerCli, err := command.NewDockerCli()
@ -111,7 +111,7 @@ func Run(makeCmd func(command.Cli) *cobra.Command, meta manager.Metadata) {
func withPluginClientConn(name string) command.CLIOption { func withPluginClientConn(name string) command.CLIOption {
return command.WithInitializeClient(func(dockerCli *command.DockerCli) (client.APIClient, error) { return command.WithInitializeClient(func(dockerCli *command.DockerCli) (client.APIClient, error) {
cmd := "docker" cmd := "docker"
if x := os.Getenv(manager.ReexecEnvvar); x != "" { if x := os.Getenv(metadata.ReexecEnvvar); x != "" {
cmd = x cmd = x
} }
var flags []string var flags []string
@ -140,9 +140,9 @@ func withPluginClientConn(name string) command.CLIOption {
}) })
} }
func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) *cli.TopLevelCommand { func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadata.Metadata) *cli.TopLevelCommand {
name := plugin.Name() name := plugin.Name()
fullname := manager.NamePrefix + name fullname := metadata.NamePrefix + name
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: fmt.Sprintf("docker [OPTIONS] %s [ARG...]", name), Use: fmt.Sprintf("docker [OPTIONS] %s [ARG...]", name),
@ -177,12 +177,12 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags()) return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags())
} }
func newMetadataSubcommand(plugin *cobra.Command, meta manager.Metadata) *cobra.Command { func newMetadataSubcommand(plugin *cobra.Command, meta metadata.Metadata) *cobra.Command {
if meta.ShortDescription == "" { if meta.ShortDescription == "" {
meta.ShortDescription = plugin.Short meta.ShortDescription = plugin.Short
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: manager.MetadataSubcommandName, Use: metadata.MetadataSubcommandName,
Hidden: true, Hidden: true,
// Suppress the global/parent PersistentPreRunE, which // Suppress the global/parent PersistentPreRunE, which
// needlessly initializes the client and tries to // needlessly initializes the client and tries to
@ -200,8 +200,8 @@ func newMetadataSubcommand(plugin *cobra.Command, meta manager.Metadata) *cobra.
// RunningStandalone tells a CLI plugin it is run standalone by direct execution // RunningStandalone tells a CLI plugin it is run standalone by direct execution
func RunningStandalone() bool { func RunningStandalone() bool {
if os.Getenv(manager.ReexecEnvvar) != "" { if os.Getenv(metadata.ReexecEnvvar) != "" {
return false return false
} }
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName
} }

View File

@ -7,7 +7,7 @@ import (
"sort" "sort"
"strings" "strings"
pluginmanager "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
cliflags "github.com/docker/cli/cli/flags" cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/homedir"
@ -252,7 +252,7 @@ func hasAdditionalHelp(cmd *cobra.Command) bool {
} }
func isPlugin(cmd *cobra.Command) bool { func isPlugin(cmd *cobra.Command) bool {
return pluginmanager.IsPluginCommand(cmd) return cmd.Annotations[metadata.CommandAnnotationPlugin] == "true"
} }
func hasAliases(cmd *cobra.Command) bool { func hasAliases(cmd *cobra.Command) bool {
@ -356,9 +356,9 @@ func decoratedName(cmd *cobra.Command) string {
} }
func vendorAndVersion(cmd *cobra.Command) string { func vendorAndVersion(cmd *cobra.Command) string {
if vendor, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVendor]; ok && isPlugin(cmd) { if vendor, ok := cmd.Annotations[metadata.CommandAnnotationPluginVendor]; ok && isPlugin(cmd) {
version := "" version := ""
if v, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVersion]; ok && v != "" { if v, ok := cmd.Annotations[metadata.CommandAnnotationPluginVersion]; ok && v != "" {
version = ", " + v version = ", " + v
} }
return fmt.Sprintf("(%s%s)", vendor, version) return fmt.Sprintf("(%s%s)", vendor, version)
@ -417,7 +417,7 @@ func invalidPlugins(cmd *cobra.Command) []*cobra.Command {
} }
func invalidPluginReason(cmd *cobra.Command) string { func invalidPluginReason(cmd *cobra.Command) string {
return cmd.Annotations[pluginmanager.CommandAnnotationPluginInvalid] return cmd.Annotations[metadata.CommandAnnotationPluginInvalid]
} }
const usageTemplate = `Usage: const usageTemplate = `Usage:

View File

@ -3,7 +3,7 @@ package cli
import ( import (
"testing" "testing"
pluginmanager "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
@ -49,9 +49,9 @@ func TestVendorAndVersion(t *testing.T) {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "test", Use: "test",
Annotations: map[string]string{ Annotations: map[string]string{
pluginmanager.CommandAnnotationPlugin: "true", metadata.CommandAnnotationPlugin: "true",
pluginmanager.CommandAnnotationPluginVendor: tc.vendor, metadata.CommandAnnotationPluginVendor: tc.vendor,
pluginmanager.CommandAnnotationPluginVersion: tc.version, metadata.CommandAnnotationPluginVersion: tc.version,
}, },
} }
assert.Equal(t, vendorAndVersion(cmd), tc.expected) assert.Equal(t, vendorAndVersion(cmd), tc.expected)
@ -69,8 +69,8 @@ func TestInvalidPlugin(t *testing.T) {
assert.Assert(t, is.Len(invalidPlugins(root), 0)) assert.Assert(t, is.Len(invalidPlugins(root), 0))
sub1.Annotations = map[string]string{ sub1.Annotations = map[string]string{
pluginmanager.CommandAnnotationPlugin: "true", metadata.CommandAnnotationPlugin: "true",
pluginmanager.CommandAnnotationPluginInvalid: "foo", metadata.CommandAnnotationPluginInvalid: "foo",
} }
root.AddCommand(sub1, sub2) root.AddCommand(sub1, sub2)
sub1.AddCommand(sub1sub1, sub1sub2) sub1.AddCommand(sub1sub1, sub1sub2)
@ -100,6 +100,6 @@ func TestDecoratedName(t *testing.T) {
topLevelCommand := &cobra.Command{Use: "pluginTopLevelCommand"} topLevelCommand := &cobra.Command{Use: "pluginTopLevelCommand"}
root.AddCommand(topLevelCommand) root.AddCommand(topLevelCommand)
assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand ") assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand ")
topLevelCommand.Annotations = map[string]string{pluginmanager.CommandAnnotationPlugin: "true"} topLevelCommand.Annotations = map[string]string{metadata.CommandAnnotationPlugin: "true"}
assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand*") assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand*")
} }

View File

@ -7,6 +7,7 @@ import (
"time" "time"
pluginmanager "github.com/docker/cli/cli-plugins/manager" pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
registrytypes "github.com/docker/docker/api/types/registry" registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
@ -201,7 +202,7 @@ var samplePluginsInfo = []pluginmanager.Plugin{
{ {
Name: "goodplugin", Name: "goodplugin",
Path: "/path/to/docker-goodplugin", Path: "/path/to/docker-goodplugin",
Metadata: pluginmanager.Metadata{ Metadata: metadata.Metadata{
SchemaVersion: "0.1.0", SchemaVersion: "0.1.0",
ShortDescription: "unit test is good", ShortDescription: "unit test is good",
Vendor: "ACME Corp", Vendor: "ACME Corp",
@ -211,7 +212,7 @@ var samplePluginsInfo = []pluginmanager.Plugin{
{ {
Name: "unversionedplugin", Name: "unversionedplugin",
Path: "/path/to/docker-unversionedplugin", Path: "/path/to/docker-unversionedplugin",
Metadata: pluginmanager.Metadata{ Metadata: metadata.Metadata{
SchemaVersion: "0.1.0", SchemaVersion: "0.1.0",
ShortDescription: "this plugin has no version", ShortDescription: "this plugin has no version",
Vendor: "ACME Corp", Vendor: "ACME Corp",

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
pluginmanager "github.com/docker/cli/cli-plugins/manager" pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -127,7 +128,7 @@ func processBuilder(dockerCli command.Cli, cmd *cobra.Command, args, osargs []st
} }
// overwrite the command path for this plugin using the alias name. // overwrite the command path for this plugin using the alias name.
cmd.Annotations[pluginmanager.CommandAnnotationPluginCommandPath] = strings.Join(append([]string{cmd.CommandPath()}, fwcmdpath...), " ") cmd.Annotations[metadata.CommandAnnotationPluginCommandPath] = strings.Join(append([]string{cmd.CommandPath()}, fwcmdpath...), " ")
return fwargs, fwosargs, envs, nil return fwargs, fwosargs, envs, nil
} }

View File

@ -5,7 +5,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata"
"gotest.tools/v3/icmd" "gotest.tools/v3/icmd"
) )
@ -21,7 +21,7 @@ func TestCLIPluginDialStdio(t *testing.T) {
// the connhelper stuff. // the connhelper stuff.
helloworld := filepath.Join(os.Getenv("DOCKER_CLI_E2E_PLUGINS_EXTRA_DIRS"), "docker-helloworld") helloworld := filepath.Join(os.Getenv("DOCKER_CLI_E2E_PLUGINS_EXTRA_DIRS"), "docker-helloworld")
cmd := icmd.Command(helloworld, "--config=blah", "--log-level", "debug", "helloworld", "--who=foo") cmd := icmd.Command(helloworld, "--config=blah", "--log-level", "debug", "helloworld", "--who=foo")
res := icmd.RunCmd(cmd, icmd.WithEnv(manager.ReexecEnvvar+"=/bin/true")) res := icmd.RunCmd(cmd, icmd.WithEnv(metadata.ReexecEnvvar+"=/bin/true"))
res.Assert(t, icmd.Expected{ res.Assert(t, icmd.Expected{
ExitCode: 0, ExitCode: 0,
Err: `msg="commandconn: starting /bin/true with [--config=blah --log-level debug system dial-stdio]"`, Err: `msg="commandconn: starting /bin/true with [--config=blah --log-level debug system dial-stdio]"`,

View File

@ -7,11 +7,11 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata"
) )
func main() { func main() {
if len(os.Args) == 2 && os.Args[1] == manager.MetadataSubcommandName { if len(os.Args) == 2 && os.Args[1] == metadata.MetadataSubcommandName {
fmt.Println(`{invalid-json}`) fmt.Println(`{invalid-json}`)
os.Exit(0) os.Exit(0)
} }

View File

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -26,7 +26,7 @@ func main() {
}, },
} }
}, },
manager.Metadata{ metadata.Metadata{
SchemaVersion: "0.1.0", SchemaVersion: "0.1.0",
Vendor: "Docker Inc.", Vendor: "Docker Inc.",
Version: "testing", Version: "testing",

View File

@ -7,14 +7,14 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func main() { func main() {
plugin.Run(RootCmd, manager.Metadata{ plugin.Run(RootCmd, metadata.Metadata{
SchemaVersion: "0.1.0", SchemaVersion: "0.1.0",
Vendor: "Docker Inc.", Vendor: "Docker Inc.",
Version: "test", Version: "test",