2016-04-23 21:31:57 -04:00
package main
import (
2016-11-16 19:38:28 -05:00
"errors"
2016-04-23 21:31:57 -04:00
"fmt"
"os"
2017-02-10 02:35:05 -05:00
"strings"
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"
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"
2016-11-02 20:43:32 -04:00
"github.com/docker/docker/api/types/versions"
2017-05-08 12:33:45 -04:00
"github.com/docker/docker/client"
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
)
2016-09-08 13:11:39 -04:00
func newDockerCommand ( dockerCli * command . DockerCli ) * cobra . Command {
2018-12-18 05:16:52 -05:00
var (
2018-12-11 09:50:04 -05:00
opts * cliflags . ClientOptions
flags * pflag . FlagSet
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 ,
2019-01-17 08:38:38 -05:00
FParseErrWhitelist : cobra . FParseErrWhitelist {
// UnknownFlags ignores any unknown
// --arguments on the top-level docker command
// only. This is necessary to allow passing
// --arguments to plugins otherwise
// e.g. `docker plugin --foo` is caught here
// in the monolithic CLI and `foo` is reported
// as an unknown argument.
UnknownFlags : 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 )
}
plugincmd , err := pluginmanager . PluginRunCommand ( dockerCli , args [ 0 ] , cmd )
if pluginmanager . IsNotFound ( err ) {
return fmt . Errorf (
"docker: '%s' is not a docker command.\nSee 'docker --help'" , args [ 0 ] )
}
if err != nil {
return err
}
return plugincmd . Run ( )
2018-10-10 05:16:27 -04:00
} ,
2016-06-22 13:08:04 -04:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2016-08-03 12:20:46 -04:00
// flags must be the top-level command flags, not cmd.Flags()
opts . Common . SetDefaultOptions ( flags )
2016-11-16 19:38:28 -05:00
if err := dockerCli . Initialize ( opts ) ; err != nil {
return err
}
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 ,
2016-06-22 13:08:04 -04:00
}
2018-12-11 09:50:04 -05:00
opts , flags , helpCmd = cli . SetupRootCommand ( cmd )
2018-05-18 16:57:28 -04:00
flags . BoolP ( "version" , "v" , false , "Print version information and quit" )
2016-11-21 17:34:55 -05:00
setFlagErrorFunc ( dockerCli , cmd , flags , opts )
2018-12-11 09:50:04 -05:00
setupHelpCommand ( dockerCli , cmd , helpCmd , flags , opts )
2016-11-21 17:34:55 -05:00
setHelpFunc ( dockerCli , cmd , flags , opts )
cmd . SetOutput ( dockerCli . Out ( ) )
commands . AddCommands ( cmd , dockerCli )
2018-12-17 11:59:11 -05:00
cli . DisableFlagsInUseLine ( cmd )
2016-11-21 17:34:55 -05:00
setValidateArgs ( dockerCli , cmd , flags , opts )
return cmd
}
func setFlagErrorFunc ( dockerCli * command . DockerCli , cmd * cobra . Command , flags * pflag . FlagSet , opts * cliflags . ClientOptions ) {
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 {
2018-06-08 10:14:48 -04:00
if err := initializeDockerCli ( dockerCli , flags , opts ) ; 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
}
return flagErrorFunc ( cmd , err )
} )
2016-11-21 17:34:55 -05:00
}
2016-06-22 13:08:04 -04:00
2018-12-11 09:50:04 -05:00
func setupHelpCommand ( dockerCli * command . DockerCli , rootCmd , helpCmd * cobra . Command , flags * pflag . FlagSet , opts * cliflags . ClientOptions ) {
origRun := helpCmd . Run
origRunE := helpCmd . RunE
helpCmd . Run = nil
helpCmd . RunE = func ( c * cobra . Command , args [ ] string ) error {
// No Persistent* hooks are called for help, so we must initialize here.
if err := initializeDockerCli ( dockerCli , flags , opts ) ; err != nil {
return err
}
2018-12-11 09:52:59 -05:00
if len ( args ) > 0 {
helpcmd , err := pluginmanager . PluginRunCommand ( dockerCli , args [ 0 ] , rootCmd )
if err == nil {
err = helpcmd . Run ( )
if err != nil {
return err
}
}
if ! pluginmanager . IsNotFound ( err ) {
return err
}
}
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 ( )
}
2016-11-21 17:34:55 -05:00
func setHelpFunc ( dockerCli * command . DockerCli , cmd * cobra . Command , flags * pflag . FlagSet , opts * cliflags . ClientOptions ) {
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 ) {
2018-06-08 10:14:48 -04:00
if err := initializeDockerCli ( dockerCli , flags , opts ) ; err != nil {
ccmd . Println ( err )
return
}
2019-01-30 05:46:31 -05:00
// Add a stub entry for every plugin so they are
// included in the help output and so that
// `tryRunPluginHelp` can find them or if we fall
// through they will be included in the default help
// output.
if err := pluginmanager . AddPluginCommandStubs ( dockerCli , ccmd . Root ( ) ) ; err != nil {
ccmd . Println ( err )
return
}
2018-12-17 11:23:55 -05:00
if len ( args ) >= 1 {
err := tryRunPluginHelp ( dockerCli , ccmd , args )
if err == nil { // Successfully ran the plugin
return
}
if ! pluginmanager . IsNotFound ( err ) {
ccmd . Println ( err )
return
}
}
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
2016-11-21 17:34:55 -05:00
func setValidateArgs ( dockerCli * command . DockerCli , cmd * cobra . Command , flags * pflag . FlagSet , opts * cliflags . ClientOptions ) {
// 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 {
2018-06-08 10:14:48 -04:00
if err := initializeDockerCli ( dockerCli , flags , opts ) ; err != nil {
return err
}
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 )
}
} )
}
2018-06-08 10:14:48 -04:00
func initializeDockerCli ( dockerCli * command . DockerCli , flags * pflag . FlagSet , opts * cliflags . ClientOptions ) error {
if dockerCli . Client ( ) != nil {
return nil
2016-11-21 17:34:55 -05:00
}
2018-06-08 10:14:48 -04:00
// when using --help, PersistentPreRun is not called, so initialization is needed.
// flags must be the top-level command flags, not cmd.Flags()
opts . Common . SetDefaultOptions ( flags )
return dockerCli . Initialize ( opts )
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
2016-06-22 18:36:51 -04:00
cmd := newDockerCommand ( dockerCli )
2016-04-23 21:31:57 -04:00
2016-06-22 13:08:04 -04:00
if err := cmd . Execute ( ) ; 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 {
Client ( ) client . APIClient
2017-12-20 09:04:41 -05:00
ClientInfo ( ) command . ClientInfo
2017-03-14 17:53:29 -04:00
ServerInfo ( ) command . ServerInfo
2017-12-26 09:40:17 -05:00
}
func hideFeatureFlag ( f * pflag . Flag , hasFeature bool , annotation string ) {
if hasFeature {
return
}
if _ , ok := f . Annotations [ annotation ] ; ok {
f . Hidden = true
}
}
func hideFeatureSubCommand ( subcmd * cobra . Command , hasFeature bool , annotation string ) {
if hasFeature {
return
}
if _ , ok := subcmd . Annotations [ annotation ] ; ok {
subcmd . Hidden = true
}
2017-03-14 17:53:29 -04:00
}
2018-10-09 22:13:01 -04:00
func hideUnsupportedFeatures ( cmd * cobra . Command , details versionDetails ) error {
2017-03-14 17:53:29 -04:00
clientVersion := details . Client ( ) . ClientVersion ( )
osType := details . ServerInfo ( ) . OSType
hasExperimental := details . ServerInfo ( ) . HasExperimental
2017-12-20 09:04:41 -05:00
hasExperimentalCLI := details . ClientInfo ( ) . HasExperimental
2018-10-09 22:13:01 -04:00
hasBuildKit , err := command . BuildKitEnabled ( details . ServerInfo ( ) )
if err != nil {
return err
}
2017-03-14 17:53:29 -04:00
2016-11-02 20:43:32 -04:00
cmd . Flags ( ) . VisitAll ( func ( f * pflag . Flag ) {
2017-12-26 09:40:17 -05:00
hideFeatureFlag ( f , hasExperimental , "experimental" )
hideFeatureFlag ( f , hasExperimentalCLI , "experimentalCLI" )
2018-10-09 22:13:01 -04:00
hideFeatureFlag ( f , hasBuildKit , "buildkit" )
hideFeatureFlag ( f , ! hasBuildKit , "no-buildkit" )
2016-11-02 20:43:32 -04:00
// hide flags not supported by the server
2017-02-07 07:52:20 -05:00
if ! isOSTypeSupported ( f , osType ) || ! isVersionSupported ( f , clientVersion ) {
2016-11-02 20:43:32 -04:00
f . Hidden = true
}
2018-06-01 08:58:43 -04:00
// root command shows all top-level flags
if cmd . Parent ( ) != nil {
if commands , ok := f . Annotations [ "top-level" ] ; ok {
f . Hidden = ! findCommand ( cmd , commands )
}
}
2016-11-02 20:43:32 -04:00
} )
for _ , subcmd := range cmd . Commands ( ) {
2017-12-26 09:40:17 -05:00
hideFeatureSubCommand ( subcmd , hasExperimental , "experimental" )
hideFeatureSubCommand ( subcmd , hasExperimentalCLI , "experimentalCLI" )
2018-10-09 22:13:01 -04:00
hideFeatureSubCommand ( subcmd , hasBuildKit , "buildkit" )
hideFeatureSubCommand ( subcmd , ! hasBuildKit , "no-buildkit" )
2016-11-02 20:43:32 -04:00
// hide subcommands not supported by the server
2017-10-25 12:59:32 -04:00
if subcmdVersion , ok := subcmd . Annotations [ "version" ] ; ok && versions . LessThan ( clientVersion , subcmdVersion ) {
2016-11-02 20:43:32 -04:00
subcmd . Hidden = true
}
2018-05-30 17:14:59 -04:00
if v , ok := subcmd . Annotations [ "ostype" ] ; ok && v != osType {
subcmd . Hidden = true
}
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
func findCommand ( cmd * cobra . Command , commands [ ] string ) bool {
if cmd == nil {
return false
}
for _ , c := range commands {
if c == cmd . Name ( ) {
return true
}
}
return findCommand ( cmd . Parent ( ) , commands )
}
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-03-14 17:53:29 -04:00
clientVersion := details . Client ( ) . ClientVersion ( )
osType := details . ServerInfo ( ) . OSType
hasExperimental := details . ServerInfo ( ) . HasExperimental
2017-12-26 09:40:17 -05:00
hasExperimentalCLI := details . ClientInfo ( ) . HasExperimental
2017-01-16 09:35:27 -05:00
errs := [ ] string { }
cmd . Flags ( ) . VisitAll ( func ( f * pflag . Flag ) {
if f . Changed {
2017-02-07 07:52:20 -05:00
if ! isVersionSupported ( f , clientVersion ) {
errs = append ( errs , fmt . Sprintf ( "\"--%s\" requires API version %s, but the Docker daemon API version is %s" , f . Name , getFlagAnnotation ( f , "version" ) , clientVersion ) )
return
}
if ! isOSTypeSupported ( f , osType ) {
2018-05-30 17:14:59 -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" ) , osType ) )
2017-01-16 09:35:27 -05:00
return
}
if _ , ok := f . Annotations [ "experimental" ] ; ok && ! hasExperimental {
errs = append ( errs , fmt . Sprintf ( "\"--%s\" is only supported on a Docker daemon with experimental features enabled" , f . Name ) )
}
2017-12-20 09:04:41 -05:00
if _ , ok := f . Annotations [ "experimentalCLI" ] ; ok && ! hasExperimentalCLI {
2018-05-30 17:14:59 -04:00
errs = append ( errs , fmt . Sprintf ( "\"--%s\" is on a Docker cli with experimental cli features enabled" , f . Name ) )
2017-12-20 09:04:41 -05:00
}
2018-10-09 22:13:01 -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 {
clientVersion := details . Client ( ) . ClientVersion ( )
2018-05-30 17:14:59 -04:00
osType := details . ServerInfo ( ) . OSType
2017-12-04 06:30:39 -05:00
hasExperimental := details . ServerInfo ( ) . HasExperimental
hasExperimentalCLI := details . ClientInfo ( ) . HasExperimental
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
for curr := cmd ; curr != nil ; curr = curr . Parent ( ) {
if cmdVersion , ok := curr . Annotations [ "version" ] ; ok && versions . LessThan ( clientVersion , cmdVersion ) {
return fmt . Errorf ( "%s requires API version %s, but the Docker daemon API version is %s" , cmd . CommandPath ( ) , cmdVersion , clientVersion )
}
2018-05-30 17:14:59 -04:00
if os , ok := curr . Annotations [ "ostype" ] ; ok && os != 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 ( ) , os , osType )
}
2017-12-04 06:30:39 -05:00
if _ , ok := curr . Annotations [ "experimental" ] ; ok && ! hasExperimental {
return fmt . Errorf ( "%s is only supported on a Docker daemon with experimental features enabled" , cmd . CommandPath ( ) )
}
if _ , ok := curr . Annotations [ "experimentalCLI" ] ; ok && ! hasExperimentalCLI {
2018-05-30 17:14:59 -04:00
return fmt . Errorf ( "%s is only supported on a Docker cli with experimental cli features enabled" , cmd . CommandPath ( ) )
2017-12-04 06:30:39 -05:00
}
}
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
}