Merge pull request #5902 from thaJeztah/cli_plugin_metadata
move cli-plugins metadata types/consts to a separate package
This commit is contained in:
commit
e201b4e8a5
@ -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",
|
||||||
|
30
cli-plugins/manager/annotations.go
Normal file
30
cli-plugins/manager/annotations.go
Normal 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
|
||||||
|
)
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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"`
|
|
||||||
}
|
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
28
cli-plugins/metadata/annotations.go
Normal file
28
cli-plugins/metadata/annotations.go
Normal 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"
|
||||||
|
)
|
36
cli-plugins/metadata/metadata.go
Normal file
36
cli-plugins/metadata/metadata.go
Normal 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"`
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
10
cli/cobra.go
10
cli/cobra.go
@ -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:
|
||||||
|
@ -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*")
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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]"`,
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user