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"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/commands"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/debug"
cliflags "github.com/docker/cli/cli/flags"
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 {
2016-06-22 18:36:51 -04:00
opts := cliflags . NewClientOptions ( )
2016-08-03 12:20:46 -04:00
var flags * pflag . FlagSet
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 ,
2016-08-03 12:20:46 -04:00
Args : noArgs ,
2018-10-10 05:16:27 -04:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return command . ShowHelp ( dockerCli . Err ( ) ) ( cmd , args )
} ,
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 )
dockerPreRun ( opts )
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
} ,
2018-05-18 20:46:27 -04:00
Version : fmt . Sprintf ( "%s, build %s" , cli . Version , cli . GitCommit ) ,
DisableFlagsInUseLine : true ,
2016-06-22 13:08:04 -04:00
}
2016-06-23 11:25:51 -04:00
cli . SetupRootCommand ( cmd )
2016-11-21 17:34:55 -05:00
flags = cmd . Flags ( )
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
flags . StringVar ( & opts . ConfigDir , "config" , cliconfig . Dir ( ) , "Location of client config files" )
opts . Common . InstallFlags ( flags )
setFlagErrorFunc ( dockerCli , cmd , flags , opts )
setHelpFunc ( dockerCli , cmd , flags , opts )
cmd . SetOutput ( dockerCli . Out ( ) )
commands . AddCommands ( cmd , dockerCli )
2018-05-18 20:46:27 -04:00
disableFlagsInUseLine ( cmd )
2016-11-21 17:34:55 -05:00
setValidateArgs ( dockerCli , cmd , flags , opts )
return cmd
}
2018-05-18 20:46:27 -04:00
func disableFlagsInUseLine ( cmd * cobra . Command ) {
visitAll ( cmd , func ( ccmd * cobra . Command ) {
// do not add a `[flags]` to the end of the usage line.
ccmd . DisableFlagsInUseLine = true
} )
}
2016-11-21 17:34:55 -05:00
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
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
}
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
}
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.
visitAll ( cmd , func ( ccmd * cobra . Command ) {
// 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 )
dockerPreRun ( opts )
return dockerCli . Initialize ( opts )
2016-11-21 17:34:55 -05:00
}
// visitAll will traverse all commands from the root.
// This is different from the VisitAll of cobra.Command where only parents
// are checked.
func visitAll ( root * cobra . Command , fn func ( * cobra . Command ) ) {
for _ , cmd := range root . Commands ( ) {
visitAll ( cmd , fn )
}
fn ( root )
2016-06-22 13:08:04 -04:00
}
2016-04-25 12:05:42 -04:00
2016-08-03 12:20:46 -04:00
func noArgs ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) == 0 {
return nil
}
return fmt . Errorf (
2016-11-18 18:57:11 -05:00
"docker: '%s' is not a docker command.\nSee 'docker --help'" , args [ 0 ] )
2016-08-03 12:20:46 -04: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 )
}
}
2016-08-03 12:20:46 -04:00
func dockerPreRun ( opts * cliflags . ClientOptions ) {
2016-10-11 07:35:12 -04:00
cliflags . SetLogLevel ( opts . Common . LogLevel )
2016-04-25 12:05:42 -04:00
2016-06-22 13:08:04 -04:00
if opts . ConfigDir != "" {
2016-12-25 14:31:52 -05:00
cliconfig . SetDir ( opts . ConfigDir )
2016-06-22 13:08:04 -04:00
}
2016-04-25 12:05:42 -04:00
2016-06-22 13:08:04 -04:00
if opts . Common . Debug {
2016-12-12 03:33:58 -05:00
debug . Enable ( )
2016-04-25 12:05:42 -04:00
}
}
2016-11-02 20:43:32 -04:00
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
}