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"
2024-02-08 21:19:54 +01:00
"encoding/json"
2023-09-26 00:57:12 +02:00
"errors"
2021-03-01 12:43:00 -03:00
"fmt"
2024-10-02 07:52:51 +02:00
"io"
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"
2023-01-04 12:41:36 +01:00
"strconv"
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
2023-11-08 10:19:24 +01:00
"github.com/compose-spec/compose-go/v2/cli"
2024-07-08 09:40:04 +02:00
"github.com/compose-spec/compose-go/v2/dotenv"
2024-02-15 08:28:36 +01:00
"github.com/compose-spec/compose-go/v2/loader"
2023-11-08 10:19:24 +01:00
"github.com/compose-spec/compose-go/v2/types"
2024-07-08 09:40:04 +02:00
composegoutils "github.com/compose-spec/compose-go/v2/utils"
2022-08-12 23:49:02 +02:00
"github.com/docker/buildx/util/logutil"
2021-04-15 12:43:18 +02:00
dockercli "github.com/docker/cli/cli"
2021-09-29 23:45:11 +02:00
"github.com/docker/cli/cli-plugins/manager"
2023-08-30 10:15:35 +02:00
"github.com/docker/cli/cli/command"
2024-10-02 07:52:51 +02:00
"github.com/docker/cli/pkg/kvfile"
2024-02-05 17:38:15 -05:00
"github.com/docker/compose/v2/cmd/formatter"
2024-03-12 09:47:41 -04:00
"github.com/docker/compose/v2/internal/desktop"
2024-03-20 07:44:27 -06:00
"github.com/docker/compose/v2/internal/experimental"
2024-02-12 14:52:58 +00:00
"github.com/docker/compose/v2/internal/tracing"
2024-02-05 17:38:15 -05:00
"github.com/docker/compose/v2/pkg/api"
ui "github.com/docker/compose/v2/pkg/progress"
2023-08-30 10:15:35 +02:00
"github.com/docker/compose/v2/pkg/remote"
2024-02-05 17:38:15 -05:00
"github.com/docker/compose/v2/pkg/utils"
2021-03-01 12:43:00 -03:00
"github.com/morikuni/aec"
2021-09-29 23:45:11 +02:00
"github.com/sirupsen/logrus"
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-05-05 10:58:24 +02:00
)
2023-05-14 16:50:41 +02:00
const (
// ComposeParallelLimit set the limit running concurrent operation on docker engine
ComposeParallelLimit = "COMPOSE_PARALLEL_LIMIT"
// ComposeProjectName define the project name to be used, instead of guessing from parent directory
ComposeProjectName = "COMPOSE_PROJECT_NAME"
// ComposeCompatibility try to mimic compose v1 as much as possible
ComposeCompatibility = "COMPOSE_COMPATIBILITY"
2024-09-08 07:01:35 +10:00
// ComposeRemoveOrphans remove "orphaned" containers, i.e. containers tagged for current project but not declared as service
2023-05-14 16:50:41 +02:00
ComposeRemoveOrphans = "COMPOSE_REMOVE_ORPHANS"
// ComposeIgnoreOrphans ignore "orphaned" containers
ComposeIgnoreOrphans = "COMPOSE_IGNORE_ORPHANS"
2023-10-02 16:41:20 -07:00
// ComposeEnvFiles defines the env files to use if --env-file isn't used
ComposeEnvFiles = "COMPOSE_ENV_FILES"
2024-03-08 14:07:51 +00:00
// ComposeMenu defines if the navigation menu should be rendered. Can be also set via --menu
ComposeMenu = "COMPOSE_MENU"
2025-04-25 11:27:16 +02:00
// ComposeProgress defines type of progress output, if --progress isn't used
ComposeProgress = "COMPOSE_PROGRESS"
2023-05-14 16:50:41 +02:00
)
2024-10-02 07:52:51 +02:00
// rawEnv load a dot env file using docker/cli key=value parser, without attempt to interpolate or evaluate values
2025-04-02 17:37:34 +02:00
func rawEnv ( r io . Reader , filename string , vars map [ string ] string , lookup func ( key string ) ( string , bool ) ) error {
2024-10-02 07:52:51 +02:00
lines , err := kvfile . ParseFromReader ( r , lookup )
if err != nil {
2025-04-02 17:37:34 +02:00
return fmt . Errorf ( "failed to parse env_file %s: %w" , filename , err )
2024-10-02 07:52:51 +02:00
}
for _ , line := range lines {
key , value , _ := strings . Cut ( line , "=" )
vars [ key ] = value
}
2025-04-02 17:37:34 +02:00
return nil
2024-10-02 07:52:51 +02:00
}
func init ( ) {
// compose evaluates env file values for interpolation
// `raw` format allows to load env_file with the same parser used by docker run --env-file
dotenv . RegisterFormat ( "raw" , rawEnv )
}
2024-03-20 07:44:27 -06:00
type Backend interface {
api . Service
SetDesktopClient ( cli * desktop . Client )
SetExperiments ( experiments * experimental . State )
}
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 {
2024-01-17 16:44:02 +00:00
ctx , cancel := context . WithCancel ( cmd . Context ( ) )
s := make ( chan os . Signal , 1 )
signal . Notify ( s , syscall . SIGTERM , syscall . SIGINT )
go func ( ) {
<- s
cancel ( )
signal . Stop ( s )
close ( s )
} ( )
2021-09-08 09:07:18 +02:00
err := fn ( ctx , cmd , args )
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-04-15 12:43:18 +02:00
}
}
2024-02-08 21:19:54 +01:00
if ui . Mode == ui . ModeJSON {
err = makeJSONError ( err )
}
2021-04-15 12:43:18 +02:00
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 )
} )
}
2022-12-19 16:38:36 +00:00
type ProjectOptions struct {
2021-09-22 09:57:31 +02:00
ProjectName string
Profiles [ ] string
ConfigPaths [ ] string
WorkDir string
ProjectDir string
2023-02-15 14:30:09 +01:00
EnvFiles [ ] string
2021-09-22 09:57:31 +02:00
Compatibility bool
2023-08-30 08:47:09 -04:00
Progress string
2023-08-30 10:15:35 +02:00
Offline bool
2024-04-09 11:32:00 +02:00
All 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
2021-09-29 23:45:11 +02:00
// WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
2023-08-30 10:15:35 +02:00
func ( o * ProjectOptions ) WithProject ( fn ProjectFunc , dockerCli command . Cli ) func ( cmd * cobra . Command , args [ ] string ) error {
return o . WithServices ( dockerCli , func ( ctx context . Context , project * types . Project , services [ ] string ) error {
2021-06-07 14:21:55 +02:00
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
2023-08-30 10:15:35 +02:00
func ( o * ProjectOptions ) WithServices ( dockerCli command . Cli , fn ProjectServicesFunc ) func ( cmd * cobra . Command , args [ ] string ) error {
2021-06-07 14:21:55 +02:00
return Adapt ( func ( ctx context . Context , args [ ] string ) error {
2023-08-18 15:16:45 +02:00
options := [ ] cli . ProjectOptionsFn {
cli . WithResolvedPaths ( true ) ,
2025-03-06 16:12:51 +01:00
cli . WithoutEnvironmentResolution ,
2023-08-18 15:16:45 +02:00
}
2024-02-12 14:52:58 +00:00
project , metrics , err := o . ToProject ( ctx , dockerCli , args , options ... )
2021-06-03 15:30:05 +02:00
if err != nil {
return err
}
2021-06-07 14:21:55 +02:00
2024-02-15 08:28:36 +01:00
ctx = context . WithValue ( ctx , tracing . MetricsKey { } , metrics )
2024-02-12 14:52:58 +00:00
2025-03-06 16:12:51 +01:00
project , err = project . WithServicesEnvironmentResolved ( true )
if err != nil {
return err
}
2021-06-07 14:21:55 +02:00
return fn ( ctx , project , args )
2021-06-03 15:30:05 +02:00
} )
}
2024-02-08 21:19:54 +01:00
type jsonErrorData struct {
Error bool ` json:"error,omitempty" `
Message string ` json:"message,omitempty" `
}
func errorAsJSON ( message string ) string {
errorMessage := & jsonErrorData {
Error : true ,
Message : message ,
}
marshal , err := json . Marshal ( errorMessage )
if err == nil {
return string ( marshal )
} else {
return message
}
}
func makeJSONError ( err error ) error {
if err == nil {
return nil
}
var statusErr dockercli . StatusError
if errors . As ( err , & statusErr ) {
return dockercli . StatusError {
StatusCode : statusErr . StatusCode ,
Status : errorAsJSON ( statusErr . Status ) ,
}
}
return fmt . Errorf ( "%s" , errorAsJSON ( err . Error ( ) ) )
}
2022-12-19 16:38:36 +00: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" )
2024-05-16 09:40:07 +02:00
f . StringArrayVar ( & o . EnvFiles , "env-file" , defaultStringArrayVar ( ComposeEnvFiles ) , "Specify an alternate environment file" )
2022-04-28 17:51:35 +02:00
f . StringVar ( & o . ProjectDir , "project-directory" , "" , "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)" )
f . StringVar ( & o . WorkDir , "workdir" , "" , "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)" )
2021-09-22 09:57:31 +02:00
f . BoolVar ( & o . Compatibility , "compatibility" , false , "Run compose in backward compatibility mode" )
2025-06-02 10:40:41 +02:00
f . StringVar ( & o . Progress , "progress" , os . Getenv ( ComposeProgress ) , fmt . Sprintf ( ` Set type of progress output (%s) ` , strings . Join ( printerModes , ", " ) ) )
2024-04-09 11:32:00 +02:00
f . BoolVar ( & o . All , "all-resources" , false , "Include all resources, even those not used by services" )
2021-03-01 12:43:00 -03:00
_ = f . MarkHidden ( "workdir" )
2020-08-18 09:40:57 +02:00
}
2024-05-16 09:40:07 +02:00
// get default value for a command line flag that is set by a coma-separated value in environment variable
func defaultStringArrayVar ( env string ) [ ] string {
return strings . FieldsFunc ( os . Getenv ( env ) , func ( c rune ) bool {
return c == ','
} )
}
2024-02-05 17:38:15 -05:00
func ( o * ProjectOptions ) projectOrName ( ctx context . Context , dockerCli command . Cli , services ... string ) ( * types . Project , string , error ) {
2022-04-11 10:58:20 +02:00
name := o . ProjectName
var project * types . Project
2022-12-14 23:22:01 +01:00
if len ( o . ConfigPaths ) > 0 || o . ProjectName == "" {
2024-02-12 14:52:58 +00:00
p , _ , err := o . ToProject ( ctx , dockerCli , services , cli . WithDiscardEnvFile )
2022-04-11 10:58:20 +02:00
if err != nil {
2023-05-14 16:50:41 +02:00
envProjectName := os . Getenv ( ComposeProjectName )
2022-08-03 14:50:33 +02:00
if envProjectName != "" {
return nil , envProjectName , nil
}
2022-04-11 10:58:20 +02:00
return nil , "" , err
}
project = p
name = p . Name
}
return project , name , nil
}
2024-02-05 17:38:15 -05:00
func ( o * ProjectOptions ) toProjectName ( ctx context . Context , dockerCli command . Cli ) ( 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
}
2023-05-22 19:14:57 +02:00
envProjectName := os . Getenv ( ComposeProjectName )
2022-03-31 14:26:16 +02:00
if envProjectName != "" {
return envProjectName , nil
}
2024-02-12 14:52:58 +00:00
project , _ , err := o . ToProject ( ctx , dockerCli , 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
}
2024-02-28 12:39:24 +01:00
func ( o * ProjectOptions ) ToModel ( ctx context . Context , dockerCli command . Cli , services [ ] string , po ... cli . ProjectOptionsFn ) ( map [ string ] any , error ) {
remotes := o . remoteLoaders ( dockerCli )
for _ , r := range remotes {
po = append ( po , cli . WithResourceLoader ( r ) )
}
options , err := o . toProjectOptions ( po ... )
if err != nil {
return nil , err
}
if o . Compatibility || utils . StringToBool ( options . Environment [ ComposeCompatibility ] ) {
api . Separator = "_"
}
return options . LoadModel ( ctx )
}
2024-04-09 11:32:00 +02:00
func ( o * ProjectOptions ) ToProject ( ctx context . Context , dockerCli command . Cli , services [ ] string , po ... cli . ProjectOptionsFn ) ( * types . Project , tracing . Metrics , error ) { //nolint:gocyclo
2024-02-12 14:52:58 +00:00
var metrics tracing . Metrics
2024-02-15 08:28:36 +01:00
remotes := o . remoteLoaders ( dockerCli )
for _ , r := range remotes {
po = append ( po , cli . WithResourceLoader ( r ) )
2023-08-30 10:15:35 +02:00
}
2021-03-22 08:56:46 +01:00
options , err := o . toProjectOptions ( po ... )
2021-01-14 15:59:57 +01:00
if err != nil {
2025-01-27 13:31:41 +01:00
return nil , metrics , err
2021-01-14 15:59:57 +01:00
}
2020-08-20 16:42:37 +02:00
2024-02-12 14:52:58 +00:00
options . WithListeners ( func ( event string , metadata map [ string ] any ) {
2024-02-15 08:28:36 +01:00
switch event {
case "extends" :
2024-02-12 14:52:58 +00:00
metrics . CountExtends ++
2024-02-15 08:28:36 +01:00
case "include" :
paths := metadata [ "path" ] . ( types . StringList )
for _ , path := range paths {
var isRemote bool
for _ , r := range remotes {
if r . Accept ( path ) {
isRemote = true
break
}
}
if isRemote {
metrics . CountIncludesRemote ++
} else {
metrics . CountIncludesLocal ++
}
}
2024-02-12 14:52:58 +00:00
}
} )
2024-02-15 08:28:36 +01:00
2023-05-14 16:50:41 +02:00
if o . Compatibility || utils . StringToBool ( options . Environment [ ComposeCompatibility ] ) {
2022-07-29 18:55:22 +02:00
api . Separator = "_"
}
2024-02-28 12:39:24 +01:00
project , err := options . LoadProject ( ctx )
2020-08-20 16:42:37 +02:00
if err != nil {
2025-01-27 13:31:41 +01:00
return nil , metrics , err
2020-08-20 16:42:37 +02:00
}
2021-02-02 11:42:19 +01:00
2023-03-01 14:07:15 +01:00
if project . Name == "" {
2024-02-12 14:52:58 +00:00
return nil , metrics , errors . New ( "project name can't be empty. Use `--project-name` to set a valid name" )
2023-03-01 14:07:15 +01:00
}
2023-12-29 11:45:45 +01:00
project , err = project . WithServicesEnabled ( services ... )
2023-03-01 15:05:26 +01:00
if err != nil {
2024-02-12 14:52:58 +00:00
return nil , metrics , err
2023-03-01 15:05:26 +01:00
}
2023-11-27 11:02:19 +01:00
for name , s := range project . Services {
2021-11-24 09:11:27 +01:00
s . CustomLabels = map [ string ] string {
api . ProjectLabel : project . Name ,
2023-11-27 11:02:19 +01:00
api . ServiceLabel : name ,
2021-11-24 09:11:27 +01:00
api . VersionLabel : api . ComposeVersion ,
api . WorkingDirLabel : project . WorkingDir ,
api . ConfigFilesLabel : strings . Join ( project . ComposeFiles , "," ) ,
api . OneoffLabel : "False" , // default, will be overridden by `run` command
}
2023-02-15 14:30:09 +01:00
if len ( o . EnvFiles ) != 0 {
s . CustomLabels [ api . EnvironmentFileLabel ] = strings . Join ( o . EnvFiles , "," )
2021-11-24 09:11:27 +01:00
}
2023-11-27 11:02:19 +01:00
project . Services [ name ] = s
2021-11-24 09:11:27 +01:00
}
2024-07-09 09:33:27 +02:00
project , err = project . WithSelectedServices ( services )
if err != nil {
return nil , tracing . Metrics { } , err
}
2024-04-09 11:32:00 +02:00
if ! o . All {
project = project . WithoutUnnecessaryResources ( )
}
2024-02-12 14:52:58 +00:00
return project , metrics , err
2020-08-20 16:42:37 +02:00
}
2024-02-15 08:28:36 +01:00
func ( o * ProjectOptions ) remoteLoaders ( dockerCli command . Cli ) [ ] loader . ResourceLoader {
if o . Offline {
return nil
}
2025-03-03 10:53:01 +01:00
git := remote . NewGitRemoteLoader ( dockerCli , o . Offline )
2023-10-31 08:38:01 +01:00
oci := remote . NewOCIRemoteLoader ( dockerCli , o . Offline )
2024-02-15 08:28:36 +01:00
return [ ] loader . ResourceLoader { git , oci }
2023-08-30 10:15:35 +02:00
}
2022-12-19 16:38:36 +00:00
func ( o * ProjectOptions ) toProjectOptions ( po ... cli . ProjectOptionsFn ) ( * cli . ProjectOptions , error ) {
2025-03-17 16:39:24 +01:00
pwd , err := os . Getwd ( )
if err != nil {
return nil , err
}
2020-08-18 09:40:57 +02:00
return cli . NewProjectOptions ( o . ConfigPaths ,
2021-03-22 08:56:46 +01:00
append ( po ,
2021-10-22 14:59:25 +02:00
cli . WithWorkingDirectory ( o . ProjectDir ) ,
2024-06-07 11:51:37 +02:00
// First apply os.Environment, always win
2022-05-17 17:06:01 +02:00
cli . WithOsEnv ,
2025-03-17 16:39:24 +01:00
// set PWD as this variable is not consistently supported on Windows
cli . WithEnv ( [ ] string { "PWD=" + pwd } ) ,
2024-06-07 11:51:37 +02:00
// Load PWD/.env if present and no explicit --env-file has been set
cli . WithEnvFiles ( o . EnvFiles ... ) ,
// read dot env file to populate project environment
cli . WithDotEnv ,
// get compose file path set by COMPOSE_FILE
2021-05-20 14:58:57 +02:00
cli . WithConfigFileEnv ,
2024-06-07 11:51:37 +02:00
// if none was selected, get default compose.yaml file from current dir or parent folder
2021-04-26 11:53:34 +02:00
cli . WithDefaultConfigPath ,
2024-06-07 11:51:37 +02:00
// .. and then, a project directory != PWD maybe has been set so let's load .env file
2024-01-25 09:26:22 +01:00
cli . WithEnvFiles ( o . EnvFiles ... ) ,
cli . WithDotEnv ,
2024-06-07 11:51:37 +02:00
// eventually COMPOSE_PROFILES should have been set
2023-08-30 09:17:57 -04:00
cli . WithDefaultProfiles ( o . Profiles ... ) ,
2021-03-22 08:56:46 +01:00
cli . WithName ( o . ProjectName ) ) ... )
2020-08-18 09:40:57 +02:00
}
2021-12-08 18:41:25 +09:00
// PluginName is the name of the plugin
2021-12-02 11:03:38 +01:00
const PluginName = "compose"
2021-09-29 23:45:11 +02:00
// RunningAsStandalone detects when running as a standalone program
func RunningAsStandalone ( ) bool {
2021-12-02 11:03:38 +01:00
return len ( os . Args ) < 2 || os . Args [ 1 ] != manager . MetadataSubcommandName && os . Args [ 1 ] != PluginName
2021-09-29 23:45:11 +02:00
}
2021-04-15 12:43:18 +02:00
// RootCommand returns the compose command with its child commands
2024-03-20 07:44:27 -06:00
func RootCommand ( dockerCli command . Cli , backend Backend ) * cobra . Command { //nolint:gocyclo
2022-08-12 23:49:02 +02:00
// filter out useless commandConn.CloseWrite warning message that can occur
// when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
// https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
logrus . AddHook ( logutil . NewFilter ( [ ] logrus . Level {
logrus . WarnLevel ,
} ,
"commandConn.CloseWrite:" ,
"commandConn.CloseRead:" ,
) )
2024-03-20 07:44:27 -06:00
experiments := experimental . NewState ( )
2022-12-19 16:38:36 +00:00
opts := ProjectOptions { }
2021-06-22 11:44:56 +02:00
var (
2022-11-30 12:08:26 +01:00
ansi string
noAnsi bool
verbose bool
version bool
parallel int
2023-01-11 16:38:57 +01:00
dryRun bool
2021-06-22 11:44:56 +02:00
)
2022-07-13 02:53:08 +02:00
c := & cobra . Command {
2021-02-03 14:02:12 +01:00
Short : "Docker Compose" ,
2024-02-14 20:02:37 +01:00
Long : "Define and run multi-container applications with Docker" ,
2021-12-02 11:03:38 +01:00
Use : PluginName ,
2021-02-03 14:02:12 +01:00
TraverseChildren : true ,
2022-07-13 02:53:08 +02:00
// By default (no Run/RunE in parent c) for typos in subcommands, cobra displays the help of parent c but exit(0) !
2021-04-06 23:19:55 +02:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) == 0 {
return cmd . Help ( )
}
2021-10-03 11:11:36 +02:00
if version {
2023-08-22 09:26:09 +02:00
return versionCommand ( dockerCli ) . Execute ( )
2021-10-03 11:11:36 +02:00
}
2021-04-06 23:19:55 +02:00
_ = cmd . Help ( )
2021-04-15 12:43:18 +02:00
return dockercli . StatusError {
2025-01-27 13:31:41 +01:00
StatusCode : 1 ,
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 {
2024-03-12 09:47:41 -04:00
ctx := cmd . Context ( )
2021-03-04 19:21:42 +01:00
parent := cmd . Root ( )
2024-03-12 09:47:41 -04:00
2021-09-30 09:07:19 +02:00
if parent != nil {
2021-09-29 23:45:11 +02:00
parentPrerun := parent . PersistentPreRunE
if parentPrerun != nil {
err := parentPrerun ( cmd , args )
if err != nil {
return err
}
2021-03-04 19:21:42 +01:00
}
}
2024-03-12 09:47:41 -04:00
if verbose {
logrus . SetLevel ( logrus . TraceLevel )
}
2024-07-08 09:40:04 +02:00
err := setEnvWithDotEnv ( opts )
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"
2022-03-24 15:56:06 +01:00
fmt . Fprint ( os . Stderr , "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n" )
2021-03-19 17:24:56 +01:00
}
2023-05-12 12:48:42 +02:00
if v , ok := os . LookupEnv ( "COMPOSE_ANSI" ) ; ok && ! cmd . Flags ( ) . Changed ( "ansi" ) {
ansi = v
}
2023-08-22 09:26:09 +02:00
formatter . SetANSIMode ( dockerCli , ansi )
2023-04-04 23:00:10 +02:00
if noColor , ok := os . LookupEnv ( "NO_COLOR" ) ; ok && noColor != "" {
2023-06-12 09:27:32 +02:00
ui . NoColor ( )
2023-08-22 09:26:09 +02:00
formatter . SetANSIMode ( dockerCli , formatter . Never )
2023-03-03 10:44:10 +01:00
}
2022-03-08 12:15:59 +01:00
switch ansi {
case "never" :
2023-06-12 09:27:32 +02:00
ui . Mode = ui . ModePlain
2023-04-05 12:44:18 +02:00
case "always" :
2023-06-12 09:27:32 +02:00
ui . Mode = ui . ModeTTY
2022-03-08 12:15:59 +01:00
}
2023-06-12 09:27:32 +02:00
2023-08-30 08:47:09 -04:00
switch opts . Progress {
2025-06-02 10:40:41 +02:00
case "" , ui . ModeAuto :
2024-02-16 18:18:09 +01:00
if ansi == "never" {
ui . Mode = ui . ModePlain
}
2023-06-12 09:27:32 +02:00
case ui . ModeTTY :
if ansi == "never" {
return fmt . Errorf ( "can't use --progress tty while ANSI support is disabled" )
}
ui . Mode = ui . ModeTTY
case ui . ModePlain :
if ansi == "always" {
return fmt . Errorf ( "can't use --progress plain while ANSI support is forced" )
}
ui . Mode = ui . ModePlain
case ui . ModeQuiet , "none" :
ui . Mode = ui . ModeQuiet
2024-02-08 11:16:02 +01:00
case ui . ModeJSON :
ui . Mode = ui . ModeJSON
2024-03-03 09:21:15 +01:00
logrus . SetFormatter ( & logrus . JSONFormatter { } )
2023-06-12 09:27:32 +02:00
default :
2023-08-30 08:47:09 -04:00
return fmt . Errorf ( "unsupported --progress value %q" , opts . Progress )
2023-06-12 09:27:32 +02:00
}
2024-03-12 09:47:41 -04:00
// (4) options validation / normalization
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
}
2023-02-15 14:30:09 +01:00
for i , file := range opts . EnvFiles {
if ! filepath . IsAbs ( file ) {
2024-06-07 11:51:37 +02:00
file , err := filepath . Abs ( file )
2023-02-15 14:30:09 +01:00
if err != nil {
return err
}
opts . EnvFiles [ i ] = file
2022-12-13 14:01:51 +01:00
}
}
2023-05-14 16:50:41 +02:00
composeCmd := cmd
2025-03-31 17:22:58 +02:00
for composeCmd . Name ( ) != PluginName {
2023-05-14 16:50:41 +02:00
if ! composeCmd . HasParent ( ) {
return fmt . Errorf ( "error parsing command line, expected %q" , PluginName )
}
composeCmd = composeCmd . Parent ( )
}
if v , ok := os . LookupEnv ( ComposeParallelLimit ) ; ok && ! composeCmd . Flags ( ) . Changed ( "parallel" ) {
2023-01-04 12:41:36 +01:00
i , err := strconv . Atoi ( v )
if err != nil {
2023-05-14 16:50:41 +02:00
return fmt . Errorf ( "%s must be an integer (found: %q)" , ComposeParallelLimit , v )
2023-01-04 12:41:36 +01:00
}
parallel = i
}
2022-11-30 12:08:26 +01:00
if parallel > 0 {
2024-03-12 09:47:41 -04:00
logrus . Debugf ( "Limiting max concurrency to %d jobs" , parallel )
2022-11-30 12:08:26 +01:00
backend . MaxConcurrency ( parallel )
}
2024-03-12 09:47:41 -04:00
2024-06-07 11:51:37 +02:00
// dry run detection
2024-07-08 09:40:04 +02:00
ctx , err = backend . DryRunMode ( ctx , dryRun )
2023-01-27 16:43:48 +01:00
if err != nil {
return err
}
cmd . SetContext ( ctx )
2024-03-12 09:47:41 -04:00
// (6) Desktop integration
2024-03-20 07:44:27 -06:00
var desktopCli * desktop . Client
if ! dryRun {
if desktopCli , err = desktop . NewFromDockerClient ( ctx , dockerCli ) ; desktopCli != nil {
logrus . Debugf ( "Enabled Docker Desktop integration (experimental) @ %s" , desktopCli . Endpoint ( ) )
backend . SetDesktopClient ( desktopCli )
} else if err != nil {
2024-03-12 09:47:41 -04:00
// not fatal, Compose will still work but behave as though
// it's not running as part of Docker Desktop
logrus . Debugf ( "failed to enable Docker Desktop integration: %v" , err )
2024-03-20 07:44:27 -06:00
} else {
logrus . Trace ( "Docker Desktop integration not enabled" )
2024-03-12 09:47:41 -04:00
}
}
2024-03-20 07:44:27 -06:00
// (7) experimental features
if err := experiments . Load ( ctx , desktopCli ) ; err != nil {
logrus . Debugf ( "Failed to query feature flags from Desktop: %v" , err )
}
backend . SetExperiments ( experiments )
2023-01-27 16:43:48 +01:00
return nil
2020-12-07 15:11:22 +01:00
} ,
2020-05-05 10:58:24 +02:00
}
2020-05-14 21:04:38 +02:00
2022-07-13 02:53:08 +02:00
c . AddCommand (
2024-10-07 15:37:22 +01:00
upCommand ( & opts , dockerCli , backend ) ,
2023-08-30 10:15:35 +02:00
downCommand ( & opts , dockerCli , backend ) ,
startCommand ( & opts , dockerCli , backend ) ,
restartCommand ( & opts , dockerCli , backend ) ,
stopCommand ( & opts , dockerCli , backend ) ,
2023-08-22 09:26:09 +02:00
psCommand ( & opts , dockerCli , backend ) ,
listCommand ( dockerCli , backend ) ,
logsCommand ( & opts , dockerCli , backend ) ,
2024-02-28 12:39:24 +01:00
configCommand ( & opts , dockerCli ) ,
2023-08-30 10:15:35 +02:00
killCommand ( & opts , dockerCli , backend ) ,
2023-08-22 09:26:09 +02:00
runCommand ( & opts , dockerCli , backend ) ,
2023-08-30 10:15:35 +02:00
removeCommand ( & opts , dockerCli , backend ) ,
2023-08-22 09:26:09 +02:00
execCommand ( & opts , dockerCli , backend ) ,
2023-12-04 09:47:53 -06:00
attachCommand ( & opts , dockerCli , backend ) ,
2024-09-17 22:13:13 +03:30
exportCommand ( & opts , dockerCli , backend ) ,
2024-11-05 22:04:30 +03:30
commitCommand ( & opts , dockerCli , backend ) ,
2023-08-30 10:15:35 +02:00
pauseCommand ( & opts , dockerCli , backend ) ,
unpauseCommand ( & opts , dockerCli , backend ) ,
2023-08-22 09:26:09 +02:00
topCommand ( & opts , dockerCli , backend ) ,
eventsCommand ( & opts , dockerCli , backend ) ,
portCommand ( & opts , dockerCli , backend ) ,
imagesCommand ( & opts , dockerCli , backend ) ,
versionCommand ( dockerCli ) ,
2023-08-30 10:15:35 +02:00
buildCommand ( & opts , dockerCli , backend ) ,
pushCommand ( & opts , dockerCli , backend ) ,
pullCommand ( & opts , dockerCli , backend ) ,
createCommand ( & opts , dockerCli , backend ) ,
copyCommand ( & opts , dockerCli , backend ) ,
waitCommand ( & opts , dockerCli , backend ) ,
2023-09-06 22:53:01 +02:00
scaleCommand ( & opts , dockerCli , backend ) ,
2023-12-22 13:59:37 +01:00
statsCommand ( & opts , dockerCli ) ,
2023-09-19 16:59:26 +02:00
watchCommand ( & opts , dockerCli , backend ) ,
2025-03-12 11:15:44 +01:00
publishCommand ( & opts , dockerCli , backend ) ,
2023-08-30 10:15:35 +02:00
alphaCommand ( & opts , dockerCli , backend ) ,
2025-05-23 10:04:57 +02:00
bridgeCommand ( & opts , dockerCli ) ,
2020-05-05 10:58:24 +02:00
)
2023-01-11 11:52:19 +01:00
2022-07-13 02:53:08 +02:00
c . Flags ( ) . SetInterspersed ( false )
opts . addProjectFlags ( c . Flags ( ) )
2022-09-26 19:21:45 +02:00
c . RegisterFlagCompletionFunc ( //nolint:errcheck
"project-name" ,
completeProjectNames ( backend ) ,
)
2023-08-03 23:37:46 +03:00
c . RegisterFlagCompletionFunc ( //nolint:errcheck
"project-directory" ,
func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return [ ] string { } , cobra . ShellCompDirectiveFilterDirs
} ,
)
2022-09-26 19:21:45 +02:00
c . RegisterFlagCompletionFunc ( //nolint:errcheck
"file" ,
func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
return [ ] string { "yaml" , "yml" } , cobra . ShellCompDirectiveFilterFileExt
} ,
)
2023-08-03 23:02:48 +03:00
c . RegisterFlagCompletionFunc ( //nolint:errcheck
"profile" ,
2023-08-30 10:15:35 +02:00
completeProfileNames ( dockerCli , & opts ) ,
2023-08-03 23:02:48 +03:00
)
2022-09-26 19:21:45 +02:00
2022-07-13 02:53:08 +02:00
c . Flags ( ) . StringVar ( & ansi , "ansi" , "auto" , ` Control when to print ANSI control characters ("never"|"always"|"auto") ` )
2022-11-30 12:08:26 +01:00
c . Flags ( ) . IntVar ( & parallel , "parallel" , - 1 , ` Control max parallelism, -1 for unlimited ` )
2022-07-13 02:53:08 +02:00
c . Flags ( ) . BoolVarP ( & version , "version" , "v" , false , "Show the Docker Compose version information" )
2023-05-05 10:40:18 +02:00
c . PersistentFlags ( ) . BoolVar ( & dryRun , "dry-run" , false , "Execute command in dry run mode" )
2022-07-13 02:53:08 +02:00
c . Flags ( ) . MarkHidden ( "version" ) //nolint:errcheck
c . Flags ( ) . BoolVar ( & noAnsi , "no-ansi" , false , ` Do not print ANSI control characters (DEPRECATED) ` )
c . Flags ( ) . MarkHidden ( "no-ansi" ) //nolint:errcheck
c . Flags ( ) . BoolVar ( & verbose , "verbose" , false , "Show more output" )
c . Flags ( ) . MarkHidden ( "verbose" ) //nolint:errcheck
return c
2020-05-05 15:37:12 +02:00
}
2022-05-30 17:30:30 +02:00
2024-07-08 09:40:04 +02:00
func setEnvWithDotEnv ( opts ProjectOptions ) error {
options , err := cli . NewProjectOptions ( opts . ConfigPaths ,
cli . WithWorkingDirectory ( opts . ProjectDir ) ,
cli . WithOsEnv ,
cli . WithEnvFiles ( opts . EnvFiles ... ) ,
cli . WithDotEnv ,
)
if err != nil {
return nil
}
envFromFile , err := dotenv . GetEnvFromFile ( composegoutils . GetAsEqualsMap ( os . Environ ( ) ) , options . EnvFiles )
if err != nil {
return nil
}
for k , v := range envFromFile {
if _ , ok := os . LookupEnv ( k ) ; ! ok {
if err = os . Setenv ( k , v ) ; err != nil {
return nil
}
}
}
return err
}
2023-06-12 09:27:32 +02:00
var printerModes = [ ] string {
ui . ModeAuto ,
ui . ModeTTY ,
ui . ModePlain ,
2024-02-08 11:16:02 +01:00
ui . ModeJSON ,
2023-06-12 09:27:32 +02:00
ui . ModeQuiet ,
}
2024-03-08 14:07:51 +00:00
func SetUnchangedOption ( name string , experimentalFlag bool ) bool {
var value bool
// If the var is defined we use that value first
if envVar , ok := os . LookupEnv ( name ) ; ok {
value = utils . StringToBool ( envVar )
} else {
// if not, we try to get it from experimental feature flag
value = experimentalFlag
}
return value
}