2016-04-23 21:31:57 -04:00
package main
import (
"fmt"
2023-10-12 15:20:16 -04:00
"net"
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"
2023-10-12 15:20:16 -04:00
"github.com/docker/cli/cli-plugins/plugin"
2017-04-17 18:07:56 -04:00
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/commands"
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"
"github.com/docker/distribution/uuid"
2016-11-02 20:43:32 -04:00
"github.com/docker/docker/api/types/versions"
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"
2016-04-23 21:31:57 -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 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 )
}
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 fmt . Errorf ( "docker: '%s' is not a docker command.\nSee 'docker --help'" , 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 )
2022-03-30 09:27:25 -04:00
registerCompletionFuncForGlobalFlags ( dockerCli , 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 )
}
} )
}
2023-10-12 15:20:16 -04:00
func setupPluginSocket ( ) ( * net . UnixListener , error ) {
return net . ListenUnix ( "unix" , & net . UnixAddr {
Name : "@docker_cli_" + uuid . Generate ( ) . String ( ) ,
Net : "unix" ,
} )
}
2022-11-03 17:30:27 -04:00
func tryPluginRun ( 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
}
2022-11-03 17:30:27 -04:00
plugincmd . Env = append ( envs , plugincmd . Env ... )
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
2023-10-12 15:20:16 -04:00
var conn * net . UnixConn
listener , err := setupPluginSocket ( )
if err == nil {
defer listener . Close ( )
plugincmd . Env = append ( plugincmd . Env , plugin . CLIPluginSocketEnvKey + "=" + listener . Addr ( ) . String ( ) )
go func ( ) {
for {
// ignore error here, if we failed to accept a connection,
// conn is nil and we fallback to previous behavior
conn , _ = listener . AcceptUnix ( )
}
} ( )
}
const exitLimit = 3
signals := make ( chan os . Signal , exitLimit )
signal . Notify ( signals , platformsignals . TerminationSignals ... )
// signal handling goroutine: listen on signals channel, and if conn is
// non-nil, attempt to close it to let the plugin know to exit. Regardless
// of whether we successfully signal the plugin or not, after 3 SIGINTs,
// we send a SIGKILL to the plugin process and exit
2020-10-17 01:40:02 -04:00
go func ( ) {
2023-10-12 15:20:16 -04:00
retries := 0
plugins: run plugin with new process group ID
Changes were made in 1554ac3b5f38147301c518c60da16f3f80c1717b to provide
a mechanism for the CLI to notify running plugin processes that they
should exit, in order to improve the general CLI/plugin UX. The current
implementation boils down to:
1. The CLI creates a socket
2. The CLI executes the plugin
3. The plugin connects to the socket
4. (When) the CLI receives a termination signal, it uses the socket to
notify the plugin that it should exit
5. The plugin's gets notified via the socket, and cancels it's `cmd.Context`,
which then gets handled appropriately
This change works in most cases and fixes the issue it sets out to solve
(see: https://github.com/docker/compose/pull/11292) however, in the case
where the user has a TTY attached and the plugin is not already handling
received signals, steps 4+ changes:
4. (When) the CLI receives a termination signal, before it can use the
socket to notify the plugin that it should exit, the plugin process
also receives a signal due to sharing the pgid with the CLI
Since we now have a proper "job control" mechanism, we can simplify the
scenarios by executing the plugins with their own process group id,
thereby removing the "double notification" issue and making it so that
plugins can handle the same whether attached to a TTY or not.
In order to make this change "plugin-binary" backwards-compatible, in
the case that a plugin does not connect to the socket, the CLI passes
the signal to the plugin process.
Co-authored-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
2024-01-08 06:59:30 -05:00
for s := range signals {
2023-10-12 15:20:16 -04:00
if conn != nil {
if err := conn . Close ( ) ; err != nil {
_ , _ = fmt . Fprintf ( dockerCli . Err ( ) , "failed to signal plugin to close: %v\n" , err )
}
conn = nil
plugins: run plugin with new process group ID
Changes were made in 1554ac3b5f38147301c518c60da16f3f80c1717b to provide
a mechanism for the CLI to notify running plugin processes that they
should exit, in order to improve the general CLI/plugin UX. The current
implementation boils down to:
1. The CLI creates a socket
2. The CLI executes the plugin
3. The plugin connects to the socket
4. (When) the CLI receives a termination signal, it uses the socket to
notify the plugin that it should exit
5. The plugin's gets notified via the socket, and cancels it's `cmd.Context`,
which then gets handled appropriately
This change works in most cases and fixes the issue it sets out to solve
(see: https://github.com/docker/compose/pull/11292) however, in the case
where the user has a TTY attached and the plugin is not already handling
received signals, steps 4+ changes:
4. (When) the CLI receives a termination signal, before it can use the
socket to notify the plugin that it should exit, the plugin process
also receives a signal due to sharing the pgid with the CLI
Since we now have a proper "job control" mechanism, we can simplify the
scenarios by executing the plugins with their own process group id,
thereby removing the "double notification" issue and making it so that
plugins can handle the same whether attached to a TTY or not.
In order to make this change "plugin-binary" backwards-compatible, in
the case that a plugin does not connect to the socket, the CLI passes
the signal to the plugin process.
Co-authored-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
2024-01-08 06:59:30 -05:00
} else {
// When the plugin is communicating via socket with the host CLI, we perform job control via the socket.
// However, if the plugin is an old version that is not socket-aware, we need to explicitly forward termination signals.
plugincmd . Process . Signal ( s )
2023-10-12 15:20:16 -04:00
}
retries ++
if retries >= exitLimit {
_ , _ = fmt . Fprintf ( dockerCli . Err ( ) , "got %d SIGTERM/SIGINTs, forcefully exiting\n" , retries )
_ = plugincmd . Process . Kill ( )
os . Exit ( 1 )
}
}
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
}
func runDocker ( dockerCli * command . DockerCli ) error {
tcmd := newDockerCommand ( dockerCli )
cmd , args , err := tcmd . HandleGlobalFlags ( )
if err != nil {
return err
}
if err := tcmd . Initialize ( ) ; err != nil {
return err
}
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
}
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 )
if err != nil || pluginmanager . IsPluginCommand ( ccmd ) {
2022-11-03 17:30:27 -04:00
err := tryPluginRun ( dockerCli , cmd , args [ 0 ] , envs )
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 ) {
return err
}
// For plugin not found we fall through to
// cmd.Execute() which deals with reporting
// "command not found" in a consistent way.
}
}
2019-04-03 06:53:31 -04:00
// We've parsed global args already, so reset args to those
// which remain.
cmd . SetArgs ( args )
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 cmd . Execute ( )
2016-11-21 17:34:55 -05:00
}
2016-04-23 21:31:57 -04:00
func main ( ) {
2019-01-28 08:52:58 -05:00
dockerCli , err := command . NewDockerCli ( )
if err != nil {
2019-01-29 10:21:39 -05:00
fmt . Fprintln ( os . Stderr , err )
2019-01-28 08:52:58 -05:00
os . Exit ( 1 )
}
logrus . SetOutput ( dockerCli . Err ( ) )
2016-04-23 21:31:57 -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 := runDocker ( dockerCli ) ; err != nil {
2016-04-23 21:31:57 -04:00
if sterr , ok := err . ( cli . StatusError ) ; ok {
if sterr . Status != "" {
2019-01-28 08:52:58 -05:00
fmt . Fprintln ( dockerCli . Err ( ) , sterr . Status )
2016-06-01 01:19:13 -04:00
}
// 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 {
2016-04-23 21:31:57 -04:00
os . Exit ( 1 )
}
os . Exit ( sterr . StatusCode )
}
2019-01-28 08:52:58 -05:00
fmt . Fprintln ( dockerCli . Err ( ) , err )
2016-04-23 21:31:57 -04:00
os . Exit ( 1 )
}
}
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
}