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"
2016-04-23 21:31:57 -04:00
"github.com/docker/docker/pkg/term"
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 ,
2016-06-22 13:08:04 -04:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if opts . Version {
showVersion ( )
return nil
}
2017-05-03 17:58:52 -04:00
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
} ,
}
2016-06-23 11:25:51 -04:00
cli . SetupRootCommand ( cmd )
2016-11-21 17:34:55 -05:00
flags = cmd . Flags ( )
flags . BoolVarP ( & opts . Version , "version" , "v" , false , "Print version information and quit" )
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 )
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 {
2016-11-21 17:34:55 -05:00
initializeDockerCli ( dockerCli , flags , opts )
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 ) {
2016-11-21 17:34:55 -05:00
initializeDockerCli ( dockerCli , flags , opts )
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
}
2017-03-14 17:53:29 -04:00
hideUnsupportedFeatures ( ccmd , dockerCli )
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 {
initializeDockerCli ( dockerCli , flags , opts )
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 )
}
} )
}
func initializeDockerCli ( dockerCli * command . DockerCli , flags * pflag . FlagSet , opts * cliflags . ClientOptions ) {
if dockerCli . Client ( ) == nil { // 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 )
dockerCli . Initialize ( opts )
}
}
// 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 ( ) {
// Set terminal emulation based on platform as required.
stdin , stdout , stderr := term . StdStreams ( )
logrus . SetOutput ( stderr )
2016-09-08 13:11:39 -04:00
dockerCli := command . NewDockerCli ( stdin , stdout , stderr )
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 != "" {
fmt . Fprintln ( stderr , 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 )
}
fmt . Fprintln ( stderr , err )
os . Exit ( 1 )
}
}
func showVersion ( ) {
2017-05-09 12:38:23 -04:00
fmt . Printf ( "Docker version %s, build %s\n" , cli . Version , cli . GitCommit )
2016-04-23 21:31:57 -04:00
}
2016-04-25 12:05:42 -04:00
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
}
func hideUnsupportedFeatures ( cmd * cobra . Command , details versionDetails ) {
clientVersion := details . Client ( ) . ClientVersion ( )
osType := details . ServerInfo ( ) . OSType
hasExperimental := details . ServerInfo ( ) . HasExperimental
2017-12-20 09:04:41 -05:00
hasExperimentalCLI := details . ClientInfo ( ) . HasExperimental
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 experimental flags
2016-11-11 20:43:06 -05:00
if ! hasExperimental {
if _ , ok := f . Annotations [ "experimental" ] ; ok {
f . Hidden = true
}
2016-11-02 20:43:32 -04:00
}
2017-12-20 09:04:41 -05:00
if ! hasExperimentalCLI {
if _ , ok := f . Annotations [ "experimentalCLI" ] ; ok {
f . Hidden = true
}
}
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
}
2016-11-02 20:43:32 -04:00
} )
for _ , subcmd := range cmd . Commands ( ) {
2016-11-02 20:43:32 -04:00
// hide experimental subcommands
2016-11-11 20:43:06 -05:00
if ! hasExperimental {
2017-10-25 12:59:32 -04:00
if _ , ok := subcmd . Annotations [ "experimental" ] ; ok {
2016-11-11 20:43:06 -05:00
subcmd . Hidden = true
}
2016-11-02 20:43:32 -04:00
}
2017-12-20 09:04:41 -05:00
if ! hasExperimentalCLI {
if _ , ok := subcmd . Annotations [ "experimentalCLI" ] ; ok {
subcmd . Hidden = true
}
}
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
}
2016-11-02 20:43:32 -04:00
}
}
2016-11-16 19:38:28 -05:00
2017-03-14 17:53:29 -04:00
func isSupported ( cmd * cobra . Command , details versionDetails ) error {
clientVersion := details . Client ( ) . ClientVersion ( )
osType := details . ServerInfo ( ) . OSType
hasExperimental := details . ServerInfo ( ) . HasExperimental
2017-12-20 09:04:41 -05:00
hasExperimentalCLI := details . ClientInfo ( ) . HasExperimental
2017-03-14 17:53:29 -04:00
2017-03-03 07:58:50 -05:00
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
for curr := cmd ; curr != nil ; curr = curr . Parent ( ) {
2017-10-25 12:59:32 -04:00
if cmdVersion , ok := curr . Annotations [ "version" ] ; ok && versions . LessThan ( clientVersion , cmdVersion ) {
2017-03-03 07:58:50 -05:00
return fmt . Errorf ( "%s requires API version %s, but the Docker daemon API version is %s" , cmd . CommandPath ( ) , cmdVersion , clientVersion )
}
2017-10-25 12:59:32 -04:00
if _ , ok := curr . Annotations [ "experimental" ] ; ok && ! hasExperimental {
2017-03-03 07:58:50 -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
}
2017-12-20 09:04:41 -05:00
if _ , ok := curr . Annotations [ "experimentalCLI" ] ; ok && ! hasExperimentalCLI {
return fmt . Errorf ( "%s is only supported when experimental cli features are enabled" , cmd . CommandPath ( ) )
}
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 ) {
errs = append ( errs , fmt . Sprintf ( "\"--%s\" requires the Docker daemon to run 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 {
errs = append ( errs , fmt . Sprintf ( "\"--%s\" is only supported when experimental cli features are enabled" , f . Name ) )
}
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
}
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
}