2016-04-23 21:31:57 -04:00
package main
import (
2024-03-28 12:23:01 -04:00
"context"
2016-04-23 21:31:57 -04:00
"fmt"
2024-06-18 09:42:34 -04:00
"io"
2016-04-23 21:31:57 -04:00
"os"
2019-02-22 12:49:44 -05:00
"os/exec"
2023-10-12 15:20:16 -04:00
"os/signal"
2017-02-10 02:35:05 -05:00
"strings"
2019-02-22 12:49:44 -05:00
"syscall"
2016-04-23 21:31:57 -04:00
2017-04-17 18:07:56 -04:00
"github.com/docker/cli/cli"
2018-12-11 09:03:47 -05:00
pluginmanager "github.com/docker/cli/cli-plugins/manager"
2024-01-12 13:17:03 -05:00
"github.com/docker/cli/cli-plugins/socket"
2017-04-17 18:07:56 -04:00
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/commands"
2024-04-01 10:23:41 -04:00
"github.com/docker/cli/cli/debug"
2017-04-17 18:07:56 -04:00
cliflags "github.com/docker/cli/cli/flags"
2019-01-08 10:03:51 -05:00
"github.com/docker/cli/cli/version"
2023-10-12 15:20:16 -04:00
platformsignals "github.com/docker/cli/cmd/docker/internal/signals"
2016-11-02 20:43:32 -04:00
"github.com/docker/docker/api/types/versions"
2024-03-12 07:38:47 -04:00
"github.com/docker/docker/errdefs"
2019-04-17 18:09:29 -04:00
"github.com/pkg/errors"
2017-08-07 05:52:40 -04:00
"github.com/sirupsen/logrus"
2016-06-22 13:08:04 -04:00
"github.com/spf13/cobra"
"github.com/spf13/pflag"
2024-03-28 12:23:01 -04:00
"go.opentelemetry.io/otel"
2016-04-23 21:31:57 -04:00
)
2024-01-11 12:21:49 -05:00
func main ( ) {
2024-04-08 04:11:09 -04:00
statusCode := dockerMain ( )
if statusCode != 0 {
os . Exit ( statusCode )
}
}
func dockerMain ( ) int {
ctx , cancelNotify := signal . NotifyContext ( context . Background ( ) , platformsignals . TerminationSignals ... )
defer cancelNotify ( )
2024-01-11 12:25:23 -05:00
2024-03-28 12:23:01 -04:00
dockerCli , err := command . NewDockerCli ( command . WithBaseContext ( ctx ) )
2024-01-11 12:21:49 -05:00
if err != nil {
fmt . Fprintln ( os . Stderr , err )
2024-04-08 04:11:09 -04:00
return 1
2024-01-11 12:21:49 -05:00
}
logrus . SetOutput ( dockerCli . Err ( ) )
2024-04-01 10:23:41 -04:00
otel . SetErrorHandler ( debug . OTELErrorHandler )
2024-01-11 12:21:49 -05:00
2024-03-28 12:23:01 -04:00
if err := runDocker ( ctx , dockerCli ) ; err != nil {
2024-01-11 12:21:49 -05:00
if sterr , ok := err . ( cli . StatusError ) ; ok {
if sterr . Status != "" {
fmt . Fprintln ( dockerCli . Err ( ) , sterr . Status )
}
// StatusError should only be used for errors, and all errors should
// have a non-zero exit status, so never exit with 0
if sterr . StatusCode == 0 {
2024-04-08 04:11:09 -04:00
return 1
2024-01-11 12:21:49 -05:00
}
2024-04-08 04:11:09 -04:00
return sterr . StatusCode
2024-01-11 12:21:49 -05:00
}
2024-03-12 07:38:47 -04:00
if errdefs . IsCancelled ( err ) {
2024-04-08 04:11:09 -04:00
return 0
2024-03-12 07:38:47 -04:00
}
2024-01-11 12:21:49 -05:00
fmt . Fprintln ( dockerCli . Err ( ) , err )
2024-04-08 04:11:09 -04:00
return 1
2024-01-11 12:21:49 -05:00
}
2024-04-08 04:11:09 -04:00
return 0
2024-01-11 12:21:49 -05:00
}
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
func newDockerCommand ( dockerCli * command . DockerCli ) * cli . TopLevelCommand {
2018-12-18 05:16:52 -05:00
var (
2018-12-11 09:50:04 -05:00
opts * cliflags . ClientOptions
helpCmd * cobra . Command
2018-12-18 05:16:52 -05:00
)
2016-08-03 12:20:46 -04:00
2016-06-22 13:08:04 -04:00
cmd := & cobra . Command {
2016-11-04 22:45:15 -04:00
Use : "docker [OPTIONS] COMMAND [ARG...]" ,
2016-10-31 23:07:31 -04:00
Short : "A self-sufficient runtime for containers" ,
2016-06-22 18:36:51 -04:00
SilenceUsage : true ,
SilenceErrors : true ,
TraverseChildren : true ,
2018-10-10 05:16:27 -04:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2018-12-11 09:03:47 -05:00
if len ( args ) == 0 {
return command . ShowHelp ( dockerCli . Err ( ) ) ( cmd , args )
}
cli: improve output and consistency for unknown (sub)commands
Before this patch, output for invalid top-level and sub-commands differed.
For top-level commands, the CLI would print an error-message and a suggestion
to use `--help`. For missing *subcommands*, we would hit a different code-path,
and different output, which includes full "usage" / "help" output.
While it is a common convention to show usage output, and may have been
a nice gesture when docker was still young and only had a few commands
and options ("you did something wrong; here's an overview of what you
can use"), that's no longer the case, and many commands have a _very_
long output.
The result of this is that the error message, which is the relevant
information in this case - "You mis-typed something" - is lost in the
output, and hard to find (sometimes even requiring scrolling back).
The output is also confusing, because it _looks_ like something ran
successfully (most of the output is not about the error!).
Even further; the suggested resolution (try `--help` to see the correct
options) is rather redundant, because running teh command with `--help`
produces _exactly_ the same output as was just showh, baring the error
message. As a fun fact, due to the usage output being printed, the
output even contains not one, but _two_ "call to actions";
- `See 'docker volume --help'.` (under the erro message)
- `Run 'docker volume COMMAND --help' for more information on a command.`
(under the usage output)
In short; the output is too verbose, confusing, and doesn't provide
a good UX. Let's reduce the output produced so that the focus is on the
important information.
This patch:
- Changes the usage to the short-usage.
- Changes the error-message to mention the _full_ command instead of only
the command after `docker` (so `docker no-such-command` instead of
`no-such-command`).
- Prefixes the error message with the binary / root-command name
(usually `docker:`); this is something we can still decide on, but
it's a pattern we already use in some places. The motivation for this
is that `docker` commands can often produce output that's a combination
of output from the CLI itself, output from the daemon, and even output
from the container. The `docker:` prefix helps to distinguish where
the message originated from (the `docker` CLI in this case).
- Adds an empty line between the error-message and the "call to action"
(`Run 'docker volume --help'...` in the example below). This helps
separating the error message ("unkown flag") from the call-to-action.
Before this patch:
Unknown top-level command:
docker nosuchcommand foo
docker: 'nosuchcommand' is not a docker command.
See 'docker --help'
Unknown sub-command:
docker volume nosuchcommand foo
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove unused local volumes
rm Remove one or more volumes
update Update a volume (cluster volumes only)
Run 'docker volume COMMAND --help' for more information on a command.
After this patch:
Unknown top-level command:
docker nosuchcommand foo
docker: unknown command: docker nosuchcommand
Run 'docker --help' for more information
Unknown sub-command:
docker volume nosuchcommand foo
docker: unknown command: 'docker volume nosuchcommand'
Usage: docker volume COMMAND
Run 'docker volume --help' for more information
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-04 18:57:07 -04:00
return fmt . Errorf ( "docker: unknown command: docker %s\n\nRun 'docker --help' for more information" , args [ 0 ] )
2018-10-10 05:16:27 -04:00
} ,
2016-06-22 13:08:04 -04:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2017-03-14 17:53:29 -04:00
return isSupported ( cmd , dockerCli )
2016-06-22 13:08:04 -04:00
} ,
2019-01-08 10:03:51 -05:00
Version : fmt . Sprintf ( "%s, build %s" , version . Version , version . GitCommit ) ,
2018-05-18 20:46:27 -04:00
DisableFlagsInUseLine : true ,
2022-03-30 09:27:25 -04:00
CompletionOptions : cobra . CompletionOptions {
DisableDefaultCmd : false ,
HiddenDefaultCmd : true ,
DisableDescriptions : true ,
} ,
2016-06-22 13:08:04 -04:00
}
2022-04-01 06:24:44 -04:00
cmd . SetIn ( dockerCli . In ( ) )
cmd . SetOut ( dockerCli . Out ( ) )
cmd . SetErr ( dockerCli . Err ( ) )
2023-06-28 10:06:19 -04:00
opts , helpCmd = cli . SetupRootCommand ( cmd )
2024-01-11 12:17:47 -05:00
_ = registerCompletionFuncForGlobalFlags ( dockerCli . ContextStore ( ) , cmd )
2023-06-28 10:06:19 -04:00
cmd . Flags ( ) . BoolP ( "version" , "v" , false , "Print version information and quit" )
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
setFlagErrorFunc ( dockerCli , cmd )
2016-11-21 17:34:55 -05:00
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
setupHelpCommand ( dockerCli , cmd , helpCmd )
setHelpFunc ( dockerCli , cmd )
2016-11-21 17:34:55 -05:00
2020-05-07 08:25:59 -04:00
cmd . SetOut ( dockerCli . Out ( ) )
2016-11-21 17:34:55 -05:00
commands . AddCommands ( cmd , dockerCli )
2018-12-17 11:59:11 -05:00
cli . DisableFlagsInUseLine ( cmd )
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
setValidateArgs ( dockerCli , cmd )
2016-11-21 17:34:55 -05:00
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
// flags must be the top-level command flags, not cmd.Flags()
2023-06-28 10:06:19 -04:00
return cli . NewTopLevelCommand ( cmd , dockerCli , opts , cmd . Flags ( ) )
2016-11-21 17:34:55 -05:00
}
2019-04-26 10:43:03 -04:00
func setFlagErrorFunc ( dockerCli command . Cli , cmd * cobra . Command ) {
2016-11-20 12:57:06 -05:00
// When invoking `docker stack --nonsense`, we need to make sure FlagErrorFunc return appropriate
// output if the feature is not supported.
// As above cli.SetupRootCommand(cmd) have already setup the FlagErrorFunc, we will add a pre-check before the FlagErrorFunc
// is called.
flagErrorFunc := cmd . FlagErrorFunc ( )
cmd . SetFlagErrorFunc ( func ( cmd * cobra . Command , err error ) error {
2019-04-26 10:08:22 -04:00
if err := pluginmanager . AddPluginCommandStubs ( dockerCli , cmd . Root ( ) ) ; err != nil {
return err
}
2017-03-14 17:53:29 -04:00
if err := isSupported ( cmd , dockerCli ) ; err != nil {
2016-11-20 12:57:06 -05:00
return err
}
2019-04-26 10:09:09 -04:00
if err := hideUnsupportedFeatures ( cmd , dockerCli ) ; err != nil {
return err
}
2016-11-20 12:57:06 -05:00
return flagErrorFunc ( cmd , err )
} )
2016-11-21 17:34:55 -05:00
}
2016-06-22 13:08:04 -04:00
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
func setupHelpCommand ( dockerCli command . Cli , rootCmd , helpCmd * cobra . Command ) {
2018-12-11 09:50:04 -05:00
origRun := helpCmd . Run
origRunE := helpCmd . RunE
helpCmd . Run = nil
helpCmd . RunE = func ( c * cobra . Command , args [ ] string ) error {
2018-12-11 09:52:59 -05:00
if len ( args ) > 0 {
helpcmd , err := pluginmanager . PluginRunCommand ( dockerCli , args [ 0 ] , rootCmd )
if err == nil {
2022-03-30 09:27:25 -04:00
return helpcmd . Run ( )
2018-12-11 09:52:59 -05:00
}
if ! pluginmanager . IsNotFound ( err ) {
2022-03-30 09:27:25 -04:00
return errors . Errorf ( "unknown help topic: %v" , strings . Join ( args , " " ) )
2018-12-11 09:52:59 -05:00
}
}
2018-12-11 09:50:04 -05:00
if origRunE != nil {
return origRunE ( c , args )
}
origRun ( c , args )
return nil
}
}
2018-12-17 11:23:55 -05:00
func tryRunPluginHelp ( dockerCli command . Cli , ccmd * cobra . Command , cargs [ ] string ) error {
root := ccmd . Root ( )
cmd , _ , err := root . Traverse ( cargs )
if err != nil {
return err
}
helpcmd , err := pluginmanager . PluginRunCommand ( dockerCli , cmd . Name ( ) , root )
if err != nil {
return err
}
return helpcmd . Run ( )
}
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
func setHelpFunc ( dockerCli command . Cli , cmd * cobra . Command ) {
2017-10-25 12:59:32 -04:00
defaultHelpFunc := cmd . HelpFunc ( )
2016-11-02 20:43:32 -04:00
cmd . SetHelpFunc ( func ( ccmd * cobra . Command , args [ ] string ) {
2023-03-27 22:08:07 -04:00
if err := pluginmanager . AddPluginCommandStubs ( dockerCli , ccmd . Root ( ) ) ; err != nil {
ccmd . Println ( err )
return
}
if len ( args ) >= 1 {
2018-12-17 11:23:55 -05:00
err := tryRunPluginHelp ( dockerCli , ccmd , args )
2023-03-27 22:08:07 -04:00
if err == nil {
return
}
2018-12-17 11:23:55 -05:00
if ! pluginmanager . IsNotFound ( err ) {
ccmd . Println ( err )
2023-03-27 22:08:07 -04:00
return
2018-12-17 11:23:55 -05:00
}
}
2017-03-14 17:53:29 -04:00
if err := isSupported ( ccmd , dockerCli ) ; err != nil {
2016-11-16 19:38:28 -05:00
ccmd . Println ( err )
return
}
2018-10-09 22:13:01 -04:00
if err := hideUnsupportedFeatures ( ccmd , dockerCli ) ; err != nil {
ccmd . Println ( err )
return
}
2019-01-22 08:44:39 -05:00
2017-10-25 12:59:32 -04:00
defaultHelpFunc ( ccmd , args )
2016-11-02 20:43:32 -04:00
} )
2016-11-21 17:34:55 -05:00
}
2016-11-02 20:43:32 -04:00
2022-03-30 09:27:25 -04:00
func setValidateArgs ( dockerCli command . Cli , cmd * cobra . Command ) {
2016-11-21 17:34:55 -05:00
// The Args is handled by ValidateArgs in cobra, which does not allows a pre-hook.
// As a result, here we replace the existing Args validation func to a wrapper,
// where the wrapper will check to see if the feature is supported or not.
// The Args validation error will only be returned if the feature is supported.
2018-12-17 11:59:11 -05:00
cli . VisitAll ( cmd , func ( ccmd * cobra . Command ) {
2016-11-21 17:34:55 -05:00
// if there is no tags for a command or any of its parent,
// there is no need to wrap the Args validation.
if ! hasTags ( ccmd ) {
return
}
2016-06-22 13:08:04 -04:00
2016-11-21 17:34:55 -05:00
if ccmd . Args == nil {
return
}
2016-06-22 18:36:51 -04:00
2016-11-21 17:34:55 -05:00
cmdArgs := ccmd . Args
ccmd . Args = func ( cmd * cobra . Command , args [ ] string ) error {
2017-03-14 17:53:29 -04:00
if err := isSupported ( cmd , dockerCli ) ; err != nil {
2016-11-21 17:34:55 -05:00
return err
}
return cmdArgs ( cmd , args )
}
} )
}
2024-04-08 04:11:09 -04:00
func tryPluginRun ( ctx context . Context , dockerCli command . Cli , cmd * cobra . Command , subcommand string , envs [ ] string ) error {
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
plugincmd , err := pluginmanager . PluginRunCommand ( dockerCli , subcommand , cmd )
if err != nil {
return err
2016-11-21 17:34:55 -05:00
}
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
2024-03-22 00:34:39 -04:00
// Establish the plugin socket, adding it to the environment under a
// well-known key if successful.
2024-02-29 16:33:03 -05:00
srv , err := socket . NewPluginServer ( nil )
2023-10-12 15:20:16 -04:00
if err == nil {
2024-03-22 00:34:39 -04:00
plugincmd . Env = append ( plugincmd . Env , socket . EnvKey + "=" + srv . Addr ( ) . String ( ) )
2023-10-12 15:20:16 -04:00
}
2024-06-11 13:01:56 -04:00
defer func ( ) {
// Close the server when plugin execution is over, so that in case
// it's still open, any sockets on the filesystem are cleaned up.
_ = srv . Close ( )
} ( )
2023-10-12 15:20:16 -04:00
2024-03-22 00:34:39 -04:00
// Set additional environment variables specified by the caller.
plugincmd . Env = append ( plugincmd . Env , envs ... )
2024-01-12 13:17:03 -05:00
2024-03-22 00:34:39 -04:00
// Background signal handling logic: block on the signals channel, and
// notify the plugin via the PluginServer (or signal) as appropriate.
2024-04-08 04:11:09 -04:00
const exitLimit = 2
tryTerminatePlugin := func ( force bool ) {
// If stdin is a TTY, the kernel will forward
// signals to the subprocess because the shared
// pgid makes the TTY a controlling terminal.
//
// The plugin should have it's own copy of this
// termination logic, and exit after 3 retries
// on it's own.
if dockerCli . Out ( ) . IsTerminal ( ) {
return
}
// Terminate the plugin server, which will
// close all connections with plugin
// subprocesses, and signal them to exit.
//
// Repeated invocations will result in EINVAL,
// or EBADF; but that is fine for our purposes.
_ = srv . Close ( )
// force the process to terminate if it hasn't already
if force {
_ = plugincmd . Process . Kill ( )
_ , _ = fmt . Fprint ( dockerCli . Err ( ) , "got 3 SIGTERM/SIGINTs, forcefully exiting\n" )
os . Exit ( 1 )
}
}
2020-10-17 01:40:02 -04:00
go func ( ) {
2023-10-12 15:20:16 -04:00
retries := 0
2024-04-08 04:11:09 -04:00
force := false
// catch the first signal through context cancellation
<- ctx . Done ( )
tryTerminatePlugin ( force )
2024-02-29 16:33:03 -05:00
2024-04-08 04:11:09 -04:00
// register subsequent signals
signals := make ( chan os . Signal , exitLimit )
signal . Notify ( signals , platformsignals . TerminationSignals ... )
2024-02-29 16:33:03 -05:00
2024-04-08 04:11:09 -04:00
for range signals {
retries ++
2024-03-22 00:34:39 -04:00
// If we're still running after 3 interruptions
// (SIGINT/SIGTERM), send a SIGKILL to the plugin as a
// final attempt to terminate, and exit.
2023-10-12 15:20:16 -04:00
if retries >= exitLimit {
2024-04-08 04:11:09 -04:00
force = true
2023-10-12 15:20:16 -04:00
}
2024-04-08 04:11:09 -04:00
tryTerminatePlugin ( force )
2023-10-12 15:20:16 -04:00
}
2020-10-17 01:40:02 -04:00
} ( )
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
if err := plugincmd . Run ( ) ; err != nil {
statusCode := 1
exitErr , ok := err . ( * exec . ExitError )
if ! ok {
return err
}
if ws , ok := exitErr . Sys ( ) . ( syscall . WaitStatus ) ; ok {
statusCode = ws . ExitStatus ( )
}
return cli . StatusError {
StatusCode : statusCode ,
}
}
return nil
}
2024-06-20 08:32:38 -04:00
// forceExitAfter3TerminationSignals waits for the first termination signal
// to be caught and the context to be marked as done, then registers a new
// signal handler for subsequent signals. It forces the process to exit
// after 3 SIGTERM/SIGINT signals.
func forceExitAfter3TerminationSignals ( ctx context . Context , w io . Writer ) {
// wait for the first signal to be caught and the context to be marked as done
<- ctx . Done ( )
// register a new signal handler for subsequent signals
sig := make ( chan os . Signal , 2 )
signal . Notify ( sig , platformsignals . TerminationSignals ... )
// once we have received a total of 3 signals we force exit the cli
for i := 0 ; i < 2 ; i ++ {
<- sig
}
_ , _ = fmt . Fprint ( w , "\ngot 3 SIGTERM/SIGINTs, forcefully exiting\n" )
os . Exit ( 1 )
2024-06-18 09:42:34 -04:00
}
2023-07-20 11:25:36 -04:00
//nolint:gocyclo
2024-03-28 12:23:01 -04:00
func runDocker ( ctx context . Context , dockerCli * command . DockerCli ) error {
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
tcmd := newDockerCommand ( dockerCli )
cmd , args , err := tcmd . HandleGlobalFlags ( )
if err != nil {
return err
}
2024-06-24 08:49:29 -04:00
if err := tcmd . Initialize ( command . WithEnableGlobalMeterProvider ( ) , command . WithEnableGlobalTracerProvider ( ) ) ; err != nil {
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
return err
}
2024-05-14 11:23:49 -04:00
mp := dockerCli . MeterProvider ( )
if mp , ok := mp . ( command . MeterProvider ) ; ok {
defer mp . Shutdown ( ctx )
} else {
fmt . Fprint ( dockerCli . Err ( ) , "Warning: Unexpected OTEL error, metrics may not be flushed" )
}
2024-05-01 22:21:59 -04:00
dockerCli . InstrumentCobraCommands ( ctx , cmd )
2024-03-28 12:23:01 -04:00
2022-11-03 17:30:27 -04:00
var envs [ ] string
args , os . Args , envs , err = processAliases ( dockerCli , cmd , args , os . Args )
2022-03-30 09:27:25 -04:00
if err != nil {
return err
}
2023-03-27 22:08:07 -04:00
if cli . HasCompletionArg ( args ) {
// We add plugin command stubs early only for completion. We don't
// want to add them for normal command execution as it would cause
// a significant performance hit.
err = pluginmanager . AddPluginCommandStubs ( dockerCli , cmd )
if err != nil {
return err
}
2019-04-17 18:09:29 -04:00
}
2023-07-20 11:25:36 -04:00
var subCommand * cobra . Command
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
if len ( args ) > 0 {
2022-09-29 16:43:47 -04:00
ccmd , _ , err := cmd . Find ( args )
2023-07-20 11:25:36 -04:00
subCommand = ccmd
2022-09-29 16:43:47 -04:00
if err != nil || pluginmanager . IsPluginCommand ( ccmd ) {
2024-04-08 04:11:09 -04:00
err := tryPluginRun ( ctx , dockerCli , cmd , args [ 0 ] , envs )
2023-07-20 11:25:36 -04:00
if err == nil {
if dockerCli . HooksEnabled ( ) && dockerCli . Out ( ) . IsTerminal ( ) && ccmd != nil {
2024-04-26 07:03:56 -04:00
pluginmanager . RunPluginHooks ( ctx , dockerCli , cmd , ccmd , args )
2023-07-20 11:25:36 -04:00
}
return nil
}
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
if ! pluginmanager . IsNotFound ( err ) {
2023-07-20 11:25:36 -04:00
// For plugin not found we fall through to
// cmd.Execute() which deals with reporting
// "command not found" in a consistent way.
allow plugins to have argument which match a top-level flag.
The issue with plugin options clashing with globals is that when cobra is
parsing the command line and it comes across an argument which doesn't start
with a `-` it (in the absence of plugins) distinguishes between "argument to
current command" and "new subcommand" based on the list of registered sub
commands.
Plugins breaks that model. When presented with `docker -D plugin -c foo` cobra
parses up to the `plugin`, sees it isn't a registered sub-command of the
top-level docker (because it isn't, it's a plugin) so it accumulates it as an
argument to the top-level `docker` command. Then it sees the `-c`, and thinks
it is the global `-c` (for AKA `--context`) option and tries to treat it as
that, which fails.
In the specific case of the top-level `docker` subcommand we know that it has
no arguments which aren't `--flags` (or `-f` short flags) and so anything which
doesn't start with a `-` must either be a (known) subcommand or an attempt to
execute a plugin.
We could simply scan for and register all installed plugins at start of day, so
that cobra can do the right thing, but we want to avoid that since it would
involve executing each plugin to fetch the metadata, even if the command wasn't
going to end up hitting a plugin.
Instead we can parse the initial set of global arguments separately before
hitting the main cobra `Execute` path, which works here exactly because we know
that the top-level has no non-flag arguments.
One slight wrinkle is that the top-level `PersistentPreRunE` is no longer
called on the plugins path (since it no longer goes via `Execute`), so we
arrange for the initialisation done there (which has to be done after global
flags are parsed to handle e.g. `--config`) to happen explictly after the
global flags are parsed. Rather than make `newDockerCommand` return the
complicated set of results needed to make this happen, instead return a closure
which achieves this.
The new functionality is introduced via a common `TopLevelCommand` abstraction
which lets us adjust the plugin entrypoint to use the same strategy for parsing
the global arguments. This isn't strictly required (in this case the stuff in
cobra's `Execute` works fine) but doing it this way avoids the possibility of
subtle differences in behaviour.
Fixes #1699, and also, as a side-effect, the first item in #1661.
Signed-off-by: Ian Campbell <ijc@docker.com>
2019-03-06 05:29:42 -05:00
return err
}
}
}
2024-06-18 09:42:34 -04:00
// This is a fallback for the case where the command does not exit
// based on context cancellation.
2024-06-20 08:32:38 -04:00
go forceExitAfter3TerminationSignals ( ctx , dockerCli . Err ( ) )
2024-06-18 09:42:34 -04:00
2019-04-03 06:53:31 -04:00
// We've parsed global args already, so reset args to those
// which remain.
cmd . SetArgs ( args )
2024-01-11 12:25:23 -05:00
err = cmd . ExecuteContext ( ctx )
2023-07-20 11:25:36 -04:00
hooks: include full configured command
Before, for plugin commands, only the plugin name (such as `buildx`)
would be both included as `RootCmd` when passed to the hook plugin,
which isn't enough information for a plugin to decide whether to execute
a hook or not since plugins implement multiple varied commands (`buildx
build`, `buildx prune`, etc.).
This commit changes the hook logic to account for this situation, so
that the the entire configured hook is passed, i.e., if a user has a
hook configured for `buildx imagetools inspect` and the command
`docker buildx imagetools inspect alpine` is called, then the plugin
hooks will be passed `buildx imagetools inspect`.
This logic works for aliased commands too, so whether `docker build ...`
or `docker buildx build` is executed (unless Buildx is disabled) the
hook will be invoked with `buildx build`.
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
hooks: include full match when invoking plugins
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-04-19 07:32:18 -04:00
// If the command is being executed in an interactive terminal
// and hook are enabled, run the plugin hooks.
2023-07-20 11:25:36 -04:00
if dockerCli . HooksEnabled ( ) && dockerCli . Out ( ) . IsTerminal ( ) && subCommand != nil {
2024-04-22 12:12:53 -04:00
var errMessage string
if err != nil {
errMessage = err . Error ( )
}
2024-04-26 07:03:56 -04:00
pluginmanager . RunCLICommandHooks ( ctx , dockerCli , cmd , subCommand , errMessage )
2023-07-20 11:25:36 -04:00
}
2024-04-17 10:21:08 -04:00
return err
2016-11-21 17:34:55 -05:00
}
2017-03-14 17:53:29 -04:00
type versionDetails interface {
2022-11-04 08:46:36 -04:00
CurrentVersion ( ) string
2017-03-14 17:53:29 -04:00
ServerInfo ( ) command . ServerInfo
2017-12-26 09:40:17 -05:00
}
2020-04-07 18:57:41 -04:00
func hideFlagIf ( f * pflag . Flag , condition func ( string ) bool , annotation string ) {
if f . Hidden {
2017-12-26 09:40:17 -05:00
return
}
2020-09-22 08:47:55 -04:00
var val string
if values , ok := f . Annotations [ annotation ] ; ok {
if len ( values ) > 0 {
val = values [ 0 ]
}
if condition ( val ) {
2020-04-07 18:57:41 -04:00
f . Hidden = true
}
2017-12-26 09:40:17 -05:00
}
}
2020-04-07 18:57:41 -04:00
func hideSubcommandIf ( subcmd * cobra . Command , condition func ( string ) bool , annotation string ) {
if subcmd . Hidden {
2017-12-26 09:40:17 -05:00
return
}
2020-04-07 18:57:41 -04:00
if v , ok := subcmd . Annotations [ annotation ] ; ok {
if condition ( v ) {
subcmd . Hidden = true
}
2017-12-26 09:40:17 -05:00
}
2017-03-14 17:53:29 -04:00
}
2018-10-09 22:13:01 -04:00
func hideUnsupportedFeatures ( cmd * cobra . Command , details versionDetails ) error {
2020-04-07 18:57:41 -04:00
var (
2022-03-29 05:04:50 -04:00
notExperimental = func ( _ string ) bool { return ! details . ServerInfo ( ) . HasExperimental }
2022-11-04 01:23:01 -04:00
notOSType = func ( v string ) bool { return details . ServerInfo ( ) . OSType != "" && v != details . ServerInfo ( ) . OSType }
2022-03-29 05:04:50 -04:00
notSwarmStatus = func ( v string ) bool {
s := details . ServerInfo ( ) . SwarmStatus
if s == nil {
// engine did not return swarm status header
return false
}
switch v {
case "manager" :
// requires the node to be a manager
return ! s . ControlAvailable
case "active" :
// requires swarm to be active on the node (e.g. for swarm leave)
// only hide the command if we're sure the node is "inactive"
// for any other status, assume the "leave" command can still
// be used.
return s . NodeState == "inactive"
case "" :
// some swarm commands, such as "swarm init" and "swarm join"
// are swarm-related, but do not require swarm to be active
return false
default :
// ignore any other value for the "swarm" annotation
return false
}
}
2022-11-04 08:46:36 -04:00
versionOlderThan = func ( v string ) bool { return versions . LessThan ( details . CurrentVersion ( ) , v ) }
2020-04-07 18:57:41 -04:00
)
2017-03-14 17:53:29 -04:00
2016-11-02 20:43:32 -04:00
cmd . Flags ( ) . VisitAll ( func ( f * pflag . Flag ) {
2016-11-02 20:43:32 -04:00
// hide flags not supported by the server
2018-06-01 08:58:43 -04:00
// root command shows all top-level flags
if cmd . Parent ( ) != nil {
2020-04-07 18:57:41 -04:00
if cmds , ok := f . Annotations [ "top-level" ] ; ok {
f . Hidden = ! findCommand ( cmd , cmds )
}
if f . Hidden {
return
2018-06-01 08:58:43 -04:00
}
}
2020-04-07 18:57:41 -04:00
hideFlagIf ( f , notExperimental , "experimental" )
hideFlagIf ( f , notOSType , "ostype" )
2022-03-29 05:04:50 -04:00
hideFlagIf ( f , notSwarmStatus , "swarm" )
2020-04-07 18:57:41 -04:00
hideFlagIf ( f , versionOlderThan , "version" )
2016-11-02 20:43:32 -04:00
} )
for _ , subcmd := range cmd . Commands ( ) {
2020-04-07 18:57:41 -04:00
hideSubcommandIf ( subcmd , notExperimental , "experimental" )
hideSubcommandIf ( subcmd , notOSType , "ostype" )
2022-03-29 05:04:50 -04:00
hideSubcommandIf ( subcmd , notSwarmStatus , "swarm" )
2020-04-07 18:57:41 -04:00
hideSubcommandIf ( subcmd , versionOlderThan , "version" )
2016-11-02 20:43:32 -04:00
}
2018-10-09 22:13:01 -04:00
return nil
2016-11-02 20:43:32 -04:00
}
2016-11-16 19:38:28 -05:00
2018-06-01 08:58:43 -04:00
// Checks if a command or one of its ancestors is in the list
2023-11-20 11:38:50 -05:00
func findCommand ( cmd * cobra . Command , cmds [ ] string ) bool {
2018-06-01 08:58:43 -04:00
if cmd == nil {
return false
}
2023-11-20 11:38:50 -05:00
for _ , c := range cmds {
2018-06-01 08:58:43 -04:00
if c == cmd . Name ( ) {
return true
}
}
2023-11-20 11:38:50 -05:00
return findCommand ( cmd . Parent ( ) , cmds )
2018-06-01 08:58:43 -04:00
}
2017-03-14 17:53:29 -04:00
func isSupported ( cmd * cobra . Command , details versionDetails ) error {
2017-12-04 06:30:39 -05:00
if err := areSubcommandsSupported ( cmd , details ) ; err != nil {
return err
}
2018-01-13 07:16:34 -05:00
return areFlagsSupported ( cmd , details )
2017-12-04 06:30:39 -05:00
}
func areFlagsSupported ( cmd * cobra . Command , details versionDetails ) error {
2017-01-16 09:35:27 -05:00
errs := [ ] string { }
cmd . Flags ( ) . VisitAll ( func ( f * pflag . Flag ) {
2023-08-22 03:34:09 -04:00
if ! f . Changed || len ( f . Annotations ) == 0 {
2020-04-07 18:57:41 -04:00
return
}
2023-08-22 03:34:09 -04:00
// Important: in the code below, calls to "details.CurrentVersion()" and
// "details.ServerInfo()" are deliberately executed inline to make them
// be executed "lazily". This is to prevent making a connection with the
// daemon to perform a "ping" (even for flags that do not require a
// daemon connection).
//
// See commit b39739123b845f872549e91be184cc583f5b387c for details.
if _ , ok := f . Annotations [ "version" ] ; ok && ! isVersionSupported ( f , details . CurrentVersion ( ) ) {
2022-11-04 08:46:36 -04:00
errs = append ( errs , fmt . Sprintf ( ` "--%s" requires API version %s, but the Docker daemon API version is %s ` , f . Name , getFlagAnnotation ( f , "version" ) , details . CurrentVersion ( ) ) )
2020-04-07 18:57:41 -04:00
return
}
2023-08-22 03:34:09 -04:00
if _ , ok := f . Annotations [ "ostype" ] ; ok && ! isOSTypeSupported ( f , details . ServerInfo ( ) . OSType ) {
2020-04-07 18:57:41 -04:00
errs = append ( errs , fmt . Sprintf (
` "--%s" is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s ` ,
f . Name ,
getFlagAnnotation ( f , "ostype" ) , details . ServerInfo ( ) . OSType ) ,
)
return
}
if _ , ok := f . Annotations [ "experimental" ] ; ok && ! details . ServerInfo ( ) . HasExperimental {
errs = append ( errs , fmt . Sprintf ( ` "--%s" is only supported on a Docker daemon with experimental features enabled ` , f . Name ) )
2017-01-16 09:35:27 -05:00
}
2020-04-07 18:57:41 -04:00
// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
2017-01-16 09:35:27 -05:00
} )
if len ( errs ) > 0 {
return errors . New ( strings . Join ( errs , "\n" ) )
2016-11-16 19:38:28 -05:00
}
2017-12-04 06:30:39 -05:00
return nil
}
2016-11-16 19:38:28 -05:00
2017-12-04 06:30:39 -05:00
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
func areSubcommandsSupported ( cmd * cobra . Command , details versionDetails ) error {
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
for curr := cmd ; curr != nil ; curr = curr . Parent ( ) {
2022-12-06 04:09:34 -05:00
// Important: in the code below, calls to "details.CurrentVersion()" and
// "details.ServerInfo()" are deliberately executed inline to make them
// be executed "lazily". This is to prevent making a connection with the
// daemon to perform a "ping" (even for commands that do not require a
// daemon connection).
//
// See commit b39739123b845f872549e91be184cc583f5b387c for details.
2022-11-04 08:46:36 -04:00
if cmdVersion , ok := curr . Annotations [ "version" ] ; ok && versions . LessThan ( details . CurrentVersion ( ) , cmdVersion ) {
return fmt . Errorf ( "%s requires API version %s, but the Docker daemon API version is %s" , cmd . CommandPath ( ) , cmdVersion , details . CurrentVersion ( ) )
2017-12-04 06:30:39 -05:00
}
2022-12-06 04:09:34 -05:00
if ost , ok := curr . Annotations [ "ostype" ] ; ok && details . ServerInfo ( ) . OSType != "" && ost != details . ServerInfo ( ) . OSType {
return fmt . Errorf ( "%s is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s" , cmd . CommandPath ( ) , ost , details . ServerInfo ( ) . OSType )
2018-05-30 17:14:59 -04:00
}
2022-12-06 04:09:34 -05:00
if _ , ok := curr . Annotations [ "experimental" ] ; ok && ! details . ServerInfo ( ) . HasExperimental {
2017-12-04 06:30:39 -05:00
return fmt . Errorf ( "%s is only supported on a Docker daemon with experimental features enabled" , cmd . CommandPath ( ) )
}
}
2016-11-16 19:38:28 -05:00
return nil
}
2017-01-16 09:35:27 -05:00
2017-02-07 07:52:20 -05:00
func getFlagAnnotation ( f * pflag . Flag , annotation string ) string {
if value , ok := f . Annotations [ annotation ] ; ok && len ( value ) == 1 {
return value [ 0 ]
2017-01-16 09:35:27 -05:00
}
return ""
}
2017-02-07 07:52:20 -05:00
func isVersionSupported ( f * pflag . Flag , clientVersion string ) bool {
if v := getFlagAnnotation ( f , "version" ) ; v != "" {
2017-01-16 09:35:27 -05:00
return versions . GreaterThanOrEqualTo ( clientVersion , v )
}
return true
}
2016-11-21 17:34:55 -05:00
2017-02-07 07:52:20 -05:00
func isOSTypeSupported ( f * pflag . Flag , osType string ) bool {
if v := getFlagAnnotation ( f , "ostype" ) ; v != "" && osType != "" {
return osType == v
}
return true
}
2016-11-21 17:34:55 -05:00
// hasTags return true if any of the command's parents has tags
func hasTags ( cmd * cobra . Command ) bool {
for curr := cmd ; curr != nil ; curr = curr . Parent ( ) {
2017-10-25 12:59:32 -04:00
if len ( curr . Annotations ) > 0 {
2016-11-21 17:34:55 -05:00
return true
}
}
return false
}