2020-04-24 18:04:32 +02:00
/ *
2020-09-22 12:13:00 +02:00
Copyright 2020 Docker Compose CLI authors
2020-06-18 16:13:24 +02:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
2020-04-24 18:04:32 +02:00
* /
package main
import (
"context"
"fmt"
2020-05-22 10:45:01 +02:00
"math/rand"
2020-04-24 18:04:32 +02:00
"os"
2020-05-13 10:24:16 +02:00
"os/signal"
2020-04-24 18:04:32 +02:00
"path/filepath"
2020-06-05 17:30:27 +02:00
"regexp"
2020-05-13 10:24:16 +02:00
"syscall"
2020-05-22 10:45:01 +02:00
"time"
2020-04-24 18:04:32 +02:00
2020-09-11 16:08:06 +02:00
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
2020-08-21 17:24:53 +02:00
"github.com/docker/compose-cli/cli/cmd"
2020-09-25 14:27:50 +02:00
"github.com/docker/compose-cli/cli/cmd/compose"
2020-08-21 17:24:53 +02:00
contextcmd "github.com/docker/compose-cli/cli/cmd/context"
"github.com/docker/compose-cli/cli/cmd/login"
2020-09-25 14:27:50 +02:00
"github.com/docker/compose-cli/cli/cmd/logout"
2020-08-21 17:24:53 +02:00
"github.com/docker/compose-cli/cli/cmd/run"
2020-09-25 14:27:50 +02:00
volume "github.com/docker/compose-cli/cli/cmd/volume"
2020-08-21 17:24:53 +02:00
"github.com/docker/compose-cli/cli/mobycli"
cliopts "github.com/docker/compose-cli/cli/options"
"github.com/docker/compose-cli/config"
apicontext "github.com/docker/compose-cli/context"
"github.com/docker/compose-cli/context/store"
2020-09-25 14:27:50 +02:00
"github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/metrics"
// Backend registrations
_ "github.com/docker/compose-cli/aci"
2020-09-10 14:17:49 +02:00
_ "github.com/docker/compose-cli/ecs"
_ "github.com/docker/compose-cli/ecs/local"
_ "github.com/docker/compose-cli/example"
_ "github.com/docker/compose-cli/local"
2020-04-24 18:04:32 +02:00
)
2020-06-23 17:14:59 +02:00
var (
version = "dev"
)
2020-05-12 14:35:16 +02:00
var (
2020-09-04 10:19:40 +02:00
contextAgnosticCommands = map [ string ] struct { } {
2020-07-29 11:58:09 +02:00
"compose" : { } ,
2020-05-29 17:07:48 +02:00
"context" : { } ,
"login" : { } ,
2020-07-08 16:01:54 +02:00
"logout" : { } ,
2020-05-29 17:07:48 +02:00
"serve" : { } ,
2020-06-12 17:54:14 +02:00
"version" : { } ,
2020-05-29 17:07:48 +02:00
}
2020-06-26 10:23:37 +02:00
unknownCommandRegexp = regexp . MustCompile ( ` unknown command "([^"]*)" ` )
2020-05-12 14:35:16 +02:00
)
2020-04-24 18:04:32 +02:00
func init ( ) {
// initial hack to get the path of the project's bin dir
// into the env of this cli for development
path , err := filepath . Abs ( filepath . Dir ( os . Args [ 0 ] ) )
if err != nil {
2020-05-14 09:59:12 +02:00
fatal ( errors . Wrap ( err , "unable to get absolute bin path" ) )
2020-04-24 18:04:32 +02:00
}
if err := os . Setenv ( "PATH" , fmt . Sprintf ( "%s:%s" , os . Getenv ( "PATH" ) , path ) ) ; err != nil {
panic ( err )
}
2020-05-22 10:45:01 +02:00
// Seed random
rand . Seed ( time . Now ( ) . UnixNano ( ) )
2020-04-24 18:04:32 +02:00
}
2020-09-04 10:19:40 +02:00
func isContextAgnosticCommand ( cmd * cobra . Command ) bool {
2020-04-26 22:13:35 +02:00
if cmd == nil {
return false
}
2020-09-04 10:19:40 +02:00
if _ , ok := contextAgnosticCommands [ cmd . Name ( ) ] ; ok {
2020-04-26 22:13:35 +02:00
return true
}
2020-09-04 10:19:40 +02:00
return isContextAgnosticCommand ( cmd . Parent ( ) )
2020-04-26 22:13:35 +02:00
}
2020-04-24 18:04:32 +02:00
func main ( ) {
2020-05-14 18:29:09 +02:00
var opts cliopts . GlobalOpts
2020-04-24 18:04:32 +02:00
root := & cobra . Command {
2020-04-27 11:32:16 +02:00
Use : "docker" ,
SilenceErrors : true ,
2020-05-03 13:35:25 +02:00
SilenceUsage : true ,
2020-04-24 18:04:32 +02:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2020-09-04 10:19:40 +02:00
if ! isContextAgnosticCommand ( cmd ) {
2020-09-16 17:22:21 +02:00
mobycli . ExecIfDefaultCtxType ( cmd . Context ( ) , cmd . Root ( ) )
2020-04-26 22:13:35 +02:00
}
2020-04-24 18:04:32 +02:00
return nil
} ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return cmd . Help ( )
} ,
}
2020-05-01 15:28:44 +02:00
root . AddCommand (
2020-05-20 15:55:05 +02:00
contextcmd . Command ( ) ,
2020-05-06 09:37:52 +02:00
cmd . PsCommand ( ) ,
2020-05-01 15:28:44 +02:00
cmd . ServeCommand ( ) ,
2020-05-03 13:35:25 +02:00
cmd . ExecCommand ( ) ,
2020-05-03 13:41:45 +02:00
cmd . LogsCommand ( ) ,
2020-05-10 22:37:28 +02:00
cmd . RmCommand ( ) ,
2020-08-12 10:35:15 +02:00
cmd . StartCommand ( ) ,
2020-06-12 10:53:06 +02:00
cmd . InspectCommand ( ) ,
2020-05-29 17:07:48 +02:00
login . Command ( ) ,
2020-07-08 16:01:54 +02:00
logout . Command ( ) ,
2020-06-23 17:14:59 +02:00
cmd . VersionCommand ( version ) ,
2020-08-11 17:49:02 +02:00
cmd . StopCommand ( ) ,
2020-09-01 16:06:07 +02:00
cmd . KillCommand ( ) ,
2020-08-20 15:55:55 +02:00
cmd . SecretCommand ( ) ,
2020-08-14 10:20:02 +02:00
// Place holders
cmd . EcsCommand ( ) ,
2020-05-01 15:28:44 +02:00
)
2020-04-24 18:04:32 +02:00
helpFunc := root . HelpFunc ( )
root . SetHelpFunc ( func ( cmd * cobra . Command , args [ ] string ) {
2020-09-04 10:19:40 +02:00
if ! isContextAgnosticCommand ( cmd ) {
2020-09-16 17:22:21 +02:00
mobycli . ExecIfDefaultCtxType ( cmd . Context ( ) , cmd . Root ( ) )
2020-04-26 22:13:35 +02:00
}
2020-04-24 18:04:32 +02:00
helpFunc ( cmd , args )
} )
2020-10-04 02:43:18 +02:00
root . PersistentFlags ( ) . BoolVarP ( & opts . Debug , "debug" , "D" , false , "Enable debug output in the logs" )
2020-06-23 11:27:19 +02:00
root . PersistentFlags ( ) . StringVarP ( & opts . Host , "host" , "H" , "" , "Daemon socket(s) to connect to" )
2020-05-14 09:48:53 +02:00
opts . AddConfigFlags ( root . PersistentFlags ( ) )
opts . AddContextFlags ( root . PersistentFlags ( ) )
2020-06-23 15:13:43 +02:00
root . Flags ( ) . BoolVarP ( & opts . Version , "version" , "v" , false , "Print version information and quit" )
2020-04-24 18:04:32 +02:00
2020-10-04 02:43:18 +02:00
walk ( root , func ( c * cobra . Command ) {
c . Flags ( ) . BoolP ( "help" , "h" , false , "Help for " + c . Name ( ) )
} )
2020-04-24 18:04:32 +02:00
// populate the opts with the global flags
_ = root . PersistentFlags ( ) . Parse ( os . Args [ 1 : ] )
2020-05-14 18:29:09 +02:00
if opts . Debug {
2020-04-24 18:04:32 +02:00
logrus . SetLevel ( logrus . DebugLevel )
}
2020-05-13 10:24:16 +02:00
ctx , cancel := newSigContext ( )
2020-04-24 18:04:32 +02:00
defer cancel ( )
2020-06-26 10:23:37 +02:00
// --host and --version should immediately be forwarded to the original cli
if opts . Host != "" || opts . Version {
2020-09-16 17:22:21 +02:00
mobycli . Exec ( root )
2020-06-23 15:13:43 +02:00
}
2020-06-23 11:27:19 +02:00
2020-05-14 18:29:09 +02:00
if opts . Config == "" {
fatal ( errors . New ( "config path cannot be empty" ) )
}
2020-05-20 15:55:05 +02:00
configDir := opts . Config
2020-06-06 22:22:30 +02:00
ctx = config . WithDir ( ctx , configDir )
2020-05-18 14:38:21 +02:00
2020-06-15 10:04:01 +02:00
currentContext := determineCurrentContext ( opts . Context , configDir )
2020-04-29 23:10:06 +02:00
2020-09-16 15:53:25 +02:00
s , err := store . New ( configDir )
2020-04-24 18:04:32 +02:00
if err != nil {
2020-09-16 17:22:21 +02:00
mobycli . Exec ( root )
2020-04-24 18:04:32 +02:00
}
2020-06-22 09:55:28 +02:00
ctype := store . DefaultContextType
cc , _ := s . Get ( currentContext )
if cc != nil {
ctype = cc . Type ( )
}
2020-09-21 17:05:49 +02:00
root . AddCommand (
run . Command ( ctype ) ,
compose . Command ( ctype ) ,
)
2020-09-21 15:39:05 +02:00
2020-09-09 10:46:11 +02:00
if ctype == store . AciContextType {
// we can also pass ctype as a parameter to the volume command and customize subcommands, flags, etc. when we have other backend implementations
root . AddCommand ( volume . ACICommand ( ) )
}
2020-05-14 09:48:53 +02:00
ctx = apicontext . WithCurrentContext ( ctx , currentContext )
2020-04-24 18:04:32 +02:00
ctx = store . WithContextStore ( ctx , s )
2020-06-26 10:23:37 +02:00
if err = root . ExecuteContext ( ctx ) ; err != nil {
2020-08-11 15:46:29 +02:00
// if user canceled request, simply exit without any error message
2020-09-18 10:56:24 +02:00
if errdefs . IsErrCanceled ( err ) || errors . Is ( ctx . Err ( ) , context . Canceled ) {
metrics . Track ( ctype , os . Args [ 1 : ] , root . PersistentFlags ( ) , metrics . CanceledStatus )
2020-07-06 13:51:48 +02:00
os . Exit ( 130 )
}
2020-09-04 10:19:40 +02:00
if ctype == store . AwsContextType {
2020-09-25 14:27:50 +02:00
exit ( root , currentContext , errors . Errorf ( ` % q context type has been renamed . Recreate the context by running :
2020-09-16 17:22:21 +02:00
$ docker context create % s < name > ` , cc . Type ( ) , store . EcsContextType ) , ctype )
2020-09-04 10:19:40 +02:00
}
2020-08-11 15:46:29 +02:00
2020-05-12 14:35:16 +02:00
// Context should always be handled by new CLI
2020-06-10 17:24:13 +02:00
requiredCmd , _ , _ := root . Find ( os . Args [ 1 : ] )
2020-09-04 10:19:40 +02:00
if requiredCmd != nil && isContextAgnosticCommand ( requiredCmd ) {
2020-09-16 17:22:21 +02:00
exit ( root , currentContext , err , ctype )
2020-05-12 14:35:16 +02:00
}
2020-09-16 17:22:21 +02:00
mobycli . ExecIfDefaultCtxType ( ctx , root )
2020-06-05 17:30:27 +02:00
2020-09-23 16:12:09 +02:00
checkIfUnknownCommandExistInDefaultContext ( err , currentContext , root , ctype )
2020-08-11 15:46:29 +02:00
2020-09-16 17:22:21 +02:00
exit ( root , currentContext , err , ctype )
2020-07-02 13:52:57 +02:00
}
2020-09-16 17:22:21 +02:00
metrics . Track ( ctype , os . Args [ 1 : ] , root . PersistentFlags ( ) , metrics . SuccessStatus )
2020-07-02 13:52:57 +02:00
}
2020-09-16 17:22:21 +02:00
func exit ( root * cobra . Command , ctx string , err error , ctype string ) {
metrics . Track ( ctype , os . Args [ 1 : ] , root . PersistentFlags ( ) , metrics . FailureStatus )
2020-07-02 13:52:57 +02:00
if errors . Is ( err , errdefs . ErrLoginRequired ) {
2020-07-29 11:58:09 +02:00
fmt . Fprintln ( os . Stderr , err )
2020-07-02 13:52:57 +02:00
os . Exit ( errdefs . ExitCodeLoginRequired )
2020-04-24 18:04:32 +02:00
}
2020-08-20 09:06:17 +02:00
if errors . Is ( err , errdefs . ErrNotImplemented ) {
2020-09-30 10:44:10 +02:00
name := metrics . GetCommand ( os . Args [ 1 : ] , root . PersistentFlags ( ) )
2020-08-20 09:06:17 +02:00
fmt . Fprintf ( os . Stderr , "Command %q not available in current context (%s)\n" , name , ctx )
2020-09-30 10:44:10 +02:00
2020-08-20 09:06:17 +02:00
os . Exit ( 1 )
}
2020-07-02 13:52:57 +02:00
fatal ( err )
2020-04-24 18:04:32 +02:00
}
2020-08-11 15:46:29 +02:00
func fatal ( err error ) {
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 1 )
}
2020-09-23 16:12:09 +02:00
func checkIfUnknownCommandExistInDefaultContext ( err error , currentContext string , root * cobra . Command , contextType string ) {
2020-06-26 10:23:37 +02:00
submatch := unknownCommandRegexp . FindSubmatch ( [ ] byte ( err . Error ( ) ) )
2020-06-05 17:30:27 +02:00
if len ( submatch ) == 2 {
dockerCommand := string ( submatch [ 1 ] )
2020-06-17 17:57:44 +02:00
if mobycli . IsDefaultContextCommand ( dockerCommand ) {
2020-06-26 10:23:37 +02:00
fmt . Fprintf ( os . Stderr , "Command %q not available in current context (%s), you can use the \"default\" context to run this command\n" , dockerCommand , currentContext )
2020-09-23 16:12:09 +02:00
metrics . Track ( contextType , os . Args [ 1 : ] , root . PersistentFlags ( ) , metrics . FailureStatus )
2020-06-05 17:30:27 +02:00
os . Exit ( 1 )
}
}
}
2020-05-13 10:24:16 +02:00
func newSigContext ( ) ( context . Context , func ( ) ) {
ctx , cancel := context . WithCancel ( context . Background ( ) )
2020-08-13 10:34:42 +02:00
s := make ( chan os . Signal , 1 )
2020-05-13 10:24:16 +02:00
signal . Notify ( s , syscall . SIGTERM , syscall . SIGINT )
go func ( ) {
<- s
cancel ( )
} ( )
return ctx , cancel
}
2020-06-15 10:04:01 +02:00
func determineCurrentContext ( flag string , configDir string ) string {
2020-05-18 14:38:21 +02:00
res := flag
if res == "" {
2020-06-06 22:22:30 +02:00
config , err := config . LoadFile ( configDir )
2020-05-18 14:38:21 +02:00
if err != nil {
2020-06-15 10:04:01 +02:00
fmt . Fprintln ( os . Stderr , errors . Wrap ( err , "WARNING" ) )
return "default"
2020-05-18 14:38:21 +02:00
}
res = config . CurrentContext
}
if res == "" {
res = "default"
}
2020-06-15 10:04:01 +02:00
return res
2020-05-18 14:38:21 +02:00
}
2020-10-04 02:43:18 +02:00
func walk ( c * cobra . Command , f func ( * cobra . Command ) ) {
f ( c )
for _ , c := range c . Commands ( ) {
walk ( c , f )
}
}