diff --git a/go.mod b/go.mod index 6942acb9..4e8963eb 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/creack/pty v1.1.24 github.com/davecgh/go-spew v1.1.1 github.com/distribution/reference v0.6.0 - github.com/docker/cli v28.0.1+incompatible + github.com/docker/cli v28.0.2+incompatible github.com/docker/cli-docs-tool v0.9.0 github.com/docker/docker v28.0.2+incompatible github.com/docker/go-units v0.5.0 diff --git a/go.sum b/go.sum index 6f311c17..a09c598a 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs= -github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v28.0.2+incompatible h1:cRPZ77FK3/IXTAIQQj1vmhlxiLS5m+MIUDwS6f57lrE= +github.com/docker/cli v28.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0= github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/annotations.go b/vendor/github.com/docker/cli/cli-plugins/manager/annotations.go new file mode 100644 index 00000000..8fc76b73 --- /dev/null +++ b/vendor/github.com/docker/cli/cli-plugins/manager/annotations.go @@ -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 +) diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/candidate.go b/vendor/github.com/docker/cli/cli-plugins/manager/candidate.go index e65ac1a5..d809926c 100644 --- a/vendor/github.com/docker/cli/cli-plugins/manager/candidate.go +++ b/vendor/github.com/docker/cli/cli-plugins/manager/candidate.go @@ -1,6 +1,10 @@ package manager -import "os/exec" +import ( + "os/exec" + + "github.com/docker/cli/cli-plugins/metadata" +) // Candidate represents a possible plugin candidate, for mocking purposes type Candidate interface { @@ -17,5 +21,5 @@ func (c *candidate) Path() string { } 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" } diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/cobra.go b/vendor/github.com/docker/cli/cli-plugins/manager/cobra.go index 4bfa06fa..ddf067be 100644 --- a/vendor/github.com/docker/cli/cli-plugins/manager/cobra.go +++ b/vendor/github.com/docker/cli/cli-plugins/manager/cobra.go @@ -2,41 +2,12 @@ package manager import ( "fmt" - "net/url" "os" - "strings" "sync" - "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli-plugins/metadata" + "github.com/docker/cli/cli/config" "github.com/spf13/cobra" - "go.opentelemetry.io/otel/attribute" -) - -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 @@ -44,10 +15,10 @@ 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 command.Cli, rootCmd *cobra.Command) (err error) { +func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (err error) { pluginCommandStubsOnce.Do(func() { var plugins []Plugin - plugins, err = ListPlugins(dockerCli, rootCmd) + plugins, err = ListPlugins(dockerCLI, rootCmd) if err != nil { return } @@ -57,12 +28,12 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e vendor = "unknown" } annotations := map[string]string{ - CommandAnnotationPlugin: "true", - CommandAnnotationPluginVendor: vendor, - CommandAnnotationPluginVersion: p.Version, + metadata.CommandAnnotationPlugin: "true", + metadata.CommandAnnotationPluginVendor: vendor, + metadata.CommandAnnotationPluginVersion: p.Version, } if p.Err != nil { - annotations[CommandAnnotationPluginInvalid] = p.Err.Error() + annotations[metadata.CommandAnnotationPluginInvalid] = p.Err.Error() } rootCmd.AddCommand(&cobra.Command{ Use: p.Name, @@ -89,7 +60,7 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e cargs = append(cargs, args...) cargs = append(cargs, toComplete) os.Args = cargs - runCommand, runErr := PluginRunCommand(dockerCli, p.Name, cmd) + runCommand, runErr := PluginRunCommand(dockerCLI, p.Name, cmd) if runErr != nil { return nil, cobra.ShellCompDirectiveError } @@ -104,44 +75,3 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e }) return err } - -const ( - dockerCliAttributePrefix = attribute.Key("docker.cli") - - cobraCommandPath = attribute.Key("cobra.command_path") -) - -func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set { - commandPath := cmd.Annotations[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 { - // values in environment variables need to be in baggage format - // otel/baggage package can be used after update to v1.22, currently it encodes incorrectly - attrsSlice := make([]string, attrs.Len()) - for iter := attrs.Iter(); iter.Next(); { - i, v := iter.IndexedAttribute() - attrsSlice[i] = string(v.Key) + "=" + url.PathEscape(v.Value.AsString()) - } - env = append(env, ResourceAttributesEnvvar+"="+strings.Join(attrsSlice, ",")) - } - return env -} diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/error.go b/vendor/github.com/docker/cli/cli-plugins/manager/error.go index cb0bbb5a..144c11fa 100644 --- a/vendor/github.com/docker/cli/cli-plugins/manager/error.go +++ b/vendor/github.com/docker/cli/cli-plugins/manager/error.go @@ -4,7 +4,7 @@ package manager import ( - "github.com/pkg/errors" + "fmt" ) // pluginError is set as Plugin.Err by NewPlugin if the plugin @@ -39,16 +39,16 @@ func (e *pluginError) MarshalText() (text []byte, err error) { } // wrapAsPluginError wraps an error in a pluginError with an -// additional message, analogous to errors.Wrapf. +// additional message. func wrapAsPluginError(err error, msg string) error { if err == nil { return nil } - return &pluginError{cause: errors.Wrap(err, msg)} + 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: errors.Errorf(msg, args...)} + return &pluginError{cause: fmt.Errorf(msg, args...)} } diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/hooks.go b/vendor/github.com/docker/cli/cli-plugins/manager/hooks.go index 5125c56d..b92dab43 100644 --- a/vendor/github.com/docker/cli/cli-plugins/manager/hooks.go +++ b/vendor/github.com/docker/cli/cli-plugins/manager/hooks.go @@ -6,7 +6,8 @@ import ( "strings" "github.com/docker/cli/cli-plugins/hooks" - "github.com/docker/cli/cli/command" + "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" @@ -29,29 +30,28 @@ type HookPluginData struct { // 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 command.Cli, rootCmd, subCommand *cobra.Command, cmdErrorMessage string) { +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, rootCmd, subCommand, commandName, flags, cmdErrorMessage) + 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 command.Cli, rootCmd, subCommand *cobra.Command, args []string) { +func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, args []string) { commandName := strings.Join(args, " ") flags := getNaiveFlags(args) - runHooks(ctx, dockerCli, rootCmd, subCommand, commandName, flags, "") + runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, "") } -func runHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) { - nextSteps := invokeAndCollectHooks(ctx, dockerCli, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage) - - hooks.PrintNextSteps(dockerCli.Err(), nextSteps) +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, dockerCli command.Cli, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string { +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(): @@ -59,11 +59,15 @@ func invokeAndCollectHooks(ctx context.Context, dockerCli command.Cli, rootCmd, default: } - pluginsCfg := dockerCli.ConfigFile().Plugins + 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) @@ -71,7 +75,7 @@ func invokeAndCollectHooks(ctx context.Context, dockerCli command.Cli, rootCmd, continue } - p, err := GetPlugin(pluginName, dockerCli, rootCmd) + p, err := getPlugin(pluginName, pluginDirs, rootCmd) if err != nil { continue } diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/manager.go b/vendor/github.com/docker/cli/cli-plugins/manager/manager.go index 9f795bc4..4b553eae 100644 --- a/vendor/github.com/docker/cli/cli-plugins/manager/manager.go +++ b/vendor/github.com/docker/cli/cli-plugins/manager/manager.go @@ -9,7 +9,7 @@ import ( "strings" "sync" - "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli-plugins/metadata" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" "github.com/fvbommel/sortorder" @@ -22,10 +22,12 @@ const ( // 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" + 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" ) @@ -91,10 +93,10 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) { continue } name := dentry.Name() - if !strings.HasPrefix(name, NamePrefix) { + if !strings.HasPrefix(name, metadata.NamePrefix) { continue } - name = strings.TrimPrefix(name, NamePrefix) + name = strings.TrimPrefix(name, metadata.NamePrefix) var err error if name, err = trimExeSuffix(name); err != nil { continue @@ -113,12 +115,15 @@ func listPluginCandidates(dirs []string) map[string][]string { } // GetPlugin returns a plugin on the system by its name -func GetPlugin(name string, dockerCli command.Cli, rootcmd *cobra.Command) (*Plugin, error) { - pluginDirs, err := getPluginDirs(dockerCli.ConfigFile()) +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 { @@ -139,7 +144,7 @@ func GetPlugin(name string, dockerCli command.Cli, rootcmd *cobra.Command) (*Plu } // ListPlugins produces a list of the plugins available on the system -func ListPlugins(dockerCli command.Cli, rootcmd *cobra.Command) ([]Plugin, error) { +func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, error) { pluginDirs, err := getPluginDirs(dockerCli.ConfigFile()) if err != nil { return nil, err @@ -186,7 +191,7 @@ func ListPlugins(dockerCli command.Cli, rootcmd *cobra.Command) ([]Plugin, error // 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 command.Cli, name string, rootcmd *cobra.Command) (*exec.Cmd, error) { +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. @@ -196,7 +201,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command // fallback to their "invalid" command path. return nil, errPluginNotFound(name) } - exename := addExeSuffix(NamePrefix + name) + exename := addExeSuffix(metadata.NamePrefix + name) pluginDirs, err := getPluginDirs(dockerCli.ConfigFile()) if err != nil { return nil, err @@ -233,7 +238,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command cmd.Stdout = os.Stdout 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) return cmd, nil @@ -243,5 +248,5 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command // IsPluginCommand checks if the given cmd is a plugin-stub. func IsPluginCommand(cmd *cobra.Command) bool { - return cmd.Annotations[CommandAnnotationPlugin] == "true" + return cmd.Annotations[metadata.CommandAnnotationPlugin] == "true" } diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/metadata.go b/vendor/github.com/docker/cli/cli-plugins/manager/metadata.go index f7aac06f..9bddb121 100644 --- a/vendor/github.com/docker/cli/cli-plugins/manager/metadata.go +++ b/vendor/github.com/docker/cli/cli-plugins/manager/metadata.go @@ -1,30 +1,23 @@ package manager +import ( + "github.com/docker/cli/cli-plugins/metadata" +) + const ( // NamePrefix is the prefix required on all plugin binary names - NamePrefix = "docker-" + NamePrefix = metadata.NamePrefix // 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" + 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 = "docker-cli-plugin-hooks" + HookSubcommandName = metadata.HookSubcommandName ) // 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"` -} +type Metadata = metadata.Metadata diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/plugin.go b/vendor/github.com/docker/cli/cli-plugins/manager/plugin.go index 5576ef43..ec196df1 100644 --- a/vendor/github.com/docker/cli/cli-plugins/manager/plugin.go +++ b/vendor/github.com/docker/cli/cli-plugins/manager/plugin.go @@ -3,13 +3,15 @@ package manager import ( "context" "encoding/json" + "errors" + "fmt" "os" "os/exec" "path/filepath" "regexp" "strings" - "github.com/pkg/errors" + "github.com/docker/cli/cli-plugins/metadata" "github.com/spf13/cobra" ) @@ -17,7 +19,7 @@ var pluginNameRe = regexp.MustCompile("^[a-z][a-z0-9]*$") // Plugin represents a potential plugin with all it's metadata. type Plugin struct { - Metadata + metadata.Metadata Name string `json:",omitempty"` Path string `json:",omitempty"` @@ -44,18 +46,18 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) { // which would fail here, so there are all real errors. fullname := filepath.Base(path) if fullname == "." { - return Plugin{}, errors.Errorf("unable to determine basename of plugin candidate %q", path) + 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{}, errors.Wrapf(err, "plugin candidate %q", path) + return Plugin{}, fmt.Errorf("plugin candidate %q: %w", path, err) } - if !strings.HasPrefix(fullname, NamePrefix) { - return Plugin{}, errors.Errorf("plugin candidate %q: does not have %q prefix", path, NamePrefix) + 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, NamePrefix), + Name: strings.TrimPrefix(fullname, metadata.NamePrefix), Path: path, } @@ -112,9 +114,9 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, 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 = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0]) + 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") diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/suffix_windows.go b/vendor/github.com/docker/cli/cli-plugins/manager/suffix_windows.go index 53b507c8..a00be881 100644 --- a/vendor/github.com/docker/cli/cli-plugins/manager/suffix_windows.go +++ b/vendor/github.com/docker/cli/cli-plugins/manager/suffix_windows.go @@ -1,22 +1,16 @@ package manager import ( + "fmt" "path/filepath" "strings" - - "github.com/pkg/errors" ) -// This is made slightly more complex due to needing to be case insensitive. +// 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 == "" { - return "", errors.Errorf("path %q lacks required file extension", s) - } - - exe := ".exe" - if !strings.EqualFold(ext, exe) { - return "", errors.Errorf("path %q lacks required %q suffix", s, exe) + if ext == "" || !strings.EqualFold(ext, ".exe") { + return "", fmt.Errorf("path %q lacks required file extension (.exe)", s) } return strings.TrimSuffix(s, ext), nil } diff --git a/vendor/github.com/docker/cli/cli-plugins/manager/telemetry.go b/vendor/github.com/docker/cli/cli-plugins/manager/telemetry.go new file mode 100644 index 00000000..ab3dace4 --- /dev/null +++ b/vendor/github.com/docker/cli/cli-plugins/manager/telemetry.go @@ -0,0 +1,85 @@ +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 +} diff --git a/vendor/github.com/docker/cli/cli-plugins/metadata/annotations.go b/vendor/github.com/docker/cli/cli-plugins/metadata/annotations.go new file mode 100644 index 00000000..1c7c6d18 --- /dev/null +++ b/vendor/github.com/docker/cli/cli-plugins/metadata/annotations.go @@ -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" +) diff --git a/vendor/github.com/docker/cli/cli-plugins/metadata/metadata.go b/vendor/github.com/docker/cli/cli-plugins/metadata/metadata.go new file mode 100644 index 00000000..9d408c00 --- /dev/null +++ b/vendor/github.com/docker/cli/cli-plugins/metadata/metadata.go @@ -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"` +} diff --git a/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go b/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go index cf57aad5..08bdf736 100644 --- a/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go +++ b/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go @@ -9,7 +9,7 @@ import ( "sync" "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/command" "github.com/docker/cli/cli/connhelper" @@ -30,7 +30,7 @@ import ( var PersistentPreRunE func(*cobra.Command, []string) error // 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) 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. -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) 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 { return command.WithInitializeClient(func(dockerCli *command.DockerCli) (client.APIClient, error) { cmd := "docker" - if x := os.Getenv(manager.ReexecEnvvar); x != "" { + if x := os.Getenv(metadata.ReexecEnvvar); x != "" { cmd = x } 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() - fullname := manager.NamePrefix + name + fullname := metadata.NamePrefix + name cmd := &cobra.Command{ 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()) } -func newMetadataSubcommand(plugin *cobra.Command, meta manager.Metadata) *cobra.Command { +func newMetadataSubcommand(plugin *cobra.Command, meta metadata.Metadata) *cobra.Command { if meta.ShortDescription == "" { meta.ShortDescription = plugin.Short } cmd := &cobra.Command{ - Use: manager.MetadataSubcommandName, + Use: metadata.MetadataSubcommandName, Hidden: true, // Suppress the global/parent PersistentPreRunE, which // 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 func RunningStandalone() bool { - if os.Getenv(manager.ReexecEnvvar) != "" { + if os.Getenv(metadata.ReexecEnvvar) != "" { return false } - return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName + return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName } diff --git a/vendor/github.com/docker/cli/cli/cobra.go b/vendor/github.com/docker/cli/cli/cobra.go index aa7dbef4..7a14b6f4 100644 --- a/vendor/github.com/docker/cli/cli/cobra.go +++ b/vendor/github.com/docker/cli/cli/cobra.go @@ -3,15 +3,12 @@ package cli import ( "fmt" "os" - "path/filepath" "sort" "strings" - pluginmanager "github.com/docker/cli/cli-plugins/manager" + "github.com/docker/cli/cli-plugins/metadata" "github.com/docker/cli/cli/command" cliflags "github.com/docker/cli/cli/flags" - "github.com/docker/docker/pkg/homedir" - "github.com/docker/docker/registry" "github.com/fvbommel/sortorder" "github.com/moby/term" "github.com/morikuni/aec" @@ -62,13 +59,6 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *c "docs.code-delimiter": `"`, // https://github.com/docker/cli-docs-tool/blob/77abede22166eaea4af7335096bdcedd043f5b19/annotation/annotation.go#L20-L22 } - // Configure registry.CertsDir() when running in rootless-mode - if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" { - if configHome, err := homedir.GetConfigHome(); err == nil { - registry.SetCertsDir(filepath.Join(configHome, "docker/certs.d")) - } - } - return opts, helpCommand } @@ -252,7 +242,7 @@ func hasAdditionalHelp(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 { @@ -356,9 +346,9 @@ func decoratedName(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 := "" - if v, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVersion]; ok && v != "" { + if v, ok := cmd.Annotations[metadata.CommandAnnotationPluginVersion]; ok && v != "" { version = ", " + v } return fmt.Sprintf("(%s%s)", vendor, version) @@ -417,7 +407,7 @@ func invalidPlugins(cmd *cobra.Command) []*cobra.Command { } func invalidPluginReason(cmd *cobra.Command) string { - return cmd.Annotations[pluginmanager.CommandAnnotationPluginInvalid] + return cmd.Annotations[metadata.CommandAnnotationPluginInvalid] } const usageTemplate = `Usage: diff --git a/vendor/github.com/docker/cli/cli/command/cli.go b/vendor/github.com/docker/cli/cli/command/cli.go index 227720fa..8b5780d6 100644 --- a/vendor/github.com/docker/cli/cli/command/cli.go +++ b/vendor/github.com/docker/cli/cli/command/cli.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "os" - "path/filepath" "runtime" "strconv" "sync" @@ -21,21 +20,15 @@ import ( "github.com/docker/cli/cli/context/store" "github.com/docker/cli/cli/debug" cliflags "github.com/docker/cli/cli/flags" - manifeststore "github.com/docker/cli/cli/manifest/store" - registryclient "github.com/docker/cli/cli/registry/client" "github.com/docker/cli/cli/streams" - "github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/version" dopts "github.com/docker/cli/opts" "github.com/docker/docker/api" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" - "github.com/docker/go-connections/tlsconfig" "github.com/pkg/errors" "github.com/spf13/cobra" - notaryclient "github.com/theupdateframework/notary/client" ) const defaultInitTimeout = 2 * time.Second @@ -53,19 +46,18 @@ type Cli interface { Streams SetIn(in *streams.In) Apply(ops ...CLIOption) error - ConfigFile() *configfile.ConfigFile + config.Provider ServerInfo() ServerInfo - NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) DefaultVersion() string CurrentVersion() string - ManifestStore() manifeststore.Store - RegistryClient(bool) registryclient.RegistryClient ContentTrustEnabled() bool BuildKitEnabled() (bool, error) ContextStore() store.Store CurrentContext() string DockerEndpoint() docker.Endpoint TelemetryClient + DeprecatedNotaryClient + DeprecatedManifestClient } // DockerCli is an instance the docker command line client. @@ -96,7 +88,7 @@ type DockerCli struct { enableGlobalMeter, enableGlobalTracer bool } -// DefaultVersion returns api.defaultVersion. +// DefaultVersion returns [api.DefaultVersion]. func (*DockerCli) DefaultVersion() string { return api.DefaultVersion } @@ -202,16 +194,16 @@ func (cli *DockerCli) BuildKitEnabled() (bool, error) { // HooksEnabled returns whether plugin hooks are enabled. func (cli *DockerCli) HooksEnabled() bool { - // legacy support DOCKER_CLI_HINTS env var - if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" { + // use DOCKER_CLI_HOOKS env var value if set and not empty + if v := os.Getenv("DOCKER_CLI_HOOKS"); v != "" { enabled, err := strconv.ParseBool(v) if err != nil { return false } return enabled } - // use DOCKER_CLI_HOOKS env var value if set and not empty - if v := os.Getenv("DOCKER_CLI_HOOKS"); v != "" { + // legacy support DOCKER_CLI_HINTS env var + if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" { enabled, err := strconv.ParseBool(v) if err != nil { return false @@ -230,21 +222,6 @@ func (cli *DockerCli) HooksEnabled() bool { return false } -// ManifestStore returns a store for local manifests -func (*DockerCli) ManifestStore() manifeststore.Store { - // TODO: support override default location from config file - return manifeststore.NewStore(filepath.Join(config.Dir(), "manifests")) -} - -// RegistryClient returns a client for communicating with a Docker distribution -// registry -func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.RegistryClient { - resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig { - return ResolveAuthConfig(cli.ConfigFile(), index) - } - return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure) -} - // WithInitializeClient is passed to DockerCli.Initialize by callers who wish to set a particular API Client for use by the CLI. func WithInitializeClient(makeClient func(dockerCli *DockerCli) (client.APIClient, error)) CLIOption { return func(dockerCli *DockerCli) error { @@ -292,6 +269,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) if cli.enableGlobalTracer { cli.createGlobalTracerProvider(cli.baseCtx) } + filterResourceAttributesEnvvar() return nil } @@ -345,7 +323,10 @@ func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, // Resolve the Docker endpoint for the default context (based on config, env vars and CLI flags) func resolveDefaultDockerEndpoint(opts *cliflags.ClientOptions) (docker.Endpoint, error) { - host, err := getServerHost(opts.Hosts, opts.TLSOptions) + // defaultToTLS determines whether we should use a TLS host as default + // if nothing was configured by the user. + defaultToTLS := opts.TLSOptions != nil + host, err := getServerHost(opts.Hosts, defaultToTLS) if err != nil { return docker.Endpoint{}, err } @@ -403,11 +384,6 @@ func (cli *DockerCli) initializeFromClient() { cli.client.NegotiateAPIVersionPing(ping) } -// NotaryClient provides a Notary Repository to interact with signed metadata for an image -func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) { - return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...) -} - // ContextStore returns the ContextStore func (cli *DockerCli) ContextStore() store.Store { return cli.contextStore @@ -553,18 +529,15 @@ func NewDockerCli(ops ...CLIOption) (*DockerCli, error) { return cli, nil } -func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) { - var host string +func getServerHost(hosts []string, defaultToTLS bool) (string, error) { switch len(hosts) { case 0: - host = os.Getenv(client.EnvOverrideHost) + return dopts.ParseHost(defaultToTLS, os.Getenv(client.EnvOverrideHost)) case 1: - host = hosts[0] + return dopts.ParseHost(defaultToTLS, hosts[0]) default: return "", errors.New("Specify only one -H") } - - return dopts.ParseHost(tlsOptions != nil, host) } // UserAgent returns the user agent string used for making API requests diff --git a/vendor/github.com/docker/cli/cli/command/cli_deprecated.go b/vendor/github.com/docker/cli/cli/command/cli_deprecated.go new file mode 100644 index 00000000..15fac1a6 --- /dev/null +++ b/vendor/github.com/docker/cli/cli/command/cli_deprecated.go @@ -0,0 +1,56 @@ +package command + +import ( + "context" + "path/filepath" + + "github.com/docker/cli/cli/config" + manifeststore "github.com/docker/cli/cli/manifest/store" + registryclient "github.com/docker/cli/cli/registry/client" + "github.com/docker/cli/cli/trust" + "github.com/docker/docker/api/types/registry" + notaryclient "github.com/theupdateframework/notary/client" +) + +type DeprecatedNotaryClient interface { + // NotaryClient provides a Notary Repository to interact with signed metadata for an image + // + // Deprecated: use [trust.GetNotaryRepository] instead. This method is no longer used and will be removed in the next release. + NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) +} + +type DeprecatedManifestClient interface { + // ManifestStore returns a store for local manifests + // + // Deprecated: use [manifeststore.NewStore] instead. This method is no longer used and will be removed in the next release. + ManifestStore() manifeststore.Store + + // RegistryClient returns a client for communicating with a Docker distribution + // registry. + // + // Deprecated: use [registryclient.NewRegistryClient]. This method is no longer used and will be removed in the next release. + RegistryClient(bool) registryclient.RegistryClient +} + +// NotaryClient provides a Notary Repository to interact with signed metadata for an image +func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) { + return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...) +} + +// ManifestStore returns a store for local manifests +// +// Deprecated: use [manifeststore.NewStore] instead. This method is no longer used and will be removed in the next release. +func (*DockerCli) ManifestStore() manifeststore.Store { + return manifeststore.NewStore(filepath.Join(config.Dir(), "manifests")) +} + +// RegistryClient returns a client for communicating with a Docker distribution +// registry +// +// Deprecated: use [registryclient.NewRegistryClient]. This method is no longer used and will be removed in the next release. +func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.RegistryClient { + resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig { + return ResolveAuthConfig(cli.ConfigFile(), index) + } + return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure) +} diff --git a/vendor/github.com/docker/cli/cli/command/formatter/displayutils.go b/vendor/github.com/docker/cli/cli/command/formatter/displayutils.go index 7847bb30..ad5c2a36 100644 --- a/vendor/github.com/docker/cli/cli/command/formatter/displayutils.go +++ b/vendor/github.com/docker/cli/cli/command/formatter/displayutils.go @@ -1,6 +1,11 @@ +// 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 formatter import ( + "fmt" + "strings" "unicode/utf8" "golang.org/x/text/width" @@ -59,3 +64,27 @@ func Ellipsis(s string, maxDisplayWidth int) string { } return s } + +// capitalizeFirst capitalizes the first character of string +func capitalizeFirst(s string) string { + switch l := len(s); l { + case 0: + return s + case 1: + return strings.ToLower(s) + default: + return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:]) + } +} + +// PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter. +func PrettyPrint(i any) string { + switch t := i.(type) { + case nil: + return "None" + case string: + return capitalizeFirst(t) + default: + return capitalizeFirst(fmt.Sprintf("%s", t)) + } +} diff --git a/vendor/github.com/docker/cli/cli/command/formatter/formatter.go b/vendor/github.com/docker/cli/cli/command/formatter/formatter.go index 5873cce8..a0771389 100644 --- a/vendor/github.com/docker/cli/cli/command/formatter/formatter.go +++ b/vendor/github.com/docker/cli/cli/command/formatter/formatter.go @@ -76,9 +76,9 @@ func (c *Context) preFormat() { func (c *Context) parseFormat() (*template.Template, error) { tmpl, err := templates.Parse(c.finalFormat) if err != nil { - return tmpl, errors.Wrap(err, "template parsing error") + return nil, errors.Wrap(err, "template parsing error") } - return tmpl, err + return tmpl, nil } func (c *Context) postFormat(tmpl *template.Template, subContext SubContext) { diff --git a/vendor/github.com/docker/cli/cli/command/telemetry.go b/vendor/github.com/docker/cli/cli/command/telemetry.go index 2ee8adfb..e8e6296b 100644 --- a/vendor/github.com/docker/cli/cli/command/telemetry.go +++ b/vendor/github.com/docker/cli/cli/command/telemetry.go @@ -4,10 +4,11 @@ import ( "context" "os" "path/filepath" + "strings" "sync" "time" - "github.com/docker/distribution/uuid" + "github.com/google/uuid" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" sdkmetric "go.opentelemetry.io/otel/sdk/metric" @@ -142,7 +143,7 @@ func defaultResourceOptions() []resource.Option { // of the CLI is its own instance. Without this, downstream // OTEL processors may think the same process is restarting // continuously. - semconv.ServiceInstanceID(uuid.Generate().String()), + semconv.ServiceInstanceID(uuid.NewString()), ), resource.WithFromEnv(), resource.WithTelemetrySDK(), @@ -216,3 +217,49 @@ func (r *cliReader) ForceFlush(ctx context.Context) error { func deltaTemporality(_ sdkmetric.InstrumentKind) metricdata.Temporality { return metricdata.DeltaTemporality } + +// 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 +const resourceAttributesEnvVar = "OTEL_RESOURCE_ATTRIBUTES" + +func filterResourceAttributesEnvvar() { + if v := os.Getenv(resourceAttributesEnvVar); v != "" { + if filtered := filterResourceAttributes(v); filtered != "" { + _ = os.Setenv(resourceAttributesEnvVar, filtered) + } else { + _ = os.Unsetenv(resourceAttributesEnvVar) + } + } +} + +// dockerCLIAttributePrefix is the prefix for any docker cli OTEL attributes. +// When updating, make sure to also update the copy in cli-plugins/manager. +// +// TODO(thaJeztah): move telemetry-related code to an (internal) package to reduce dependency on cli/command in cli-plugins, which has too many imports. +const dockerCLIAttributePrefix = "docker.cli." + +func filterResourceAttributes(s string) string { + if trimmed := strings.TrimSpace(s); trimmed == "" { + return trimmed + } + + pairs := strings.Split(s, ",") + elems := make([]string, 0, len(pairs)) + for _, p := range pairs { + k, _, found := strings.Cut(p, "=") + if !found { + // Do not interact with invalid otel resources. + elems = append(elems, p) + continue + } + + // Skip attributes that have our docker.cli prefix. + if strings.HasPrefix(k, dockerCLIAttributePrefix) { + continue + } + elems = append(elems, p) + } + return strings.Join(elems, ",") +} diff --git a/vendor/github.com/docker/cli/cli/command/utils.go b/vendor/github.com/docker/cli/cli/command/utils.go index 8a8368fb..de692052 100644 --- a/vendor/github.com/docker/cli/cli/command/utils.go +++ b/vendor/github.com/docker/cli/cli/command/utils.go @@ -13,10 +13,9 @@ import ( "runtime" "strings" + "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/streams" "github.com/docker/docker/api/types/filters" - mounttypes "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/api/types/versions" "github.com/docker/docker/errdefs" "github.com/moby/sys/sequential" "github.com/moby/term" @@ -51,30 +50,6 @@ func CopyToFile(outfile string, r io.Reader) error { return nil } -// capitalizeFirst capitalizes the first character of string -func capitalizeFirst(s string) string { - switch l := len(s); l { - case 0: - return s - case 1: - return strings.ToLower(s) - default: - return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:]) - } -} - -// PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter. -func PrettyPrint(i any) string { - switch t := i.(type) { - case nil: - return "None" - case string: - return capitalizeFirst(t) - default: - return capitalizeFirst(fmt.Sprintf("%s", t)) - } -} - var ErrPromptTerminated = errdefs.Cancelled(errors.New("prompt terminated")) // DisableInputEcho disables input echo on the provided streams.In. @@ -166,11 +141,12 @@ func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, m } // PruneFilters returns consolidated prune filters obtained from config.json and cli -func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args { - if dockerCli.ConfigFile() == nil { +func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.Args { + cfg := dockerCLI.ConfigFile() + if cfg == nil { return pruneFilters } - for _, f := range dockerCli.ConfigFile().PruneFilters { + for _, f := range cfg.PruneFilters { k, v, ok := strings.Cut(f, "=") if !ok { continue @@ -239,48 +215,3 @@ func ValidateOutputPathFileMode(fileMode os.FileMode) error { } return nil } - -func stringSliceIndex(s, subs []string) int { - j := 0 - if len(subs) > 0 { - for i, x := range s { - if j < len(subs) && subs[j] == x { - j++ - } else { - j = 0 - } - if len(subs) == j { - return i + 1 - j - } - } - } - return -1 -} - -// StringSliceReplaceAt replaces the sub-slice find, with the sub-slice replace, in the string -// slice s, returning a new slice and a boolean indicating if the replacement happened. -// requireIdx is the index at which old needs to be found at (or -1 to disregard that). -func StringSliceReplaceAt(s, find, replace []string, requireIndex int) ([]string, bool) { - idx := stringSliceIndex(s, find) - if (requireIndex != -1 && requireIndex != idx) || idx == -1 { - return s, false - } - out := append([]string{}, s[:idx]...) - out = append(out, replace...) - out = append(out, s[idx+len(find):]...) - return out, true -} - -// ValidateMountWithAPIVersion validates a mount with the server API version. -func ValidateMountWithAPIVersion(m mounttypes.Mount, serverAPIVersion string) error { - if m.BindOptions != nil { - if m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") { - return errors.Errorf("bind-recursive=disabled requires API v1.40 or later") - } - // ReadOnlyNonRecursive can be safely ignored when API < 1.44 - if m.BindOptions.ReadOnlyForceRecursive && versions.LessThan(serverAPIVersion, "1.44") { - return errors.Errorf("bind-recursive=readonly requires API v1.44 or later") - } - } - return nil -} diff --git a/vendor/github.com/docker/cli/cli/config/config.go b/vendor/github.com/docker/cli/cli/config/config.go index 910b3c00..daf50433 100644 --- a/vendor/github.com/docker/cli/cli/config/config.go +++ b/vendor/github.com/docker/cli/cli/config/config.go @@ -69,6 +69,11 @@ func getHomeDir() string { return home } +// Provider defines an interface for providing the CLI config. +type Provider interface { + ConfigFile() *configfile.ConfigFile +} + // Dir returns the directory the configuration file is stored in func Dir() string { initConfigDir.Do(func() { diff --git a/vendor/github.com/docker/cli/cli/internal/jsonstream/display.go b/vendor/github.com/docker/cli/cli/internal/jsonstream/display.go new file mode 100644 index 00000000..8981eca3 --- /dev/null +++ b/vendor/github.com/docker/cli/cli/internal/jsonstream/display.go @@ -0,0 +1,68 @@ +package jsonstream + +import ( + "context" + "io" + + "github.com/docker/docker/pkg/jsonmessage" +) + +type ( + Stream = jsonmessage.Stream + JSONMessage = jsonmessage.JSONMessage + JSONError = jsonmessage.JSONError + JSONProgress = jsonmessage.JSONProgress +) + +type ctxReader struct { + err chan error + r io.Reader +} + +func (r *ctxReader) Read(p []byte) (n int, err error) { + select { + case err = <-r.err: + return 0, err + default: + return r.r.Read(p) + } +} + +type Options func(*options) + +type options struct { + AuxCallback func(JSONMessage) +} + +func WithAuxCallback(cb func(JSONMessage)) Options { + return func(o *options) { + o.AuxCallback = cb + } +} + +// Display prints the JSON messages from the given reader to the given stream. +// +// It wraps the [jsonmessage.DisplayJSONMessagesStream] function to make it +// "context aware" and appropriately returns why the function was canceled. +// +// It returns an error if the context is canceled, but not if the input reader / stream is closed. +func Display(ctx context.Context, in io.Reader, stream Stream, opts ...Options) error { + if ctx.Err() != nil { + return ctx.Err() + } + + reader := &ctxReader{err: make(chan error, 1), r: in} + stopFunc := context.AfterFunc(ctx, func() { reader.err <- ctx.Err() }) + defer stopFunc() + + o := options{} + for _, opt := range opts { + opt(&o) + } + + if err := jsonmessage.DisplayJSONMessagesStream(reader, stream, stream.FD(), stream.IsTerminal(), o.AuxCallback); err != nil { + return err + } + + return ctx.Err() +} diff --git a/vendor/github.com/docker/cli/cli/registry/client/client.go b/vendor/github.com/docker/cli/cli/registry/client/client.go index bbc7f4c5..31975d48 100644 --- a/vendor/github.com/docker/cli/cli/registry/client/client.go +++ b/vendor/github.com/docker/cli/cli/registry/client/client.go @@ -8,7 +8,6 @@ import ( "github.com/distribution/reference" manifesttypes "github.com/docker/cli/cli/manifest/types" - "github.com/docker/cli/cli/trust" "github.com/docker/distribution" distributionclient "github.com/docker/distribution/registry/client" registrytypes "github.com/docker/docker/api/types/registry" @@ -38,12 +37,6 @@ func NewRegistryClient(resolver AuthConfigResolver, userAgent string, insecure b // AuthConfigResolver returns Auth Configuration for an index type AuthConfigResolver func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig -// PutManifestOptions is the data sent to push a manifest -type PutManifestOptions struct { - MediaType string - Payload []byte -} - type client struct { authConfigResolver AuthConfigResolver insecureRegistry bool @@ -61,13 +54,13 @@ func (err ErrBlobCreated) Error() string { err.From, err.Target) } -// ErrHTTPProto returned if attempting to use TLS with a non-TLS registry -type ErrHTTPProto struct { - OrigErr string +// httpProtoError returned if attempting to use TLS with a non-TLS registry +type httpProtoError struct { + cause error } -func (err ErrHTTPProto) Error() string { - return err.OrigErr +func (e httpProtoError) Error() string { + return e.cause.Error() } var _ RegistryClient = &client{} @@ -78,7 +71,7 @@ func (c *client) MountBlob(ctx context.Context, sourceRef reference.Canonical, t if err != nil { return err } - repoEndpoint.actions = trust.ActionsPushAndPull + repoEndpoint.actions = []string{"pull", "push"} repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint) if err != nil { return err @@ -104,7 +97,7 @@ func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest return "", err } - repoEndpoint.actions = trust.ActionsPushAndPull + repoEndpoint.actions = []string{"pull", "push"} repo, err := c.getRepositoryForReference(ctx, ref, repoEndpoint) if err != nil { return "", err @@ -121,7 +114,10 @@ func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest } dgst, err := manifestService.Put(ctx, manifest, opts...) - return dgst, errors.Wrapf(err, "failed to put manifest %s", ref) + if err != nil { + return dgst, errors.Wrapf(err, "failed to put manifest %s", ref) + } + return dgst, nil } func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) { @@ -135,7 +131,7 @@ func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Na return nil, err } if !repoEndpoint.endpoint.TLSConfig.InsecureSkipVerify { - return nil, ErrHTTPProto{OrigErr: err.Error()} + return nil, httpProtoError{cause: err} } // --insecure was set; fall back to plain HTTP if url := repoEndpoint.endpoint.URL; url != nil && url.Scheme == "https" { @@ -157,7 +153,10 @@ func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoi c.userAgent, repoEndpoint.actions, ) - return httpTransport, errors.Wrap(err, "failed to configure transport") + if err != nil { + return nil, errors.Wrap(err, "failed to configure transport") + } + return httpTransport, nil } // GetManifest returns an ImageManifest for the reference diff --git a/vendor/github.com/docker/cli/cli/registry/client/endpoint.go b/vendor/github.com/docker/cli/cli/registry/client/endpoint.go index 95312b05..ab35e73c 100644 --- a/vendor/github.com/docker/cli/cli/registry/client/endpoint.go +++ b/vendor/github.com/docker/cli/cli/registry/client/endpoint.go @@ -6,7 +6,6 @@ import ( "time" "github.com/distribution/reference" - "github.com/docker/cli/cli/trust" "github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/transport" registrytypes "github.com/docker/docker/api/types/registry" @@ -31,10 +30,7 @@ func (r repositoryEndpoint) BaseURL() string { } func newDefaultRepositoryEndpoint(ref reference.Named, insecure bool) (repositoryEndpoint, error) { - repoInfo, err := registry.ParseRepositoryInfo(ref) - if err != nil { - return repositoryEndpoint{}, err - } + repoInfo, _ := registry.ParseRepositoryInfo(ref) endpoint, err := getDefaultEndpointFromRepoInfo(repoInfo) if err != nil { return repositoryEndpoint{}, err @@ -94,7 +90,7 @@ func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.API modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) } else { if len(actions) == 0 { - actions = trust.ActionsPullOnly + actions = []string{"pull"} } creds := registry.NewStaticCredentialStore(&authConfig) tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...) @@ -104,14 +100,11 @@ func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.API return transport.NewTransport(base, modifiers...), nil } -// RepoNameForReference returns the repository name from a reference +// RepoNameForReference returns the repository name from a reference. +// +// Deprecated: this function is no longer used and will be removed in the next release. func RepoNameForReference(ref reference.Named) (string, error) { - // insecure is fine since this only returns the name - repo, err := newDefaultRepositoryEndpoint(ref, false) - if err != nil { - return "", err - } - return repo.Name(), nil + return reference.Path(reference.TrimNamed(ref)), nil } type existingTokenHandler struct { diff --git a/vendor/github.com/docker/cli/cli/registry/client/fetcher.go b/vendor/github.com/docker/cli/cli/registry/client/fetcher.go index d1f255bf..f0a2f986 100644 --- a/vendor/github.com/docker/cli/cli/registry/client/fetcher.go +++ b/vendor/github.com/docker/cli/cli/registry/client/fetcher.go @@ -220,10 +220,7 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, return err } - repoInfo, err := registry.ParseRepositoryInfo(namedRef) - if err != nil { - return err - } + repoInfo, _ := registry.ParseRepositoryInfo(namedRef) confirmedTLSRegistries := make(map[string]bool) for _, endpoint := range endpoints { @@ -241,7 +238,8 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint) if err != nil { logrus.Debugf("error %s with repo endpoint %+v", err, repoEndpoint) - if _, ok := err.(ErrHTTPProto); ok { + var protoErr httpProtoError + if errors.As(err, &protoErr) { continue } return err @@ -272,11 +270,6 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, // allEndpoints returns a list of endpoints ordered by priority (v2, http). func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) { - repoInfo, err := registry.ParseRepositoryInfo(namedRef) - if err != nil { - return nil, err - } - var serviceOpts registry.ServiceOptions if insecure { logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef)) @@ -286,6 +279,7 @@ func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoi if err != nil { return []registry.APIEndpoint{}, err } + repoInfo, _ := registry.ParseRepositoryInfo(namedRef) endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name)) logrus.Debugf("endpoints for %s: %v", namedRef, endpoints) return endpoints, err diff --git a/vendor/github.com/docker/cli/cli/trust/trust.go b/vendor/github.com/docker/cli/cli/trust/trust.go index bb7e597a..5e7aff3d 100644 --- a/vendor/github.com/docker/cli/cli/trust/trust.go +++ b/vendor/github.com/docker/cli/cli/trust/trust.go @@ -40,10 +40,11 @@ var ( ActionsPullOnly = []string{"pull"} // ActionsPushAndPull defines the actions for read-write interactions with a Notary Repository ActionsPushAndPull = []string{"pull", "push"} - // NotaryServer is the endpoint serving the Notary trust server - NotaryServer = "https://notary.docker.io" ) +// NotaryServer is the endpoint serving the Notary trust server +const NotaryServer = "https://notary.docker.io" + // GetTrustDirectory returns the base trust directory name func GetTrustDirectory() string { return filepath.Join(config.Dir(), "trust") @@ -238,6 +239,20 @@ func NotaryError(repoName string, err error) error { return err } +// AddToAllSignableRoles attempts to add the image target to all the top level +// delegation roles we can (based on whether we have the signing key and whether +// the role's path allows us to). +// +// If there are no delegation roles, we add to the targets role. +func AddToAllSignableRoles(repo client.Repository, target *client.Target) error { + signableRoles, err := GetSignableRoles(repo, target) + if err != nil { + return err + } + + return repo.AddTarget(target, signableRoles...) +} + // GetSignableRoles returns a list of roles for which we have valid signing // keys, given a notary repository and a target func GetSignableRoles(repo client.Repository, target *client.Target) ([]data.RoleName, error) { @@ -307,11 +322,7 @@ func GetImageReferencesAndAuth(ctx context.Context, } // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := registry.ParseRepositoryInfo(ref) - if err != nil { - return ImageRefAndAuth{}, err - } - + repoInfo, _ := registry.ParseRepositoryInfo(ref) authConfig := authResolver(ctx, repoInfo.Index) return ImageRefAndAuth{ original: imgName, diff --git a/vendor/github.com/docker/cli/cli/trust/trust_push.go b/vendor/github.com/docker/cli/cli/trust/trust_push.go new file mode 100644 index 00000000..63fbdf93 --- /dev/null +++ b/vendor/github.com/docker/cli/cli/trust/trust_push.go @@ -0,0 +1,143 @@ +package trust + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "sort" + + "github.com/distribution/reference" + "github.com/docker/cli/cli/internal/jsonstream" + "github.com/docker/cli/cli/streams" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/registry" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/theupdateframework/notary/client" + "github.com/theupdateframework/notary/tuf/data" +) + +// Streams is an interface which exposes the standard input and output streams. +// +// Same interface as [github.com/docker/cli/cli/command.Streams] but defined here to prevent a circular import. +type Streams interface { + In() *streams.In + Out() *streams.Out + Err() *streams.Out +} + +// PushTrustedReference pushes a canonical reference to the trust server. +// +//nolint:gocyclo +func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader, userAgent string) error { + // If it is a trusted push we would like to find the target entry which match the + // tag provided in the function and then do an AddTarget later. + notaryTarget := &client.Target{} + // Count the times of calling for handleTarget, + // if it is called more that once, that should be considered an error in a trusted push. + cnt := 0 + handleTarget := func(msg jsonstream.JSONMessage) { + cnt++ + if cnt > 1 { + // handleTarget should only be called once. This will be treated as an error. + return + } + + var pushResult types.PushResult + err := json.Unmarshal(*msg.Aux, &pushResult) + if err == nil && pushResult.Tag != "" { + if dgst, err := digest.Parse(pushResult.Digest); err == nil { + h, err := hex.DecodeString(dgst.Hex()) + if err != nil { + notaryTarget = nil + return + } + notaryTarget.Name = pushResult.Tag + notaryTarget.Hashes = data.Hashes{string(dgst.Algorithm()): h} + notaryTarget.Length = int64(pushResult.Size) + } + } + } + + var tag string + switch x := ref.(type) { + case reference.Canonical: + return errors.New("cannot push a digest reference") + case reference.NamedTagged: + tag = x.Tag() + default: + // We want trust signatures to always take an explicit tag, + // otherwise it will act as an untrusted push. + if err := jsonstream.Display(ctx, in, ioStreams.Out()); err != nil { + return err + } + _, _ = fmt.Fprintln(ioStreams.Err(), "No tag specified, skipping trust metadata push") + return nil + } + + if err := jsonstream.Display(ctx, in, ioStreams.Out(), jsonstream.WithAuxCallback(handleTarget)); err != nil { + return err + } + + if cnt > 1 { + return errors.Errorf("internal error: only one call to handleTarget expected") + } + + if notaryTarget == nil { + return errors.Errorf("no targets found, provide a specific tag in order to sign it") + } + + _, _ = fmt.Fprintln(ioStreams.Out(), "Signing and pushing trust metadata") + + repo, err := GetNotaryRepository(ioStreams.In(), ioStreams.Out(), userAgent, repoInfo, &authConfig, "push", "pull") + if err != nil { + return errors.Wrap(err, "error establishing connection to trust repository") + } + + // get the latest repository metadata so we can figure out which roles to sign + _, err = repo.ListTargets() + + switch err.(type) { + case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist: + keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole) + var rootKeyID string + // always select the first root key + if len(keys) > 0 { + sort.Strings(keys) + rootKeyID = keys[0] + } else { + rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey) + if err != nil { + return err + } + rootKeyID = rootPublicKey.ID() + } + + // Initialize the notary repository with a remotely managed snapshot key + if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil { + return NotaryError(repoInfo.Name.Name(), err) + } + _, _ = fmt.Fprintf(ioStreams.Out(), "Finished initializing %q\n", repoInfo.Name.Name()) + err = repo.AddTarget(notaryTarget, data.CanonicalTargetsRole) + case nil: + // already initialized and we have successfully downloaded the latest metadata + err = AddToAllSignableRoles(repo, notaryTarget) + default: + return NotaryError(repoInfo.Name.Name(), err) + } + + if err == nil { + err = repo.Publish() + } + + if err != nil { + err = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag) + return NotaryError(repoInfo.Name.Name(), err) + } + + _, _ = fmt.Fprintf(ioStreams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag) + return nil +} diff --git a/vendor/github.com/docker/cli/cli/trust/trust_tag.go b/vendor/github.com/docker/cli/cli/trust/trust_tag.go new file mode 100644 index 00000000..053f9317 --- /dev/null +++ b/vendor/github.com/docker/cli/cli/trust/trust_tag.go @@ -0,0 +1,22 @@ +package trust + +import ( + "context" + "fmt" + "io" + + "github.com/distribution/reference" + "github.com/docker/docker/client" +) + +// TagTrusted tags a trusted ref. It is a shallow wrapper around [client.Client.ImageTag] +// that updates the given image references to their familiar format for tagging +// and printing. +func TagTrusted(ctx context.Context, apiClient client.ImageAPIClient, out io.Writer, trustedRef reference.Canonical, ref reference.NamedTagged) error { + // Use familiar references when interacting with client and output + familiarRef := reference.FamiliarString(ref) + trustedFamiliarRef := reference.FamiliarString(trustedRef) + + _, _ = fmt.Fprintf(out, "Tagging %s as %s\n", trustedFamiliarRef, familiarRef) + return apiClient.ImageTag(ctx, trustedFamiliarRef, familiarRef) +} diff --git a/vendor/github.com/docker/cli/opts/duration.go b/vendor/github.com/docker/cli/opts/duration.go index d55c51e6..41a27a56 100644 --- a/vendor/github.com/docker/cli/opts/duration.go +++ b/vendor/github.com/docker/cli/opts/duration.go @@ -1,9 +1,8 @@ package opts import ( + "errors" "time" - - "github.com/pkg/errors" ) // PositiveDurationOpt is an option type for time.Duration that uses a pointer. @@ -20,7 +19,7 @@ func (d *PositiveDurationOpt) Set(s string) error { return err } if *d.DurationOpt.value < 0 { - return errors.Errorf("duration cannot be negative") + return errors.New("duration cannot be negative") } return nil } diff --git a/vendor/github.com/docker/cli/opts/env.go b/vendor/github.com/docker/cli/opts/env.go index 214d6f44..675ddda9 100644 --- a/vendor/github.com/docker/cli/opts/env.go +++ b/vendor/github.com/docker/cli/opts/env.go @@ -1,10 +1,9 @@ package opts import ( + "errors" "os" "strings" - - "github.com/pkg/errors" ) // ValidateEnv validates an environment variable and returns it. diff --git a/vendor/github.com/docker/cli/opts/gpus.go b/vendor/github.com/docker/cli/opts/gpus.go index 993f6da9..6a56c49c 100644 --- a/vendor/github.com/docker/cli/opts/gpus.go +++ b/vendor/github.com/docker/cli/opts/gpus.go @@ -2,12 +2,12 @@ package opts import ( "encoding/csv" + "errors" "fmt" "strconv" "strings" "github.com/docker/docker/api/types/container" - "github.com/pkg/errors" ) // GpuOpts is a Value type for parsing mounts @@ -20,7 +20,14 @@ func parseCount(s string) (int, error) { return -1, nil } i, err := strconv.Atoi(s) - return i, errors.Wrap(err, "count must be an integer") + if err != nil { + var numErr *strconv.NumError + if errors.As(err, &numErr) { + err = numErr.Err + } + return 0, fmt.Errorf(`invalid count (%s): value must be either "all" or an integer: %w`, s, err) + } + return i, nil } // Set a new mount value @@ -69,7 +76,7 @@ func (o *GpuOpts) Set(value string) error { r := csv.NewReader(strings.NewReader(val)) optFields, err := r.Read() if err != nil { - return errors.Wrap(err, "failed to read gpu options") + return fmt.Errorf("failed to read gpu options: %w", err) } req.Options = ConvertKVStringsToMap(optFields) default: diff --git a/vendor/github.com/docker/cli/opts/mount.go b/vendor/github.com/docker/cli/opts/mount.go index 275a4d7f..a4219b07 100644 --- a/vendor/github.com/docker/cli/opts/mount.go +++ b/vendor/github.com/docker/cli/opts/mount.go @@ -135,8 +135,7 @@ func (m *MountOpt) Set(value string) error { // TODO: implicitly set propagation and error if the user specifies a propagation in a future refactor/UX polish pass // https://github.com/docker/cli/pull/4316#discussion_r1341974730 default: - return fmt.Errorf("invalid value for %s: %s (must be \"enabled\", \"disabled\", \"writable\", or \"readonly\")", - key, val) + return fmt.Errorf(`invalid value for %s: %s (must be "enabled", "disabled", "writable", or "readonly")`, key, val) } case "volume-subpath": volumeOptions().Subpath = val diff --git a/vendor/github.com/docker/cli/opts/network.go b/vendor/github.com/docker/cli/opts/network.go index c3510870..43b3a09d 100644 --- a/vendor/github.com/docker/cli/opts/network.go +++ b/vendor/github.com/docker/cli/opts/network.go @@ -89,7 +89,11 @@ func (n *NetworkOpt) Set(value string) error { //nolint:gocyclo case gwPriorityOpt: netOpt.GwPriority, err = strconv.Atoi(val) if err != nil { - return fmt.Errorf("invalid gw-priority: %w", err) + var numErr *strconv.NumError + if errors.As(err, &numErr) { + err = numErr.Err + } + return fmt.Errorf("invalid gw-priority (%s): %w", val, err) } default: return errors.New("invalid field key " + key) diff --git a/vendor/github.com/docker/cli/opts/opts.go b/vendor/github.com/docker/cli/opts/opts.go index 061fda57..f6b33707 100644 --- a/vendor/github.com/docker/cli/opts/opts.go +++ b/vendor/github.com/docker/cli/opts/opts.go @@ -1,6 +1,7 @@ package opts import ( + "errors" "fmt" "math/big" "net" @@ -9,8 +10,7 @@ import ( "strings" "github.com/docker/docker/api/types/filters" - units "github.com/docker/go-units" - "github.com/pkg/errors" + "github.com/docker/go-units" ) var ( diff --git a/vendor/github.com/docker/cli/opts/opts_deprecated.go b/vendor/github.com/docker/cli/opts/opts_deprecated.go new file mode 100644 index 00000000..2eab7a12 --- /dev/null +++ b/vendor/github.com/docker/cli/opts/opts_deprecated.go @@ -0,0 +1,18 @@ +package opts + +import "github.com/docker/cli/opts/swarmopts" + +// PortOpt represents a port config in swarm mode. +// +// Deprecated: use [swarmopts.PortOpt] +type PortOpt = swarmopts.PortOpt + +// ConfigOpt is a Value type for parsing configs. +// +// Deprecated: use [swarmopts.ConfigOpt] +type ConfigOpt = swarmopts.ConfigOpt + +// SecretOpt is a Value type for parsing secrets +// +// Deprecated: use [swarmopts.SecretOpt] +type SecretOpt = swarmopts.SecretOpt diff --git a/vendor/github.com/docker/cli/opts/config.go b/vendor/github.com/docker/cli/opts/swarmopts/config.go similarity index 88% rename from vendor/github.com/docker/cli/opts/config.go rename to vendor/github.com/docker/cli/opts/swarmopts/config.go index 1fc0eb35..ff137304 100644 --- a/vendor/github.com/docker/cli/opts/config.go +++ b/vendor/github.com/docker/cli/opts/swarmopts/config.go @@ -1,4 +1,4 @@ -package opts +package swarmopts import ( "encoding/csv" @@ -8,12 +8,12 @@ import ( "strconv" "strings" - swarmtypes "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/api/types/swarm" ) // ConfigOpt is a Value type for parsing configs type ConfigOpt struct { - values []*swarmtypes.ConfigReference + values []*swarm.ConfigReference } // Set a new config value @@ -24,8 +24,8 @@ func (o *ConfigOpt) Set(value string) error { return err } - options := &swarmtypes.ConfigReference{ - File: &swarmtypes.ConfigReferenceFileTarget{ + options := &swarm.ConfigReference{ + File: &swarm.ConfigReferenceFileTarget{ UID: "0", GID: "0", Mode: 0o444, @@ -95,6 +95,6 @@ func (o *ConfigOpt) String() string { } // Value returns the config requests -func (o *ConfigOpt) Value() []*swarmtypes.ConfigReference { +func (o *ConfigOpt) Value() []*swarm.ConfigReference { return o.values } diff --git a/vendor/github.com/docker/cli/opts/port.go b/vendor/github.com/docker/cli/opts/swarmopts/port.go similarity index 84% rename from vendor/github.com/docker/cli/opts/port.go rename to vendor/github.com/docker/cli/opts/swarmopts/port.go index 0407355e..e15c6b83 100644 --- a/vendor/github.com/docker/cli/opts/port.go +++ b/vendor/github.com/docker/cli/opts/swarmopts/port.go @@ -1,4 +1,4 @@ -package opts +package swarmopts import ( "encoding/csv" @@ -46,42 +46,50 @@ func (p *PortOpt) Set(value string) error { // TODO(thaJeztah): these options should not be case-insensitive. key, val, ok := strings.Cut(strings.ToLower(field), "=") if !ok || key == "" { - return fmt.Errorf("invalid field %s", field) + return fmt.Errorf("invalid field: %s", field) } switch key { case portOptProtocol: if val != string(swarm.PortConfigProtocolTCP) && val != string(swarm.PortConfigProtocolUDP) && val != string(swarm.PortConfigProtocolSCTP) { - return fmt.Errorf("invalid protocol value %s", val) + return fmt.Errorf("invalid protocol value '%s'", val) } pConfig.Protocol = swarm.PortConfigProtocol(val) case portOptMode: if val != string(swarm.PortConfigPublishModeIngress) && val != string(swarm.PortConfigPublishModeHost) { - return fmt.Errorf("invalid publish mode value %s", val) + return fmt.Errorf("invalid publish mode value (%s): must be either '%s' or '%s'", val, swarm.PortConfigPublishModeIngress, swarm.PortConfigPublishModeHost) } pConfig.PublishMode = swarm.PortConfigPublishMode(val) case portOptTargetPort: tPort, err := strconv.ParseUint(val, 10, 16) if err != nil { - return err + var numErr *strconv.NumError + if errors.As(err, &numErr) { + err = numErr.Err + } + return fmt.Errorf("invalid target port (%s): value must be an integer: %w", val, err) } pConfig.TargetPort = uint32(tPort) case portOptPublishedPort: pPort, err := strconv.ParseUint(val, 10, 16) if err != nil { - return err + var numErr *strconv.NumError + if errors.As(err, &numErr) { + err = numErr.Err + } + return fmt.Errorf("invalid published port (%s): value must be an integer: %w", val, err) } pConfig.PublishedPort = uint32(pPort) default: - return fmt.Errorf("invalid field key %s", key) + return fmt.Errorf("invalid field key: %s", key) } } if pConfig.TargetPort == 0 { - return fmt.Errorf("missing mandatory field %q", portOptTargetPort) + return fmt.Errorf("missing mandatory field '%s'", portOptTargetPort) } if pConfig.PublishMode == "" { diff --git a/vendor/github.com/docker/cli/opts/secret.go b/vendor/github.com/docker/cli/opts/swarmopts/secret.go similarity index 88% rename from vendor/github.com/docker/cli/opts/secret.go rename to vendor/github.com/docker/cli/opts/swarmopts/secret.go index bdf232de..9f97627a 100644 --- a/vendor/github.com/docker/cli/opts/secret.go +++ b/vendor/github.com/docker/cli/opts/swarmopts/secret.go @@ -1,4 +1,4 @@ -package opts +package swarmopts import ( "encoding/csv" @@ -8,12 +8,12 @@ import ( "strconv" "strings" - swarmtypes "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/api/types/swarm" ) // SecretOpt is a Value type for parsing secrets type SecretOpt struct { - values []*swarmtypes.SecretReference + values []*swarm.SecretReference } // Set a new secret value @@ -24,8 +24,8 @@ func (o *SecretOpt) Set(value string) error { return err } - options := &swarmtypes.SecretReference{ - File: &swarmtypes.SecretReferenceFileTarget{ + options := &swarm.SecretReference{ + File: &swarm.SecretReferenceFileTarget{ UID: "0", GID: "0", Mode: 0o444, @@ -94,6 +94,6 @@ func (o *SecretOpt) String() string { } // Value returns the secret requests -func (o *SecretOpt) Value() []*swarmtypes.SecretReference { +func (o *SecretOpt) Value() []*swarm.SecretReference { return o.values } diff --git a/vendor/modules.txt b/vendor/modules.txt index a14133be..82b76616 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -229,11 +229,12 @@ github.com/davecgh/go-spew/spew # github.com/distribution/reference v0.6.0 ## explicit; go 1.20 github.com/distribution/reference -# github.com/docker/cli v28.0.1+incompatible +# github.com/docker/cli v28.0.2+incompatible ## explicit 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/plugin github.com/docker/cli/cli-plugins/socket github.com/docker/cli/cli/command @@ -252,6 +253,7 @@ github.com/docker/cli/cli/context/store github.com/docker/cli/cli/debug github.com/docker/cli/cli/flags github.com/docker/cli/cli/hints +github.com/docker/cli/cli/internal/jsonstream github.com/docker/cli/cli/manifest/store github.com/docker/cli/cli/manifest/types github.com/docker/cli/cli/registry/client @@ -260,6 +262,7 @@ github.com/docker/cli/cli/trust github.com/docker/cli/cli/version github.com/docker/cli/internal/tui github.com/docker/cli/opts +github.com/docker/cli/opts/swarmopts github.com/docker/cli/pkg/kvfile github.com/docker/cli/templates # github.com/docker/cli-docs-tool v0.9.0