up.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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. "errors"
  17. "fmt"
  18. "os"
  19. "path/filepath"
  20. "github.com/docker/compose-cli/api/client"
  21. "github.com/docker/compose-cli/api/compose"
  22. "github.com/docker/compose-cli/api/context/store"
  23. "github.com/docker/compose-cli/api/progress"
  24. "github.com/docker/compose-cli/cli/formatter"
  25. "github.com/compose-spec/compose-go/types"
  26. "github.com/spf13/cobra"
  27. )
  28. // composeOptions hold options common to `up` and `run` to run compose project
  29. type composeOptions struct {
  30. *projectOptions
  31. Build bool
  32. // ACI only
  33. DomainName string
  34. }
  35. type upOptions struct {
  36. *composeOptions
  37. Detach bool
  38. Environment []string
  39. removeOrphans bool
  40. forceRecreate bool
  41. noRecreate bool
  42. noStart bool
  43. }
  44. func (o upOptions) recreateStrategy() string {
  45. if o.noRecreate {
  46. return compose.RecreateNever
  47. }
  48. if o.forceRecreate {
  49. return compose.RecreateForce
  50. }
  51. return compose.RecreateDiverged
  52. }
  53. func upCommand(p *projectOptions, contextType string) *cobra.Command {
  54. opts := upOptions{
  55. composeOptions: &composeOptions{
  56. projectOptions: p,
  57. },
  58. }
  59. upCmd := &cobra.Command{
  60. Use: "up [SERVICE...]",
  61. Short: "Create and start containers",
  62. RunE: func(cmd *cobra.Command, args []string) error {
  63. switch contextType {
  64. case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
  65. if opts.forceRecreate && opts.noRecreate {
  66. return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
  67. }
  68. return runCreateStart(cmd.Context(), opts, args)
  69. default:
  70. return runUp(cmd.Context(), opts, args)
  71. }
  72. },
  73. }
  74. flags := upCmd.Flags()
  75. flags.StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
  76. flags.BoolVarP(&opts.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
  77. flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
  78. flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
  79. switch contextType {
  80. case store.AciContextType:
  81. flags.StringVar(&opts.DomainName, "domainname", "", "Container NIS domain name")
  82. case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
  83. flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
  84. flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
  85. flags.BoolVar(&opts.noStart, "no-start", false, "Don't start the services after creating them.")
  86. }
  87. return upCmd
  88. }
  89. func runUp(ctx context.Context, opts upOptions, services []string) error {
  90. c, project, err := setup(ctx, *opts.composeOptions, services)
  91. if err != nil {
  92. return err
  93. }
  94. _, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
  95. return "", c.ComposeService().Up(ctx, project, compose.UpOptions{
  96. Detach: opts.Detach,
  97. })
  98. })
  99. return err
  100. }
  101. func runCreateStart(ctx context.Context, opts upOptions, services []string) error {
  102. c, project, err := setup(ctx, *opts.composeOptions, services)
  103. if err != nil {
  104. return err
  105. }
  106. _, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
  107. err := c.ComposeService().Create(ctx, project, compose.CreateOptions{
  108. RemoveOrphans: opts.removeOrphans,
  109. Recreate: opts.recreateStrategy(),
  110. })
  111. if err != nil {
  112. return "", err
  113. }
  114. if opts.Detach {
  115. err = c.ComposeService().Start(ctx, project, nil)
  116. }
  117. return "", err
  118. })
  119. if err != nil {
  120. return err
  121. }
  122. if opts.noStart {
  123. return nil
  124. }
  125. if opts.Detach {
  126. return nil
  127. }
  128. err = c.ComposeService().Start(ctx, project, formatter.NewLogConsumer(ctx, os.Stdout))
  129. if errors.Is(ctx.Err(), context.Canceled) {
  130. fmt.Println("Gracefully stopping...")
  131. ctx = context.Background()
  132. _, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
  133. return "", c.ComposeService().Stop(ctx, project)
  134. })
  135. }
  136. return err
  137. }
  138. func setup(ctx context.Context, opts composeOptions, services []string) (*client.Client, *types.Project, error) {
  139. c, err := client.NewWithDefaultLocalBackend(ctx)
  140. if err != nil {
  141. return nil, nil, err
  142. }
  143. project, err := opts.toProject(services)
  144. if err != nil {
  145. return nil, nil, err
  146. }
  147. if opts.DomainName != "" {
  148. // arbitrarily set the domain name on the first service ; ACI backend will expose the entire project
  149. project.Services[0].DomainName = opts.DomainName
  150. }
  151. if opts.Build {
  152. for _, service := range project.Services {
  153. service.PullPolicy = types.PullPolicyBuild
  154. }
  155. }
  156. if opts.EnvFile != "" {
  157. var services types.Services
  158. for _, s := range project.Services {
  159. ef := opts.EnvFile
  160. if ef != "" {
  161. if !filepath.IsAbs(ef) {
  162. ef = filepath.Join(project.WorkingDir, opts.EnvFile)
  163. }
  164. if s.Labels == nil {
  165. s.Labels = make(map[string]string)
  166. }
  167. s.Labels[compose.EnvironmentFileLabel] = ef
  168. services = append(services, s)
  169. }
  170. }
  171. project.Services = services
  172. }
  173. return c, project, nil
  174. }