mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 00:47:48 +08:00
use cli-plugins metadata package
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
db4b96e62c
commit
391acba718
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/version"
|
"github.com/docker/buildx/version"
|
||||||
"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/plugin"
|
"github.com/docker/cli/cli-plugins/plugin"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/debug"
|
"github.com/docker/cli/cli/debug"
|
||||||
@ -64,7 +64,7 @@ func flushMetrics(cmd *command.DockerCli) {
|
|||||||
|
|
||||||
func runPlugin(cmd *command.DockerCli) error {
|
func runPlugin(cmd *command.DockerCli) error {
|
||||||
rootCmd := commands.NewRootCmd("buildx", true, cmd)
|
rootCmd := commands.NewRootCmd("buildx", true, cmd)
|
||||||
return plugin.RunPlugin(cmd, rootCmd, manager.Metadata{
|
return plugin.RunPlugin(cmd, rootCmd, metadata.Metadata{
|
||||||
SchemaVersion: "0.1.0",
|
SchemaVersion: "0.1.0",
|
||||||
Vendor: "Docker Inc.",
|
Vendor: "Docker Inc.",
|
||||||
Version: version.Version,
|
Version: version.Version,
|
||||||
|
18
vendor/github.com/docker/cli/cli-plugins/hooks/printer.go
generated
vendored
18
vendor/github.com/docker/cli/cli-plugins/hooks/printer.go
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
package hooks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/morikuni/aec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PrintNextSteps(out io.Writer, messages []string) {
|
|
||||||
if len(messages) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintln(out, aec.Bold.Apply("\nWhat's next:"))
|
|
||||||
for _, n := range messages {
|
|
||||||
_, _ = fmt.Fprintln(out, " ", n)
|
|
||||||
}
|
|
||||||
}
|
|
116
vendor/github.com/docker/cli/cli-plugins/hooks/template.go
generated
vendored
116
vendor/github.com/docker/cli/cli-plugins/hooks/template.go
generated
vendored
@ -1,116 +0,0 @@
|
|||||||
package hooks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HookType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
NextSteps = iota
|
|
||||||
)
|
|
||||||
|
|
||||||
// HookMessage represents a plugin hook response. Plugins
|
|
||||||
// declaring support for CLI hooks need to print a json
|
|
||||||
// representation of this type when their hook subcommand
|
|
||||||
// is invoked.
|
|
||||||
type HookMessage struct {
|
|
||||||
Type HookType
|
|
||||||
Template string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemplateReplaceSubcommandName returns a hook template string
|
|
||||||
// that will be replaced by the CLI subcommand being executed
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// "you ran the subcommand: " + TemplateReplaceSubcommandName()
|
|
||||||
//
|
|
||||||
// when being executed after the command:
|
|
||||||
// `docker run --name "my-container" alpine`
|
|
||||||
// will result in the message:
|
|
||||||
// `you ran the subcommand: run`
|
|
||||||
func TemplateReplaceSubcommandName() string {
|
|
||||||
return hookTemplateCommandName
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemplateReplaceFlagValue returns a hook template string
|
|
||||||
// that will be replaced by the flags value.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// "you ran a container named: " + TemplateReplaceFlagValue("name")
|
|
||||||
//
|
|
||||||
// when being executed after the command:
|
|
||||||
// `docker run --name "my-container" alpine`
|
|
||||||
// will result in the message:
|
|
||||||
// `you ran a container named: my-container`
|
|
||||||
func TemplateReplaceFlagValue(flag string) string {
|
|
||||||
return fmt.Sprintf(hookTemplateFlagValue, flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemplateReplaceArg takes an index i and returns a hook
|
|
||||||
// template string that the CLI will replace the template with
|
|
||||||
// the ith argument, after processing the passed flags.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// "run this image with `docker run " + TemplateReplaceArg(0) + "`"
|
|
||||||
//
|
|
||||||
// when being executed after the command:
|
|
||||||
// `docker pull alpine`
|
|
||||||
// will result in the message:
|
|
||||||
// "Run this image with `docker run alpine`"
|
|
||||||
func TemplateReplaceArg(i int) string {
|
|
||||||
return fmt.Sprintf(hookTemplateArg, strconv.Itoa(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
|
|
||||||
tmpl := template.New("").Funcs(commandFunctions)
|
|
||||||
tmpl, err := tmpl.Parse(hookTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
err = tmpl.Execute(&b, cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return strings.Split(b.String(), "\n"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrHookTemplateParse = errors.New("failed to parse hook template")
|
|
||||||
|
|
||||||
const (
|
|
||||||
hookTemplateCommandName = "{{.Name}}"
|
|
||||||
hookTemplateFlagValue = `{{flag . "%s"}}`
|
|
||||||
hookTemplateArg = "{{arg . %s}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandFunctions = template.FuncMap{
|
|
||||||
"flag": getFlagValue,
|
|
||||||
"arg": getArgValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFlagValue(cmd *cobra.Command, flag string) (string, error) {
|
|
||||||
cmdFlag := cmd.Flag(flag)
|
|
||||||
if cmdFlag == nil {
|
|
||||||
return "", ErrHookTemplateParse
|
|
||||||
}
|
|
||||||
return cmdFlag.Value.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getArgValue(cmd *cobra.Command, i int) (string, error) {
|
|
||||||
flags := cmd.Flags()
|
|
||||||
if flags == nil {
|
|
||||||
return "", ErrHookTemplateParse
|
|
||||||
}
|
|
||||||
return flags.Arg(i), nil
|
|
||||||
}
|
|
30
vendor/github.com/docker/cli/cli-plugins/manager/annotations.go
generated
vendored
30
vendor/github.com/docker/cli/cli-plugins/manager/annotations.go
generated
vendored
@ -1,30 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
25
vendor/github.com/docker/cli/cli-plugins/manager/candidate.go
generated
vendored
25
vendor/github.com/docker/cli/cli-plugins/manager/candidate.go
generated
vendored
@ -1,25 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Candidate represents a possible plugin candidate, for mocking purposes
|
|
||||||
type Candidate interface {
|
|
||||||
Path() string
|
|
||||||
Metadata() ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type candidate struct {
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *candidate) Path() string {
|
|
||||||
return c.path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *candidate) Metadata() ([]byte, error) {
|
|
||||||
return exec.Command(c.path, metadata.MetadataSubcommandName).Output() // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
|
|
||||||
}
|
|
77
vendor/github.com/docker/cli/cli-plugins/manager/cobra.go
generated
vendored
77
vendor/github.com/docker/cli/cli-plugins/manager/cobra.go
generated
vendored
@ -1,77 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
|
||||||
"github.com/docker/cli/cli/config"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var pluginCommandStubsOnce sync.Once
|
|
||||||
|
|
||||||
// AddPluginCommandStubs adds a stub cobra.Commands for each valid and invalid
|
|
||||||
// plugin. The command stubs will have several annotations added, see
|
|
||||||
// `CommandAnnotationPlugin*`.
|
|
||||||
func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (err error) {
|
|
||||||
pluginCommandStubsOnce.Do(func() {
|
|
||||||
var plugins []Plugin
|
|
||||||
plugins, err = ListPlugins(dockerCLI, rootCmd)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, p := range plugins {
|
|
||||||
vendor := p.Vendor
|
|
||||||
if vendor == "" {
|
|
||||||
vendor = "unknown"
|
|
||||||
}
|
|
||||||
annotations := map[string]string{
|
|
||||||
metadata.CommandAnnotationPlugin: "true",
|
|
||||||
metadata.CommandAnnotationPluginVendor: vendor,
|
|
||||||
metadata.CommandAnnotationPluginVersion: p.Version,
|
|
||||||
}
|
|
||||||
if p.Err != nil {
|
|
||||||
annotations[metadata.CommandAnnotationPluginInvalid] = p.Err.Error()
|
|
||||||
}
|
|
||||||
rootCmd.AddCommand(&cobra.Command{
|
|
||||||
Use: p.Name,
|
|
||||||
Short: p.ShortDescription,
|
|
||||||
Run: func(_ *cobra.Command, _ []string) {},
|
|
||||||
Annotations: annotations,
|
|
||||||
DisableFlagParsing: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
flags := rootCmd.PersistentFlags()
|
|
||||||
flags.SetOutput(nil)
|
|
||||||
perr := flags.Parse(args)
|
|
||||||
if perr != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if flags.Changed("help") {
|
|
||||||
cmd.HelpFunc()(rootCmd, args)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("docker: unknown command: docker %s\n\nRun 'docker --help' for more information", cmd.Name())
|
|
||||||
},
|
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
||||||
// Delegate completion to plugin
|
|
||||||
cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name}
|
|
||||||
cargs = append(cargs, args...)
|
|
||||||
cargs = append(cargs, toComplete)
|
|
||||||
os.Args = cargs
|
|
||||||
runCommand, runErr := PluginRunCommand(dockerCLI, p.Name, cmd)
|
|
||||||
if runErr != nil {
|
|
||||||
return nil, cobra.ShellCompDirectiveError
|
|
||||||
}
|
|
||||||
runErr = runCommand.Run()
|
|
||||||
if runErr == nil {
|
|
||||||
os.Exit(0) // plugin already rendered complete data
|
|
||||||
}
|
|
||||||
return nil, cobra.ShellCompDirectiveError
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
54
vendor/github.com/docker/cli/cli-plugins/manager/error.go
generated
vendored
54
vendor/github.com/docker/cli/cli-plugins/manager/error.go
generated
vendored
@ -1,54 +0,0 @@
|
|||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
|
||||||
//go:build go1.22
|
|
||||||
|
|
||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// pluginError is set as Plugin.Err by NewPlugin if the plugin
|
|
||||||
// candidate fails one of the candidate tests. This exists primarily
|
|
||||||
// to implement encoding.TextMarshaller such that rendering a plugin as JSON
|
|
||||||
// (e.g. for `docker info -f '{{json .CLIPlugins}}'`) renders the Err
|
|
||||||
// field as a useful string and not just `{}`. See
|
|
||||||
// https://github.com/golang/go/issues/10748 for some discussion
|
|
||||||
// around why the builtin error type doesn't implement this.
|
|
||||||
type pluginError struct {
|
|
||||||
cause error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error satisfies the core error interface for pluginError.
|
|
||||||
func (e *pluginError) Error() string {
|
|
||||||
return e.cause.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cause satisfies the errors.causer interface for pluginError.
|
|
||||||
func (e *pluginError) Cause() error {
|
|
||||||
return e.cause
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
|
||||||
func (e *pluginError) Unwrap() error {
|
|
||||||
return e.cause
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText marshalls the pluginError into a textual form.
|
|
||||||
func (e *pluginError) MarshalText() (text []byte, err error) {
|
|
||||||
return []byte(e.cause.Error()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrapAsPluginError wraps an error in a pluginError with an
|
|
||||||
// additional message.
|
|
||||||
func wrapAsPluginError(err error, msg string) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &pluginError{cause: fmt.Errorf("%s: %w", msg, err)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPluginError creates a new pluginError, analogous to
|
|
||||||
// errors.Errorf.
|
|
||||||
func NewPluginError(msg string, args ...any) error {
|
|
||||||
return &pluginError{cause: fmt.Errorf(msg, args...)}
|
|
||||||
}
|
|
203
vendor/github.com/docker/cli/cli-plugins/manager/hooks.go
generated
vendored
203
vendor/github.com/docker/cli/cli-plugins/manager/hooks.go
generated
vendored
@ -1,203 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli-plugins/hooks"
|
|
||||||
"github.com/docker/cli/cli/config"
|
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HookPluginData is the type representing the information
|
|
||||||
// that plugins declaring support for hooks get passed when
|
|
||||||
// being invoked following a CLI command execution.
|
|
||||||
type HookPluginData struct {
|
|
||||||
// RootCmd is a string representing the matching hook configuration
|
|
||||||
// which is currently being invoked. If a hook for `docker context` is
|
|
||||||
// configured and the user executes `docker context ls`, the plugin will
|
|
||||||
// be invoked with `context`.
|
|
||||||
RootCmd string
|
|
||||||
Flags map[string]string
|
|
||||||
CommandError string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunCLICommandHooks is the entrypoint into the hooks execution flow after
|
|
||||||
// a main CLI command was executed. It calls the hook subcommand for all
|
|
||||||
// present CLI plugins that declare support for hooks in their metadata and
|
|
||||||
// parses/prints their responses.
|
|
||||||
func RunCLICommandHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, cmdErrorMessage string) {
|
|
||||||
commandName := strings.TrimPrefix(subCommand.CommandPath(), rootCmd.Name()+" ")
|
|
||||||
flags := getCommandFlags(subCommand)
|
|
||||||
|
|
||||||
runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, cmdErrorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunPluginHooks is the entrypoint for the hooks execution flow
|
|
||||||
// after a plugin command was just executed by the CLI.
|
|
||||||
func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, args []string) {
|
|
||||||
commandName := strings.Join(args, " ")
|
|
||||||
flags := getNaiveFlags(args)
|
|
||||||
|
|
||||||
runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
|
|
||||||
nextSteps := invokeAndCollectHooks(ctx, cfg, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
|
|
||||||
hooks.PrintNextSteps(subCommand.ErrOrStderr(), nextSteps)
|
|
||||||
}
|
|
||||||
|
|
||||||
func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
|
|
||||||
// check if the context was cancelled before invoking hooks
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginsCfg := cfg.Plugins
|
|
||||||
if pluginsCfg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginDirs, err := getPluginDirs(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nextSteps := make([]string, 0, len(pluginsCfg))
|
|
||||||
for pluginName, cfg := range pluginsCfg {
|
|
||||||
match, ok := pluginMatch(cfg, subCmdStr)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := getPlugin(pluginName, pluginDirs, rootCmd)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
hookReturn, err := p.RunHook(ctx, HookPluginData{
|
|
||||||
RootCmd: match,
|
|
||||||
Flags: flags,
|
|
||||||
CommandError: cmdErrorMessage,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// skip misbehaving plugins, but don't halt execution
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var hookMessageData hooks.HookMessage
|
|
||||||
err = json.Unmarshal(hookReturn, &hookMessageData)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// currently the only hook type
|
|
||||||
if hookMessageData.Type != hooks.NextSteps {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
processedHook, err := hooks.ParseTemplate(hookMessageData.Template, subCmd)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var appended bool
|
|
||||||
nextSteps, appended = appendNextSteps(nextSteps, processedHook)
|
|
||||||
if !appended {
|
|
||||||
logrus.Debugf("Plugin %s responded with an empty hook message %q. Ignoring.", pluginName, string(hookReturn))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nextSteps
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendNextSteps appends the processed hook output to the nextSteps slice.
|
|
||||||
// If the processed hook output is empty, it is not appended.
|
|
||||||
// Empty lines are not stripped if there's at least one non-empty line.
|
|
||||||
func appendNextSteps(nextSteps []string, processed []string) ([]string, bool) {
|
|
||||||
empty := true
|
|
||||||
for _, l := range processed {
|
|
||||||
if strings.TrimSpace(l) != "" {
|
|
||||||
empty = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if empty {
|
|
||||||
return nextSteps, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(nextSteps, processed...), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// pluginMatch takes a plugin configuration and a string representing the
|
|
||||||
// command being executed (such as 'image ls' – the root 'docker' is omitted)
|
|
||||||
// and, if the configuration includes a hook for the invoked command, returns
|
|
||||||
// the configured hook string.
|
|
||||||
func pluginMatch(pluginCfg map[string]string, subCmd string) (string, bool) {
|
|
||||||
configuredPluginHooks, ok := pluginCfg["hooks"]
|
|
||||||
if !ok || configuredPluginHooks == "" {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
commands := strings.Split(configuredPluginHooks, ",")
|
|
||||||
for _, hookCmd := range commands {
|
|
||||||
if hookMatch(hookCmd, subCmd) {
|
|
||||||
return hookCmd, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hookMatch(hookCmd, subCmd string) bool {
|
|
||||||
hookCmdTokens := strings.Split(hookCmd, " ")
|
|
||||||
subCmdTokens := strings.Split(subCmd, " ")
|
|
||||||
|
|
||||||
if len(hookCmdTokens) > len(subCmdTokens) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, v := range hookCmdTokens {
|
|
||||||
if v != subCmdTokens[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCommandFlags(cmd *cobra.Command) map[string]string {
|
|
||||||
flags := make(map[string]string)
|
|
||||||
cmd.Flags().Visit(func(f *pflag.Flag) {
|
|
||||||
var fValue string
|
|
||||||
if f.Value.Type() == "bool" {
|
|
||||||
fValue = f.Value.String()
|
|
||||||
}
|
|
||||||
flags[f.Name] = fValue
|
|
||||||
})
|
|
||||||
return flags
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNaiveFlags string-matches argv and parses them into a map.
|
|
||||||
// This is used when calling hooks after a plugin command, since
|
|
||||||
// in this case we can't rely on the cobra command tree to parse
|
|
||||||
// flags in this case. In this case, no values are ever passed,
|
|
||||||
// since we don't have enough information to process them.
|
|
||||||
func getNaiveFlags(args []string) map[string]string {
|
|
||||||
flags := make(map[string]string)
|
|
||||||
for _, arg := range args {
|
|
||||||
if strings.HasPrefix(arg, "--") {
|
|
||||||
flags[arg[2:]] = ""
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(arg, "-") {
|
|
||||||
flags[arg[1:]] = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flags
|
|
||||||
}
|
|
252
vendor/github.com/docker/cli/cli-plugins/manager/manager.go
generated
vendored
252
vendor/github.com/docker/cli/cli-plugins/manager/manager.go
generated
vendored
@ -1,252 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
|
||||||
"github.com/docker/cli/cli/config"
|
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
|
||||||
"github.com/fvbommel/sortorder"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 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 = metadata.ReexecEnvvar
|
|
||||||
|
|
||||||
// ResourceAttributesEnvvar is the name of the envvar that includes additional
|
|
||||||
// resource attributes for OTEL.
|
|
||||||
//
|
|
||||||
// Deprecated: The "OTEL_RESOURCE_ATTRIBUTES" env-var is part of the OpenTelemetry specification; users should define their own const for this. This const will be removed in the next release.
|
|
||||||
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
|
|
||||||
)
|
|
||||||
|
|
||||||
// errPluginNotFound is the error returned when a plugin could not be found.
|
|
||||||
type errPluginNotFound string
|
|
||||||
|
|
||||||
func (errPluginNotFound) NotFound() {}
|
|
||||||
|
|
||||||
func (e errPluginNotFound) Error() string {
|
|
||||||
return "Error: No such CLI plugin: " + string(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
type notFound interface{ NotFound() }
|
|
||||||
|
|
||||||
// IsNotFound is true if the given error is due to a plugin not being found.
|
|
||||||
func IsNotFound(err error) bool {
|
|
||||||
if e, ok := err.(*pluginError); ok {
|
|
||||||
err = e.Cause()
|
|
||||||
}
|
|
||||||
_, ok := err.(notFound)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPluginDirs returns the platform-specific locations to search for plugins
|
|
||||||
// in order of preference.
|
|
||||||
//
|
|
||||||
// Plugin-discovery is performed in the following order of preference:
|
|
||||||
//
|
|
||||||
// 1. The "cli-plugins" directory inside the CLIs [config.Path] (usually "~/.docker/cli-plugins").
|
|
||||||
// 2. Additional plugin directories as configured through [ConfigFile.CLIPluginsExtraDirs].
|
|
||||||
// 3. Platform-specific defaultSystemPluginDirs.
|
|
||||||
//
|
|
||||||
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
|
|
||||||
func getPluginDirs(cfg *configfile.ConfigFile) ([]string, error) {
|
|
||||||
var pluginDirs []string
|
|
||||||
|
|
||||||
if cfg != nil {
|
|
||||||
pluginDirs = append(pluginDirs, cfg.CLIPluginsExtraDirs...)
|
|
||||||
}
|
|
||||||
pluginDir, err := config.Path("cli-plugins")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginDirs = append(pluginDirs, pluginDir)
|
|
||||||
pluginDirs = append(pluginDirs, defaultSystemPluginDirs...)
|
|
||||||
return pluginDirs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addPluginCandidatesFromDir(res map[string][]string, d string) {
|
|
||||||
dentries, err := os.ReadDir(d)
|
|
||||||
// Silently ignore any directories which we cannot list (e.g. due to
|
|
||||||
// permissions or anything else) or which is not a directory
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, dentry := range dentries {
|
|
||||||
switch dentry.Type() & os.ModeType {
|
|
||||||
case 0, os.ModeSymlink:
|
|
||||||
// Regular file or symlink, keep going
|
|
||||||
default:
|
|
||||||
// Something else, ignore.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := dentry.Name()
|
|
||||||
if !strings.HasPrefix(name, metadata.NamePrefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name = strings.TrimPrefix(name, metadata.NamePrefix)
|
|
||||||
var err error
|
|
||||||
if name, err = trimExeSuffix(name); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res[name] = append(res[name], filepath.Join(d, dentry.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// listPluginCandidates returns a map from plugin name to the list of (unvalidated) Candidates. The list is in descending order of priority.
|
|
||||||
func listPluginCandidates(dirs []string) map[string][]string {
|
|
||||||
result := make(map[string][]string)
|
|
||||||
for _, d := range dirs {
|
|
||||||
addPluginCandidatesFromDir(result, d)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPlugin returns a plugin on the system by its name
|
|
||||||
func GetPlugin(name string, dockerCLI config.Provider, rootcmd *cobra.Command) (*Plugin, error) {
|
|
||||||
pluginDirs, err := getPluginDirs(dockerCLI.ConfigFile())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return getPlugin(name, pluginDirs, rootcmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPlugin(name string, pluginDirs []string, rootcmd *cobra.Command) (*Plugin, error) {
|
|
||||||
candidates := listPluginCandidates(pluginDirs)
|
|
||||||
if paths, ok := candidates[name]; ok {
|
|
||||||
if len(paths) == 0 {
|
|
||||||
return nil, errPluginNotFound(name)
|
|
||||||
}
|
|
||||||
c := &candidate{paths[0]}
|
|
||||||
p, err := newPlugin(c, rootcmd.Commands())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !IsNotFound(p.Err) {
|
|
||||||
p.ShadowedPaths = paths[1:]
|
|
||||||
}
|
|
||||||
return &p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errPluginNotFound(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPlugins produces a list of the plugins available on the system
|
|
||||||
func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, error) {
|
|
||||||
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
candidates := listPluginCandidates(pluginDirs)
|
|
||||||
|
|
||||||
var plugins []Plugin
|
|
||||||
var mu sync.Mutex
|
|
||||||
eg, _ := errgroup.WithContext(context.TODO())
|
|
||||||
cmds := rootcmd.Commands()
|
|
||||||
for _, paths := range candidates {
|
|
||||||
func(paths []string) {
|
|
||||||
eg.Go(func() error {
|
|
||||||
if len(paths) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c := &candidate{paths[0]}
|
|
||||||
p, err := newPlugin(c, cmds)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !IsNotFound(p.Err) {
|
|
||||||
p.ShadowedPaths = paths[1:]
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
plugins = append(plugins, p)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}(paths)
|
|
||||||
}
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(plugins, func(i, j int) bool {
|
|
||||||
return sortorder.NaturalLess(plugins[i].Name, plugins[j].Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
return plugins, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PluginRunCommand returns an "os/exec".Cmd which when .Run() will execute the named plugin.
|
|
||||||
// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conficts.
|
|
||||||
// The error returned satisfies the IsNotFound() predicate if no plugin was found or if the first candidate plugin was invalid somehow.
|
|
||||||
func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Command) (*exec.Cmd, error) {
|
|
||||||
// This uses the full original args, not the args which may
|
|
||||||
// have been provided by cobra to our caller. This is because
|
|
||||||
// they lack e.g. global options which we must propagate here.
|
|
||||||
args := os.Args[1:]
|
|
||||||
if !pluginNameRe.MatchString(name) {
|
|
||||||
// We treat this as "not found" so that callers will
|
|
||||||
// fallback to their "invalid" command path.
|
|
||||||
return nil, errPluginNotFound(name)
|
|
||||||
}
|
|
||||||
exename := addExeSuffix(metadata.NamePrefix + name)
|
|
||||||
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range pluginDirs {
|
|
||||||
path := filepath.Join(d, exename)
|
|
||||||
|
|
||||||
// We stat here rather than letting the exec tell us
|
|
||||||
// ENOENT because the latter does not distinguish a
|
|
||||||
// file not existing from its dynamic loader or one of
|
|
||||||
// its libraries not existing.
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &candidate{path: path}
|
|
||||||
plugin, err := newPlugin(c, rootcmd.Commands())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if plugin.Err != nil {
|
|
||||||
// TODO: why are we not returning plugin.Err?
|
|
||||||
return nil, errPluginNotFound(name)
|
|
||||||
}
|
|
||||||
cmd := exec.Command(plugin.Path, args...) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
|
|
||||||
|
|
||||||
// Using dockerCli.{In,Out,Err}() here results in a hang until something is input.
|
|
||||||
// See: - https://github.com/golang/go/issues/10338
|
|
||||||
// - https://github.com/golang/go/commit/d000e8742a173aa0659584aa01b7ba2834ba28ab
|
|
||||||
// os.Stdin is a *os.File which avoids this behaviour. We don't need the functionality
|
|
||||||
// of the wrappers here anyway.
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
cmd.Env = append(cmd.Environ(), metadata.ReexecEnvvar+"="+os.Args[0])
|
|
||||||
cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin)
|
|
||||||
|
|
||||||
return cmd, nil
|
|
||||||
}
|
|
||||||
return nil, errPluginNotFound(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPluginCommand checks if the given cmd is a plugin-stub.
|
|
||||||
func IsPluginCommand(cmd *cobra.Command) bool {
|
|
||||||
return cmd.Annotations[metadata.CommandAnnotationPlugin] == "true"
|
|
||||||
}
|
|
20
vendor/github.com/docker/cli/cli-plugins/manager/manager_unix.go
generated
vendored
20
vendor/github.com/docker/cli/cli-plugins/manager/manager_unix.go
generated
vendored
@ -1,20 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package manager
|
|
||||||
|
|
||||||
// defaultSystemPluginDirs are the platform-specific locations to search
|
|
||||||
// for plugins in order of preference.
|
|
||||||
//
|
|
||||||
// Plugin-discovery is performed in the following order of preference:
|
|
||||||
//
|
|
||||||
// 1. The "cli-plugins" directory inside the CLIs config-directory (usually "~/.docker/cli-plugins").
|
|
||||||
// 2. Additional plugin directories as configured through [ConfigFile.CLIPluginsExtraDirs].
|
|
||||||
// 3. Platform-specific defaultSystemPluginDirs (as defined below).
|
|
||||||
//
|
|
||||||
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
|
|
||||||
var defaultSystemPluginDirs = []string{
|
|
||||||
"/usr/local/lib/docker/cli-plugins",
|
|
||||||
"/usr/local/libexec/docker/cli-plugins",
|
|
||||||
"/usr/lib/docker/cli-plugins",
|
|
||||||
"/usr/libexec/docker/cli-plugins",
|
|
||||||
}
|
|
21
vendor/github.com/docker/cli/cli-plugins/manager/manager_windows.go
generated
vendored
21
vendor/github.com/docker/cli/cli-plugins/manager/manager_windows.go
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// defaultSystemPluginDirs are the platform-specific locations to search
|
|
||||||
// for plugins in order of preference.
|
|
||||||
//
|
|
||||||
// Plugin-discovery is performed in the following order of preference:
|
|
||||||
//
|
|
||||||
// 1. The "cli-plugins" directory inside the CLIs config-directory (usually "~/.docker/cli-plugins").
|
|
||||||
// 2. Additional plugin directories as configured through [ConfigFile.CLIPluginsExtraDirs].
|
|
||||||
// 3. Platform-specific defaultSystemPluginDirs (as defined below).
|
|
||||||
//
|
|
||||||
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
|
|
||||||
var defaultSystemPluginDirs = []string{
|
|
||||||
filepath.Join(os.Getenv("ProgramData"), "Docker", "cli-plugins"),
|
|
||||||
filepath.Join(os.Getenv("ProgramFiles"), "Docker", "cli-plugins"),
|
|
||||||
}
|
|
23
vendor/github.com/docker/cli/cli-plugins/manager/metadata.go
generated
vendored
23
vendor/github.com/docker/cli/cli-plugins/manager/metadata.go
generated
vendored
@ -1,23 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NamePrefix is the prefix required on all plugin binary names
|
|
||||||
NamePrefix = metadata.NamePrefix
|
|
||||||
|
|
||||||
// MetadataSubcommandName is the name of the plugin subcommand
|
|
||||||
// which must be supported by every plugin and returns the
|
|
||||||
// plugin metadata.
|
|
||||||
MetadataSubcommandName = metadata.MetadataSubcommandName
|
|
||||||
|
|
||||||
// HookSubcommandName is the name of the plugin subcommand
|
|
||||||
// which must be implemented by plugins declaring support
|
|
||||||
// for hooks in their metadata.
|
|
||||||
HookSubcommandName = metadata.HookSubcommandName
|
|
||||||
)
|
|
||||||
|
|
||||||
// Metadata provided by the plugin.
|
|
||||||
type Metadata = metadata.Metadata
|
|
126
vendor/github.com/docker/cli/cli-plugins/manager/plugin.go
generated
vendored
126
vendor/github.com/docker/cli/cli-plugins/manager/plugin.go
generated
vendored
@ -1,126 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var pluginNameRe = regexp.MustCompile("^[a-z][a-z0-9]*$")
|
|
||||||
|
|
||||||
// Plugin represents a potential plugin with all it's metadata.
|
|
||||||
type Plugin struct {
|
|
||||||
metadata.Metadata
|
|
||||||
|
|
||||||
Name string `json:",omitempty"`
|
|
||||||
Path string `json:",omitempty"`
|
|
||||||
|
|
||||||
// Err is non-nil if the plugin failed one of the candidate tests.
|
|
||||||
Err error `json:",omitempty"`
|
|
||||||
|
|
||||||
// ShadowedPaths contains the paths of any other plugins which this plugin takes precedence over.
|
|
||||||
ShadowedPaths []string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// newPlugin determines if the given candidate is valid and returns a
|
|
||||||
// Plugin. If the candidate fails one of the tests then `Plugin.Err`
|
|
||||||
// is set, and is always a `pluginError`, but the `Plugin` is still
|
|
||||||
// returned with no error. An error is only returned due to a
|
|
||||||
// non-recoverable error.
|
|
||||||
func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
|
|
||||||
path := c.Path()
|
|
||||||
if path == "" {
|
|
||||||
return Plugin{}, errors.New("plugin candidate path cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// The candidate listing process should have skipped anything
|
|
||||||
// which would fail here, so there are all real errors.
|
|
||||||
fullname := filepath.Base(path)
|
|
||||||
if fullname == "." {
|
|
||||||
return Plugin{}, fmt.Errorf("unable to determine basename of plugin candidate %q", path)
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if fullname, err = trimExeSuffix(fullname); err != nil {
|
|
||||||
return Plugin{}, fmt.Errorf("plugin candidate %q: %w", path, err)
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(fullname, metadata.NamePrefix) {
|
|
||||||
return Plugin{}, fmt.Errorf("plugin candidate %q: does not have %q prefix", path, metadata.NamePrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
p := Plugin{
|
|
||||||
Name: strings.TrimPrefix(fullname, metadata.NamePrefix),
|
|
||||||
Path: path,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now apply the candidate tests, so these update p.Err.
|
|
||||||
if !pluginNameRe.MatchString(p.Name) {
|
|
||||||
p.Err = NewPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String())
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cmd := range cmds {
|
|
||||||
// Ignore conflicts with commands which are
|
|
||||||
// just plugin stubs (i.e. from a previous
|
|
||||||
// call to AddPluginCommandStubs).
|
|
||||||
if IsPluginCommand(cmd) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cmd.Name() == p.Name {
|
|
||||||
p.Err = NewPluginError("plugin %q duplicates builtin command", p.Name)
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
if cmd.HasAlias(p.Name) {
|
|
||||||
p.Err = NewPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name())
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are supposed to check for relevant execute permissions here. Instead we rely on an attempt to execute.
|
|
||||||
meta, err := c.Metadata()
|
|
||||||
if err != nil {
|
|
||||||
p.Err = wrapAsPluginError(err, "failed to fetch metadata")
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(meta, &p.Metadata); err != nil {
|
|
||||||
p.Err = wrapAsPluginError(err, "invalid metadata")
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
if p.Metadata.SchemaVersion != "0.1.0" {
|
|
||||||
p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
if p.Metadata.Vendor == "" {
|
|
||||||
p.Err = NewPluginError("plugin metadata does not define a vendor")
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunHook executes the plugin's hooks command
|
|
||||||
// and returns its unprocessed output.
|
|
||||||
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {
|
|
||||||
hDataBytes, err := json.Marshal(hookData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, wrapAsPluginError(err, "failed to marshall hook data")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = append(pCmd.Env, metadata.ReexecEnvvar+"="+os.Args[0])
|
|
||||||
hookCmdOutput, err := pCmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand")
|
|
||||||
}
|
|
||||||
|
|
||||||
return hookCmdOutput, nil
|
|
||||||
}
|
|
11
vendor/github.com/docker/cli/cli-plugins/manager/suffix_unix.go
generated
vendored
11
vendor/github.com/docker/cli/cli-plugins/manager/suffix_unix.go
generated
vendored
@ -1,11 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package manager
|
|
||||||
|
|
||||||
func trimExeSuffix(s string) (string, error) {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addExeSuffix(s string) string {
|
|
||||||
return s
|
|
||||||
}
|
|
20
vendor/github.com/docker/cli/cli-plugins/manager/suffix_windows.go
generated
vendored
20
vendor/github.com/docker/cli/cli-plugins/manager/suffix_windows.go
generated
vendored
@ -1,20 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is made slightly more complex due to needing to be case-insensitive.
|
|
||||||
func trimExeSuffix(s string) (string, error) {
|
|
||||||
ext := filepath.Ext(s)
|
|
||||||
if ext == "" || !strings.EqualFold(ext, ".exe") {
|
|
||||||
return "", fmt.Errorf("path %q lacks required file extension (.exe)", s)
|
|
||||||
}
|
|
||||||
return strings.TrimSuffix(s, ext), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addExeSuffix(s string) string {
|
|
||||||
return s + ".exe"
|
|
||||||
}
|
|
85
vendor/github.com/docker/cli/cli-plugins/manager/telemetry.go
generated
vendored
85
vendor/github.com/docker/cli/cli-plugins/manager/telemetry.go
generated
vendored
@ -1,85 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"go.opentelemetry.io/otel"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
"go.opentelemetry.io/otel/baggage"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// resourceAttributesEnvVar is the name of the envvar that includes additional
|
|
||||||
// resource attributes for OTEL as defined in the [OpenTelemetry specification].
|
|
||||||
//
|
|
||||||
// [OpenTelemetry specification]: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration
|
|
||||||
resourceAttributesEnvVar = "OTEL_RESOURCE_ATTRIBUTES"
|
|
||||||
|
|
||||||
// dockerCLIAttributePrefix is the prefix for any docker cli OTEL attributes.
|
|
||||||
//
|
|
||||||
// It is a copy of the const defined in [command.dockerCLIAttributePrefix].
|
|
||||||
dockerCLIAttributePrefix = "docker.cli."
|
|
||||||
cobraCommandPath = attribute.Key("cobra.command_path")
|
|
||||||
)
|
|
||||||
|
|
||||||
func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set {
|
|
||||||
commandPath := cmd.Annotations[metadata.CommandAnnotationPluginCommandPath]
|
|
||||||
if commandPath == "" {
|
|
||||||
commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
attrSet := attribute.NewSet(
|
|
||||||
cobraCommandPath.String(commandPath),
|
|
||||||
)
|
|
||||||
|
|
||||||
kvs := make([]attribute.KeyValue, 0, attrSet.Len())
|
|
||||||
for iter := attrSet.Iter(); iter.Next(); {
|
|
||||||
attr := iter.Attribute()
|
|
||||||
kvs = append(kvs, attribute.KeyValue{
|
|
||||||
Key: dockerCLIAttributePrefix + attr.Key,
|
|
||||||
Value: attr.Value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return attribute.NewSet(kvs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendPluginResourceAttributesEnvvar(env []string, cmd *cobra.Command, plugin Plugin) []string {
|
|
||||||
if attrs := getPluginResourceAttributes(cmd, plugin); attrs.Len() > 0 {
|
|
||||||
// Construct baggage members for each of the attributes.
|
|
||||||
// Ignore any failures as these aren't significant and
|
|
||||||
// represent an internal issue.
|
|
||||||
members := make([]baggage.Member, 0, attrs.Len())
|
|
||||||
for iter := attrs.Iter(); iter.Next(); {
|
|
||||||
attr := iter.Attribute()
|
|
||||||
m, err := baggage.NewMemberRaw(string(attr.Key), attr.Value.AsString())
|
|
||||||
if err != nil {
|
|
||||||
otel.Handle(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
members = append(members, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine plugin added resource attributes with ones found in the environment
|
|
||||||
// variable. Our own attributes should be namespaced so there shouldn't be a
|
|
||||||
// conflict. We do not parse the environment variable because we do not want
|
|
||||||
// to handle errors in user configuration.
|
|
||||||
attrsSlice := make([]string, 0, 2)
|
|
||||||
if v := strings.TrimSpace(os.Getenv(resourceAttributesEnvVar)); v != "" {
|
|
||||||
attrsSlice = append(attrsSlice, v)
|
|
||||||
}
|
|
||||||
if b, err := baggage.New(members...); err != nil {
|
|
||||||
otel.Handle(err)
|
|
||||||
} else if b.Len() > 0 {
|
|
||||||
attrsSlice = append(attrsSlice, b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(attrsSlice) > 0 {
|
|
||||||
env = append(env, resourceAttributesEnvVar+"="+strings.Join(attrsSlice, ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return env
|
|
||||||
}
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -232,8 +232,6 @@ github.com/distribution/reference
|
|||||||
# github.com/docker/cli v28.0.2+incompatible
|
# github.com/docker/cli v28.0.2+incompatible
|
||||||
## explicit
|
## explicit
|
||||||
github.com/docker/cli/cli
|
github.com/docker/cli/cli
|
||||||
github.com/docker/cli/cli-plugins/hooks
|
|
||||||
github.com/docker/cli/cli-plugins/manager
|
|
||||||
github.com/docker/cli/cli-plugins/metadata
|
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-plugins/socket
|
github.com/docker/cli/cli-plugins/socket
|
||||||
|
Loading…
x
Reference in New Issue
Block a user