compose.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package compose
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "io"
  20. "os"
  21. "os/signal"
  22. "path/filepath"
  23. "strconv"
  24. "strings"
  25. "syscall"
  26. "github.com/compose-spec/compose-go/v2/cli"
  27. "github.com/compose-spec/compose-go/v2/dotenv"
  28. "github.com/compose-spec/compose-go/v2/loader"
  29. "github.com/compose-spec/compose-go/v2/types"
  30. composegoutils "github.com/compose-spec/compose-go/v2/utils"
  31. "github.com/docker/buildx/util/logutil"
  32. dockercli "github.com/docker/cli/cli"
  33. "github.com/docker/cli/cli-plugins/metadata"
  34. "github.com/docker/cli/cli/command"
  35. "github.com/docker/cli/pkg/kvfile"
  36. "github.com/docker/compose/v2/cmd/formatter"
  37. "github.com/docker/compose/v2/internal/tracing"
  38. "github.com/docker/compose/v2/pkg/api"
  39. ui "github.com/docker/compose/v2/pkg/progress"
  40. "github.com/docker/compose/v2/pkg/remote"
  41. "github.com/docker/compose/v2/pkg/utils"
  42. "github.com/morikuni/aec"
  43. "github.com/sirupsen/logrus"
  44. "github.com/spf13/cobra"
  45. "github.com/spf13/pflag"
  46. )
  47. const (
  48. // ComposeParallelLimit set the limit running concurrent operation on docker engine
  49. ComposeParallelLimit = "COMPOSE_PARALLEL_LIMIT"
  50. // ComposeProjectName define the project name to be used, instead of guessing from parent directory
  51. ComposeProjectName = "COMPOSE_PROJECT_NAME"
  52. // ComposeCompatibility try to mimic compose v1 as much as possible
  53. ComposeCompatibility = "COMPOSE_COMPATIBILITY"
  54. // ComposeRemoveOrphans remove "orphaned" containers, i.e. containers tagged for current project but not declared as service
  55. ComposeRemoveOrphans = "COMPOSE_REMOVE_ORPHANS"
  56. // ComposeIgnoreOrphans ignore "orphaned" containers
  57. ComposeIgnoreOrphans = "COMPOSE_IGNORE_ORPHANS"
  58. // ComposeEnvFiles defines the env files to use if --env-file isn't used
  59. ComposeEnvFiles = "COMPOSE_ENV_FILES"
  60. // ComposeMenu defines if the navigation menu should be rendered. Can be also set via --menu
  61. ComposeMenu = "COMPOSE_MENU"
  62. // ComposeProgress defines type of progress output, if --progress isn't used
  63. ComposeProgress = "COMPOSE_PROGRESS"
  64. )
  65. // rawEnv load a dot env file using docker/cli key=value parser, without attempt to interpolate or evaluate values
  66. func rawEnv(r io.Reader, filename string, vars map[string]string, lookup func(key string) (string, bool)) error {
  67. lines, err := kvfile.ParseFromReader(r, lookup)
  68. if err != nil {
  69. return fmt.Errorf("failed to parse env_file %s: %w", filename, err)
  70. }
  71. for _, line := range lines {
  72. key, value, _ := strings.Cut(line, "=")
  73. vars[key] = value
  74. }
  75. return nil
  76. }
  77. func init() {
  78. // compose evaluates env file values for interpolation
  79. // `raw` format allows to load env_file with the same parser used by docker run --env-file
  80. dotenv.RegisterFormat("raw", rawEnv)
  81. }
  82. // Command defines a compose CLI command as a func with args
  83. type Command func(context.Context, []string) error
  84. // CobraCommand defines a cobra command function
  85. type CobraCommand func(context.Context, *cobra.Command, []string) error
  86. // AdaptCmd adapt a CobraCommand func to cobra library
  87. func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
  88. return func(cmd *cobra.Command, args []string) error {
  89. ctx, cancel := context.WithCancel(cmd.Context())
  90. s := make(chan os.Signal, 1)
  91. signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
  92. go func() {
  93. <-s
  94. cancel()
  95. signal.Stop(s)
  96. close(s)
  97. }()
  98. err := fn(ctx, cmd, args)
  99. if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
  100. err = dockercli.StatusError{
  101. StatusCode: 130,
  102. }
  103. }
  104. if ui.Mode == ui.ModeJSON {
  105. err = makeJSONError(err)
  106. }
  107. return err
  108. }
  109. }
  110. // Adapt a Command func to cobra library
  111. func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
  112. return AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
  113. return fn(ctx, args)
  114. })
  115. }
  116. type ProjectOptions struct {
  117. ProjectName string
  118. Profiles []string
  119. ConfigPaths []string
  120. WorkDir string
  121. ProjectDir string
  122. EnvFiles []string
  123. Compatibility bool
  124. Progress string
  125. Offline bool
  126. All bool
  127. }
  128. // ProjectFunc does stuff within a types.Project
  129. type ProjectFunc func(ctx context.Context, project *types.Project) error
  130. // ProjectServicesFunc does stuff within a types.Project and a selection of services
  131. type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error
  132. // WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
  133. func (o *ProjectOptions) WithProject(fn ProjectFunc, dockerCli command.Cli) func(cmd *cobra.Command, args []string) error {
  134. return o.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
  135. return fn(ctx, project)
  136. })
  137. }
  138. // WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
  139. func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
  140. return Adapt(func(ctx context.Context, args []string) error {
  141. options := []cli.ProjectOptionsFn{
  142. cli.WithResolvedPaths(true),
  143. cli.WithoutEnvironmentResolution,
  144. }
  145. project, metrics, err := o.ToProject(ctx, dockerCli, args, options...)
  146. if err != nil {
  147. return err
  148. }
  149. ctx = context.WithValue(ctx, tracing.MetricsKey{}, metrics)
  150. project, err = project.WithServicesEnvironmentResolved(true)
  151. if err != nil {
  152. return err
  153. }
  154. return fn(ctx, project, args)
  155. })
  156. }
  157. type jsonErrorData struct {
  158. Error bool `json:"error,omitempty"`
  159. Message string `json:"message,omitempty"`
  160. }
  161. func errorAsJSON(message string) string {
  162. errorMessage := &jsonErrorData{
  163. Error: true,
  164. Message: message,
  165. }
  166. marshal, err := json.Marshal(errorMessage)
  167. if err == nil {
  168. return string(marshal)
  169. } else {
  170. return message
  171. }
  172. }
  173. func makeJSONError(err error) error {
  174. if err == nil {
  175. return nil
  176. }
  177. var statusErr dockercli.StatusError
  178. if errors.As(err, &statusErr) {
  179. return dockercli.StatusError{
  180. StatusCode: statusErr.StatusCode,
  181. Status: errorAsJSON(statusErr.Status),
  182. }
  183. }
  184. return fmt.Errorf("%s", errorAsJSON(err.Error()))
  185. }
  186. func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
  187. f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
  188. f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
  189. f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
  190. f.StringArrayVar(&o.EnvFiles, "env-file", defaultStringArrayVar(ComposeEnvFiles), "Specify an alternate environment file")
  191. f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
  192. 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)")
  193. f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
  194. f.StringVar(&o.Progress, "progress", os.Getenv(ComposeProgress), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
  195. f.BoolVar(&o.All, "all-resources", false, "Include all resources, even those not used by services")
  196. _ = f.MarkHidden("workdir")
  197. }
  198. // get default value for a command line flag that is set by a coma-separated value in environment variable
  199. func defaultStringArrayVar(env string) []string {
  200. return strings.FieldsFunc(os.Getenv(env), func(c rune) bool {
  201. return c == ','
  202. })
  203. }
  204. func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cli, services ...string) (*types.Project, string, error) {
  205. name := o.ProjectName
  206. var project *types.Project
  207. if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
  208. p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile, cli.WithoutEnvironmentResolution)
  209. if err != nil {
  210. envProjectName := os.Getenv(ComposeProjectName)
  211. if envProjectName != "" {
  212. return nil, envProjectName, nil
  213. }
  214. return nil, "", err
  215. }
  216. project = p
  217. name = p.Name
  218. }
  219. return project, name, nil
  220. }
  221. func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cli) (string, error) {
  222. if o.ProjectName != "" {
  223. return o.ProjectName, nil
  224. }
  225. envProjectName := os.Getenv(ComposeProjectName)
  226. if envProjectName != "" {
  227. return envProjectName, nil
  228. }
  229. project, _, err := o.ToProject(ctx, dockerCli, nil)
  230. if err != nil {
  231. return "", err
  232. }
  233. return project.Name, nil
  234. }
  235. func (o *ProjectOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
  236. remotes := o.remoteLoaders(dockerCli)
  237. for _, r := range remotes {
  238. po = append(po, cli.WithResourceLoader(r))
  239. }
  240. options, err := o.toProjectOptions(po...)
  241. if err != nil {
  242. return nil, err
  243. }
  244. if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
  245. api.Separator = "_"
  246. }
  247. return options.LoadModel(ctx)
  248. }
  249. func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { //nolint:gocyclo
  250. var metrics tracing.Metrics
  251. remotes := o.remoteLoaders(dockerCli)
  252. for _, r := range remotes {
  253. po = append(po, cli.WithResourceLoader(r))
  254. }
  255. options, err := o.toProjectOptions(po...)
  256. if err != nil {
  257. return nil, metrics, err
  258. }
  259. options.WithListeners(func(event string, metadata map[string]any) {
  260. switch event {
  261. case "extends":
  262. metrics.CountExtends++
  263. case "include":
  264. paths := metadata["path"].(types.StringList)
  265. for _, path := range paths {
  266. var isRemote bool
  267. for _, r := range remotes {
  268. if r.Accept(path) {
  269. isRemote = true
  270. break
  271. }
  272. }
  273. if isRemote {
  274. metrics.CountIncludesRemote++
  275. } else {
  276. metrics.CountIncludesLocal++
  277. }
  278. }
  279. }
  280. })
  281. if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
  282. api.Separator = "_"
  283. }
  284. project, err := options.LoadProject(ctx)
  285. if err != nil {
  286. return nil, metrics, err
  287. }
  288. if project.Name == "" {
  289. return nil, metrics, errors.New("project name can't be empty. Use `--project-name` to set a valid name")
  290. }
  291. project, err = project.WithServicesEnabled(services...)
  292. if err != nil {
  293. return nil, metrics, err
  294. }
  295. for name, s := range project.Services {
  296. s.CustomLabels = map[string]string{
  297. api.ProjectLabel: project.Name,
  298. api.ServiceLabel: name,
  299. api.VersionLabel: api.ComposeVersion,
  300. api.WorkingDirLabel: project.WorkingDir,
  301. api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
  302. api.OneoffLabel: "False", // default, will be overridden by `run` command
  303. }
  304. if len(o.EnvFiles) != 0 {
  305. s.CustomLabels[api.EnvironmentFileLabel] = strings.Join(o.EnvFiles, ",")
  306. }
  307. project.Services[name] = s
  308. }
  309. project, err = project.WithSelectedServices(services)
  310. if err != nil {
  311. return nil, tracing.Metrics{}, err
  312. }
  313. if !o.All {
  314. project = project.WithoutUnnecessaryResources()
  315. }
  316. return project, metrics, err
  317. }
  318. func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceLoader {
  319. if o.Offline {
  320. return nil
  321. }
  322. git := remote.NewGitRemoteLoader(dockerCli, o.Offline)
  323. oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
  324. return []loader.ResourceLoader{git, oci}
  325. }
  326. func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
  327. opts := []cli.ProjectOptionsFn{
  328. cli.WithWorkingDirectory(o.ProjectDir),
  329. // First apply os.Environment, always win
  330. cli.WithOsEnv,
  331. }
  332. if _, present := os.LookupEnv("PWD"); !present {
  333. if pwd, err := os.Getwd(); err != nil {
  334. return nil, err
  335. } else {
  336. opts = append(opts, cli.WithEnv([]string{"PWD=" + pwd}))
  337. }
  338. }
  339. opts = append(opts,
  340. // Load PWD/.env if present and no explicit --env-file has been set
  341. cli.WithEnvFiles(o.EnvFiles...),
  342. // read dot env file to populate project environment
  343. cli.WithDotEnv,
  344. // get compose file path set by COMPOSE_FILE
  345. cli.WithConfigFileEnv,
  346. // if none was selected, get default compose.yaml file from current dir or parent folder
  347. cli.WithDefaultConfigPath,
  348. // .. and then, a project directory != PWD maybe has been set so let's load .env file
  349. cli.WithEnvFiles(o.EnvFiles...),
  350. cli.WithDotEnv,
  351. // eventually COMPOSE_PROFILES should have been set
  352. cli.WithDefaultProfiles(o.Profiles...),
  353. cli.WithName(o.ProjectName),
  354. )
  355. return cli.NewProjectOptions(o.ConfigPaths, append(po, opts...)...)
  356. }
  357. // PluginName is the name of the plugin
  358. const PluginName = "compose"
  359. // RunningAsStandalone detects when running as a standalone program
  360. func RunningAsStandalone() bool {
  361. return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName && os.Args[1] != PluginName
  362. }
  363. // RootCommand returns the compose command with its child commands
  364. func RootCommand(dockerCli command.Cli, backend api.Compose) *cobra.Command { //nolint:gocyclo
  365. // filter out useless commandConn.CloseWrite warning message that can occur
  366. // when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
  367. // https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
  368. logrus.AddHook(logutil.NewFilter([]logrus.Level{
  369. logrus.WarnLevel,
  370. },
  371. "commandConn.CloseWrite:",
  372. "commandConn.CloseRead:",
  373. ))
  374. opts := ProjectOptions{}
  375. var (
  376. ansi string
  377. noAnsi bool
  378. verbose bool
  379. version bool
  380. parallel int
  381. dryRun bool
  382. )
  383. c := &cobra.Command{
  384. Short: "Docker Compose",
  385. Long: "Define and run multi-container applications with Docker",
  386. Use: PluginName,
  387. TraverseChildren: true,
  388. // By default (no Run/RunE in parent c) for typos in subcommands, cobra displays the help of parent c but exit(0) !
  389. RunE: func(cmd *cobra.Command, args []string) error {
  390. if len(args) == 0 {
  391. return cmd.Help()
  392. }
  393. if version {
  394. return versionCommand(dockerCli).Execute()
  395. }
  396. _ = cmd.Help()
  397. return dockercli.StatusError{
  398. StatusCode: 1,
  399. Status: fmt.Sprintf("unknown docker command: %q", "compose "+args[0]),
  400. }
  401. },
  402. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  403. ctx := cmd.Context()
  404. parent := cmd.Root()
  405. if parent != nil {
  406. parentPrerun := parent.PersistentPreRunE
  407. if parentPrerun != nil {
  408. err := parentPrerun(cmd, args)
  409. if err != nil {
  410. return err
  411. }
  412. }
  413. }
  414. if verbose {
  415. logrus.SetLevel(logrus.TraceLevel)
  416. }
  417. err := setEnvWithDotEnv(opts)
  418. if err != nil {
  419. return err
  420. }
  421. if noAnsi {
  422. if ansi != "auto" {
  423. return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
  424. }
  425. ansi = "never"
  426. fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n")
  427. }
  428. if v, ok := os.LookupEnv("COMPOSE_ANSI"); ok && !cmd.Flags().Changed("ansi") {
  429. ansi = v
  430. }
  431. formatter.SetANSIMode(dockerCli, ansi)
  432. if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
  433. ui.NoColor()
  434. formatter.SetANSIMode(dockerCli, formatter.Never)
  435. }
  436. switch ansi {
  437. case "never":
  438. ui.Mode = ui.ModePlain
  439. case "always":
  440. ui.Mode = ui.ModeTTY
  441. }
  442. switch opts.Progress {
  443. case "", ui.ModeAuto:
  444. if ansi == "never" {
  445. ui.Mode = ui.ModePlain
  446. }
  447. case ui.ModeTTY:
  448. if ansi == "never" {
  449. return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
  450. }
  451. ui.Mode = ui.ModeTTY
  452. case ui.ModePlain:
  453. if ansi == "always" {
  454. return fmt.Errorf("can't use --progress plain while ANSI support is forced")
  455. }
  456. ui.Mode = ui.ModePlain
  457. case ui.ModeQuiet, "none":
  458. ui.Mode = ui.ModeQuiet
  459. case ui.ModeJSON:
  460. ui.Mode = ui.ModeJSON
  461. logrus.SetFormatter(&logrus.JSONFormatter{})
  462. default:
  463. return fmt.Errorf("unsupported --progress value %q", opts.Progress)
  464. }
  465. // (4) options validation / normalization
  466. if opts.WorkDir != "" {
  467. if opts.ProjectDir != "" {
  468. return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
  469. }
  470. opts.ProjectDir = opts.WorkDir
  471. fmt.Fprint(os.Stderr, aec.Apply("option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n", aec.RedF))
  472. }
  473. for i, file := range opts.EnvFiles {
  474. if !filepath.IsAbs(file) {
  475. file, err := filepath.Abs(file)
  476. if err != nil {
  477. return err
  478. }
  479. opts.EnvFiles[i] = file
  480. }
  481. }
  482. composeCmd := cmd
  483. for composeCmd.Name() != PluginName {
  484. if !composeCmd.HasParent() {
  485. return fmt.Errorf("error parsing command line, expected %q", PluginName)
  486. }
  487. composeCmd = composeCmd.Parent()
  488. }
  489. if v, ok := os.LookupEnv(ComposeParallelLimit); ok && !composeCmd.Flags().Changed("parallel") {
  490. i, err := strconv.Atoi(v)
  491. if err != nil {
  492. return fmt.Errorf("%s must be an integer (found: %q)", ComposeParallelLimit, v)
  493. }
  494. parallel = i
  495. }
  496. if parallel > 0 {
  497. logrus.Debugf("Limiting max concurrency to %d jobs", parallel)
  498. backend.MaxConcurrency(parallel)
  499. }
  500. // dry run detection
  501. ctx, err = backend.DryRunMode(ctx, dryRun)
  502. if err != nil {
  503. return err
  504. }
  505. cmd.SetContext(ctx)
  506. return nil
  507. },
  508. }
  509. c.AddCommand(
  510. upCommand(&opts, dockerCli, backend),
  511. downCommand(&opts, dockerCli, backend),
  512. startCommand(&opts, dockerCli, backend),
  513. restartCommand(&opts, dockerCli, backend),
  514. stopCommand(&opts, dockerCli, backend),
  515. psCommand(&opts, dockerCli, backend),
  516. listCommand(dockerCli, backend),
  517. logsCommand(&opts, dockerCli, backend),
  518. configCommand(&opts, dockerCli),
  519. killCommand(&opts, dockerCli, backend),
  520. runCommand(&opts, dockerCli, backend),
  521. removeCommand(&opts, dockerCli, backend),
  522. execCommand(&opts, dockerCli, backend),
  523. attachCommand(&opts, dockerCli, backend),
  524. exportCommand(&opts, dockerCli, backend),
  525. commitCommand(&opts, dockerCli, backend),
  526. pauseCommand(&opts, dockerCli, backend),
  527. unpauseCommand(&opts, dockerCli, backend),
  528. topCommand(&opts, dockerCli, backend),
  529. eventsCommand(&opts, dockerCli, backend),
  530. portCommand(&opts, dockerCli, backend),
  531. imagesCommand(&opts, dockerCli, backend),
  532. versionCommand(dockerCli),
  533. buildCommand(&opts, dockerCli, backend),
  534. pushCommand(&opts, dockerCli, backend),
  535. pullCommand(&opts, dockerCli, backend),
  536. createCommand(&opts, dockerCli, backend),
  537. copyCommand(&opts, dockerCli, backend),
  538. waitCommand(&opts, dockerCli, backend),
  539. scaleCommand(&opts, dockerCli, backend),
  540. statsCommand(&opts, dockerCli),
  541. watchCommand(&opts, dockerCli, backend),
  542. publishCommand(&opts, dockerCli, backend),
  543. alphaCommand(&opts, dockerCli, backend),
  544. bridgeCommand(&opts, dockerCli),
  545. volumesCommand(&opts, dockerCli, backend),
  546. )
  547. c.Flags().SetInterspersed(false)
  548. opts.addProjectFlags(c.Flags())
  549. c.RegisterFlagCompletionFunc( //nolint:errcheck
  550. "project-name",
  551. completeProjectNames(backend),
  552. )
  553. c.RegisterFlagCompletionFunc( //nolint:errcheck
  554. "project-directory",
  555. func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
  556. return []string{}, cobra.ShellCompDirectiveFilterDirs
  557. },
  558. )
  559. c.RegisterFlagCompletionFunc( //nolint:errcheck
  560. "file",
  561. func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
  562. return []string{"yaml", "yml"}, cobra.ShellCompDirectiveFilterFileExt
  563. },
  564. )
  565. c.RegisterFlagCompletionFunc( //nolint:errcheck
  566. "profile",
  567. completeProfileNames(dockerCli, &opts),
  568. )
  569. c.RegisterFlagCompletionFunc( //nolint:errcheck
  570. "progress",
  571. cobra.FixedCompletions(printerModes, cobra.ShellCompDirectiveNoFileComp),
  572. )
  573. c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
  574. c.Flags().IntVar(&parallel, "parallel", -1, `Control max parallelism, -1 for unlimited`)
  575. c.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
  576. c.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Execute command in dry run mode")
  577. c.Flags().MarkHidden("version") //nolint:errcheck
  578. c.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
  579. c.Flags().MarkHidden("no-ansi") //nolint:errcheck
  580. c.Flags().BoolVar(&verbose, "verbose", false, "Show more output")
  581. c.Flags().MarkHidden("verbose") //nolint:errcheck
  582. return c
  583. }
  584. func setEnvWithDotEnv(opts ProjectOptions) error {
  585. options, err := cli.NewProjectOptions(opts.ConfigPaths,
  586. cli.WithWorkingDirectory(opts.ProjectDir),
  587. cli.WithOsEnv,
  588. cli.WithEnvFiles(opts.EnvFiles...),
  589. cli.WithDotEnv,
  590. )
  591. if err != nil {
  592. return nil
  593. }
  594. envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), options.EnvFiles)
  595. if err != nil {
  596. return nil
  597. }
  598. for k, v := range envFromFile {
  599. if _, ok := os.LookupEnv(k); !ok && strings.HasPrefix(k, "COMPOSE_") {
  600. if err = os.Setenv(k, v); err != nil {
  601. return nil
  602. }
  603. }
  604. }
  605. return err
  606. }
  607. var printerModes = []string{
  608. ui.ModeAuto,
  609. ui.ModeTTY,
  610. ui.ModePlain,
  611. ui.ModeJSON,
  612. ui.ModeQuiet,
  613. }