2020-05-14 21:13:07 +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-05-14 21:13:07 +02:00
* /
2020-05-05 10:58:24 +02:00
package compose
import (
2021-04-15 12:43:18 +02:00
"context"
2021-03-01 12:43:00 -03:00
"fmt"
2021-03-19 13:37:26 +01:00
"os"
2021-04-22 22:05:01 +02:00
"os/signal"
2021-06-07 14:21:55 +02:00
"path/filepath"
2021-04-06 11:20:58 +02:00
"strings"
2021-04-22 22:05:01 +02:00
"syscall"
2021-03-01 12:43:00 -03:00
2021-08-31 18:53:24 +02:00
"github.com/docker/compose/v2/cmd/formatter"
2021-08-31 15:41:20 +02:00
2021-06-22 11:44:56 +02:00
"github.com/sirupsen/logrus"
2020-08-18 16:56:42 +02:00
"github.com/compose-spec/compose-go/cli"
2020-11-27 14:47:53 +01:00
"github.com/compose-spec/compose-go/types"
2021-04-15 12:43:18 +02:00
dockercli "github.com/docker/cli/cli"
2021-03-01 12:43:00 -03:00
"github.com/morikuni/aec"
"github.com/pkg/errors"
2020-07-29 14:43:41 +02:00
"github.com/spf13/cobra"
2020-11-27 14:47:53 +01:00
"github.com/spf13/pflag"
2020-07-29 14:43:41 +02:00
2021-08-31 18:53:24 +02:00
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
2020-05-05 10:58:24 +02:00
)
2021-02-25 19:38:11 -03:00
// Command defines a compose CLI command as a func with args
2021-04-15 12:43:18 +02:00
type Command func ( context . Context , [ ] string ) error
2021-09-20 12:55:16 +02:00
// CobraCommand defines a cobra command function
2021-09-08 09:07:18 +02:00
type CobraCommand func ( context . Context , * cobra . Command , [ ] string ) error
// AdaptCmd adapt a CobraCommand func to cobra library
func AdaptCmd ( fn CobraCommand ) func ( cmd * cobra . Command , args [ ] string ) error {
2021-04-15 12:43:18 +02:00
return func ( cmd * cobra . Command , args [ ] string ) error {
2021-04-22 22:05:01 +02:00
ctx := cmd . Context ( )
contextString := fmt . Sprintf ( "%s" , ctx )
if ! strings . HasSuffix ( contextString , ".WithCancel" ) { // need to handle cancel
cancellableCtx , cancel := context . WithCancel ( cmd . Context ( ) )
ctx = cancellableCtx
s := make ( chan os . Signal , 1 )
signal . Notify ( s , syscall . SIGTERM , syscall . SIGINT )
go func ( ) {
<- s
cancel ( )
} ( )
}
2021-09-08 09:07:18 +02:00
err := fn ( ctx , cmd , args )
2021-06-15 09:57:38 +02:00
var composeErr compose . Error
2021-06-14 16:26:14 +02:00
if api . IsErrCanceled ( err ) || errors . Is ( ctx . Err ( ) , context . Canceled ) {
2021-04-22 22:05:01 +02:00
err = dockercli . StatusError {
StatusCode : 130 ,
2021-06-15 09:57:38 +02:00
Status : compose . CanceledStatus ,
2021-04-22 22:05:01 +02:00
}
}
2021-04-15 12:43:18 +02:00
if errors . As ( err , & composeErr ) {
err = dockercli . StatusError {
StatusCode : composeErr . GetMetricsFailureCategory ( ) . ExitCode ,
Status : err . Error ( ) ,
}
}
return err
}
}
2021-09-08 09:07:18 +02:00
// Adapt a Command func to cobra library
func Adapt ( fn Command ) func ( cmd * cobra . Command , args [ ] string ) error {
return AdaptCmd ( func ( ctx context . Context , cmd * cobra . Command , args [ ] string ) error {
return fn ( ctx , args )
} )
}
2021-02-23 09:35:26 +01:00
// Warning is a global warning to be displayed to user on command failure
var Warning string
2021-01-20 11:02:46 +01:00
type projectOptions struct {
2021-09-22 09:57:31 +02:00
ProjectName string
Profiles [ ] string
ConfigPaths [ ] string
WorkDir string
ProjectDir string
EnvFile string
Compatibility bool
2020-10-12 11:03:43 +02:00
}
2021-06-03 15:30:05 +02:00
// ProjectFunc does stuff within a types.Project
type ProjectFunc func ( ctx context . Context , project * types . Project ) error
2021-06-07 14:21:55 +02:00
// ProjectServicesFunc does stuff within a types.Project and a selection of services
type ProjectServicesFunc func ( ctx context . Context , project * types . Project , services [ ] string ) error
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
func ( o * projectOptions ) WithProject ( fn ProjectFunc ) func ( cmd * cobra . Command , args [ ] string ) error {
return o . WithServices ( func ( ctx context . Context , project * types . Project , services [ ] string ) error {
return fn ( ctx , project )
} )
}
2021-06-03 15:30:05 +02:00
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
2021-06-07 14:21:55 +02:00
func ( o * projectOptions ) WithServices ( fn ProjectServicesFunc ) func ( cmd * cobra . Command , args [ ] string ) error {
return Adapt ( func ( ctx context . Context , args [ ] string ) error {
2021-09-03 10:13:13 +02:00
project , err := o . toProject ( args , cli . WithResolvedPaths ( true ) )
2021-06-03 15:30:05 +02:00
if err != nil {
return err
}
2021-06-07 14:21:55 +02:00
if o . EnvFile != "" {
var services types . Services
for _ , s := range project . Services {
ef := o . EnvFile
if ef != "" {
if ! filepath . IsAbs ( ef ) {
ef = filepath . Join ( project . WorkingDir , o . EnvFile )
}
if s . Labels == nil {
s . Labels = make ( map [ string ] string )
}
2021-06-14 16:26:14 +02:00
s . Labels [ api . EnvironmentFileLabel ] = ef
2021-06-07 14:21:55 +02:00
services = append ( services , s )
}
}
project . Services = services
}
return fn ( ctx , project , args )
2021-06-03 15:30:05 +02:00
} )
}
2021-01-20 11:02:46 +01:00
func ( o * projectOptions ) addProjectFlags ( f * pflag . FlagSet ) {
2021-02-02 11:42:19 +01:00
f . StringArrayVar ( & o . Profiles , "profile" , [ ] string { } , "Specify a profile to enable" )
2021-01-20 11:02:46 +01:00
f . StringVarP ( & o . ProjectName , "project-name" , "p" , "" , "Project name" )
f . StringArrayVarP ( & o . ConfigPaths , "file" , "f" , [ ] string { } , "Compose configuration files" )
f . StringVar ( & o . EnvFile , "env-file" , "" , "Specify an alternate environment file." )
2021-02-25 17:37:11 -03:00
f . StringVar ( & o . ProjectDir , "project-directory" , "" , "Specify an alternate working directory\n(default: the path of the Compose file)" )
2021-03-01 12:43:00 -03:00
f . StringVar ( & o . WorkDir , "workdir" , "" , "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the Compose file)" )
2021-09-22 09:57:31 +02:00
f . BoolVar ( & o . Compatibility , "compatibility" , false , "Run compose in backward compatibility mode" )
2021-03-01 12:43:00 -03:00
_ = f . MarkHidden ( "workdir" )
2020-08-18 09:40:57 +02:00
}
2021-01-20 11:02:46 +01:00
func ( o * projectOptions ) toProjectName ( ) ( string , error ) {
2020-12-29 16:22:57 -03:00
if o . ProjectName != "" {
return o . ProjectName , nil
2020-08-20 16:42:37 +02:00
}
2021-02-02 11:42:19 +01:00
project , err := o . toProject ( nil )
2020-08-20 16:42:37 +02:00
if err != nil {
return "" , err
}
2021-01-14 15:59:57 +01:00
return project . Name , nil
}
2021-03-22 08:56:46 +01:00
func ( o * projectOptions ) toProject ( services [ ] string , po ... cli . ProjectOptionsFn ) ( * types . Project , error ) {
options , err := o . toProjectOptions ( po ... )
2021-01-14 15:59:57 +01:00
if err != nil {
2021-06-15 09:57:38 +02:00
return nil , compose . WrapComposeError ( err )
2021-01-14 15:59:57 +01:00
}
2020-08-20 16:42:37 +02:00
project , err := cli . ProjectFromOptions ( options )
if err != nil {
2021-06-15 09:57:38 +02:00
return nil , compose . WrapComposeError ( err )
2020-08-20 16:42:37 +02:00
}
2021-02-02 11:42:19 +01:00
2021-05-05 10:12:04 +02:00
if len ( services ) > 0 {
s , err := project . GetServices ( services ... )
if err != nil {
return nil , err
}
o . Profiles = append ( o . Profiles , s . GetProfiles ( ) ... )
2021-02-02 11:42:19 +01:00
}
2021-04-06 11:20:58 +02:00
if profiles , ok := options . Environment [ "COMPOSE_PROFILES" ] ; ok {
o . Profiles = append ( o . Profiles , strings . Split ( profiles , "," ) ... )
}
2021-02-02 11:42:19 +01:00
project . ApplyProfiles ( o . Profiles )
2021-06-23 10:49:50 +02:00
project . WithoutUnnecessaryResources ( )
2021-02-02 11:42:19 +01:00
err = project . ForServices ( services )
return project , err
2020-08-20 16:42:37 +02:00
}
2021-03-22 08:56:46 +01:00
func ( o * projectOptions ) toProjectOptions ( po ... cli . ProjectOptionsFn ) ( * cli . ProjectOptions , error ) {
2020-08-18 09:40:57 +02:00
return cli . NewProjectOptions ( o . ConfigPaths ,
2021-03-22 08:56:46 +01:00
append ( po ,
cli . WithEnvFile ( o . EnvFile ) ,
cli . WithDotEnv ,
cli . WithOsEnv ,
cli . WithWorkingDirectory ( o . ProjectDir ) ,
2021-05-20 14:58:57 +02:00
cli . WithConfigFileEnv ,
2021-04-26 11:53:34 +02:00
cli . WithDefaultConfigPath ,
2021-03-22 08:56:46 +01:00
cli . WithName ( o . ProjectName ) ) ... )
2020-08-18 09:40:57 +02:00
}
2021-04-15 12:43:18 +02:00
// RootCommand returns the compose command with its child commands
2021-08-31 15:41:20 +02:00
func RootCommand ( backend api . Service ) * cobra . Command {
2021-01-20 11:02:46 +01:00
opts := projectOptions { }
2021-06-22 11:44:56 +02:00
var (
ansi string
noAnsi bool
verbose bool
)
2020-05-05 10:58:24 +02:00
command := & cobra . Command {
2021-02-03 14:02:12 +01:00
Short : "Docker Compose" ,
Use : "compose" ,
TraverseChildren : true ,
2021-04-06 23:19:55 +02:00
// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) == 0 {
return cmd . Help ( )
}
_ = cmd . Help ( )
2021-04-15 12:43:18 +02:00
return dockercli . StatusError {
2021-06-15 09:57:38 +02:00
StatusCode : compose . CommandSyntaxFailure . ExitCode ,
2021-04-15 12:43:18 +02:00
Status : fmt . Sprintf ( "unknown docker command: %q" , "compose " + args [ 0 ] ) ,
}
2021-04-06 23:19:55 +02:00
} ,
2020-12-07 15:11:22 +01:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-03-04 19:21:42 +01:00
parent := cmd . Root ( )
parentPrerun := parent . PersistentPreRunE
if parentPrerun != nil {
err := parentPrerun ( cmd , args )
if err != nil {
return err
}
}
2021-03-19 17:24:56 +01:00
if noAnsi {
if ansi != "auto" {
2021-03-19 17:48:06 +01:00
return errors . New ( ` cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi" ` )
2021-03-19 17:24:56 +01:00
}
ansi = "never"
fmt . Fprint ( os . Stderr , aec . Apply ( "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n" , aec . RedF ) )
}
2021-06-22 11:44:56 +02:00
if verbose {
logrus . SetLevel ( logrus . TraceLevel )
}
2021-03-08 10:22:24 +01:00
formatter . SetANSIMode ( ansi )
2021-03-01 12:43:00 -03:00
if opts . WorkDir != "" {
if opts . ProjectDir != "" {
2021-03-19 17:48:06 +01:00
return errors . New ( ` cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead ` )
2021-03-01 12:43:00 -03:00
}
opts . ProjectDir = opts . WorkDir
2021-03-19 17:24:56 +01:00
fmt . Fprint ( os . Stderr , aec . Apply ( "option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n" , aec . RedF ) )
2021-03-01 12:43:00 -03:00
}
2021-09-22 09:57:31 +02:00
if opts . Compatibility || os . Getenv ( "COMPOSE_COMPATIBILITY" ) == "true" {
compose . Separator = "_"
}
2020-12-07 15:11:22 +01:00
return nil
} ,
2020-05-05 10:58:24 +02:00
}
2020-05-14 21:04:38 +02:00
2020-05-05 10:58:24 +02:00
command . AddCommand (
2021-06-07 14:21:55 +02:00
upCommand ( & opts , backend ) ,
2021-06-07 11:21:57 +02:00
downCommand ( & opts , backend ) ,
2021-04-14 11:47:02 +02:00
startCommand ( & opts , backend ) ,
restartCommand ( & opts , backend ) ,
stopCommand ( & opts , backend ) ,
psCommand ( & opts , backend ) ,
2021-08-31 15:41:20 +02:00
listCommand ( backend ) ,
logsCommand ( & opts , backend ) ,
2021-04-14 11:47:02 +02:00
convertCommand ( & opts , backend ) ,
killCommand ( & opts , backend ) ,
runCommand ( & opts , backend ) ,
removeCommand ( & opts , backend ) ,
execCommand ( & opts , backend ) ,
pauseCommand ( & opts , backend ) ,
unpauseCommand ( & opts , backend ) ,
topCommand ( & opts , backend ) ,
eventsCommand ( & opts , backend ) ,
portCommand ( & opts , backend ) ,
imagesCommand ( & opts , backend ) ,
2021-04-02 13:18:51 +02:00
versionCommand ( ) ,
2021-08-31 15:41:20 +02:00
buildCommand ( & opts , backend ) ,
pushCommand ( & opts , backend ) ,
pullCommand ( & opts , backend ) ,
createCommand ( & opts , backend ) ,
copyCommand ( & opts , backend ) ,
2020-05-05 10:58:24 +02:00
)
2020-12-03 09:24:15 +01:00
command . Flags ( ) . SetInterspersed ( false )
2021-02-03 14:02:12 +01:00
opts . addProjectFlags ( command . Flags ( ) )
2021-03-08 10:22:24 +01:00
command . Flags ( ) . StringVar ( & ansi , "ansi" , "auto" , ` Control when to print ANSI control characters ("never"|"always"|"auto") ` )
2021-03-19 17:24:56 +01:00
command . Flags ( ) . BoolVar ( & noAnsi , "no-ansi" , false , ` Do not print ANSI control characters (DEPRECATED) ` )
command . Flags ( ) . MarkHidden ( "no-ansi" ) //nolint:errcheck
2021-06-22 11:44:56 +02:00
command . Flags ( ) . BoolVar ( & verbose , "verbose" , false , "Show more output" )
command . Flags ( ) . MarkHidden ( "verbose" ) //nolint:errcheck
2020-05-14 21:04:38 +02:00
return command
2020-05-05 15:37:12 +02:00
}