@@ -18,18 +18,21 @@ package compose
1818
1919import (
2020 "context"
21- "errors"
2221 "fmt"
2322 "os"
23+ "os/signal"
2424 "path/filepath"
25+ "syscall"
2526
2627 "github.com/docker/compose-cli/api/client"
2728 "github.com/docker/compose-cli/api/compose"
2829 "github.com/docker/compose-cli/api/context/store"
2930 "github.com/docker/compose-cli/api/progress"
31+ "github.com/docker/compose-cli/cli/cmd"
3032 "github.com/docker/compose-cli/cli/formatter"
3133
3234 "github.com/compose-spec/compose-go/types"
35+ "github.com/sirupsen/logrus"
3336 "github.com/spf13/cobra"
3437)
3538
@@ -49,6 +52,8 @@ type upOptions struct {
4952 forceRecreate bool
5053 noRecreate bool
5154 noStart bool
55+ cascadeStop bool
56+ exitCodeFrom string
5257}
5358
5459func (o upOptions ) recreateStrategy () string {
@@ -73,6 +78,12 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
7378 RunE : func (cmd * cobra.Command , args []string ) error {
7479 switch contextType {
7580 case store .LocalContextType , store .DefaultContextType , store .EcsLocalSimulationContextType :
81+ if opts .exitCodeFrom != "" {
82+ opts .cascadeStop = true
83+ }
84+ if opts .cascadeStop && opts .Detach {
85+ return fmt .Errorf ("--abort-on-container-exit and --detach are incompatible" )
86+ }
7687 if opts .forceRecreate && opts .noRecreate {
7788 return fmt .Errorf ("--force-recreate and --no-recreate are incompatible" )
7889 }
@@ -95,6 +106,8 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
95106 flags .BoolVar (& opts .forceRecreate , "force-recreate" , false , "Recreate containers even if their configuration and image haven't changed." )
96107 flags .BoolVar (& opts .noRecreate , "no-recreate" , false , "If containers already exist, don't recreate them. Incompatible with --force-recreate." )
97108 flags .BoolVar (& opts .noStart , "no-start" , false , "Don't start the services after creating them." )
109+ flags .BoolVar (& opts .cascadeStop , "abort-on-container-exit" , false , "Stops all containers if any container was stopped. Incompatible with -d" )
110+ flags .StringVar (& opts .exitCodeFrom , "exit-code-from" , "" , "Return the exit code of the selected service container. Implies --abort-on-container-exit" )
98111 }
99112
100113 return upCmd
@@ -120,6 +133,13 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
120133 return err
121134 }
122135
136+ if opts .exitCodeFrom != "" {
137+ _ , err := project .GetService (opts .exitCodeFrom )
138+ if err != nil {
139+ return err
140+ }
141+ }
142+
123143 _ , err = progress .Run (ctx , func (ctx context.Context ) (string , error ) {
124144 err := c .ComposeService ().Create (ctx , project , compose.CreateOptions {
125145 RemoveOrphans : opts .removeOrphans ,
@@ -129,7 +149,7 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
129149 return "" , err
130150 }
131151 if opts .Detach {
132- err = c .ComposeService ().Start (ctx , project , nil )
152+ err = c .ComposeService ().Start (ctx , project , compose. StartOptions {} )
133153 }
134154 return "" , err
135155 })
@@ -145,13 +165,38 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
145165 return nil
146166 }
147167
148- err = c .ComposeService ().Start (ctx , project , formatter .NewLogConsumer (ctx , os .Stdout ))
149- if errors .Is (ctx .Err (), context .Canceled ) {
150- fmt .Println ("Gracefully stopping..." )
151- ctx = context .Background ()
152- _ , err = progress .Run (ctx , func (ctx context.Context ) (string , error ) {
168+ queue := make (chan compose.ContainerEvent )
169+ printer := printer {
170+ queue : queue ,
171+ }
172+
173+ stopFunc := func () error {
174+ ctx := context .Background ()
175+ _ , err := progress .Run (ctx , func (ctx context.Context ) (string , error ) {
153176 return "" , c .ComposeService ().Stop (ctx , project )
154177 })
178+ return err
179+ }
180+ signalChan := make (chan os.Signal , 1 )
181+ signal .Notify (signalChan , syscall .SIGINT , syscall .SIGTERM )
182+ go func () {
183+ <- signalChan
184+ fmt .Println ("Gracefully stopping..." )
185+ stopFunc () // nolint:errcheck
186+ }()
187+
188+ err = c .ComposeService ().Start (ctx , project , compose.StartOptions {
189+ Attach : func (event compose.ContainerEvent ) {
190+ queue <- event
191+ },
192+ })
193+ if err != nil {
194+ return err
195+ }
196+
197+ exitCode , err := printer .run (ctx , opts .cascadeStop , opts .exitCodeFrom , stopFunc )
198+ if exitCode != 0 {
199+ return cmd.ExitCodeError {ExitCode : exitCode }
155200 }
156201 return err
157202}
@@ -196,3 +241,37 @@ func setup(ctx context.Context, opts composeOptions, services []string) (*client
196241
197242 return c , project , nil
198243}
244+
245+ type printer struct {
246+ queue chan compose.ContainerEvent
247+ }
248+
249+ func (p printer ) run (ctx context.Context , cascadeStop bool , exitCodeFrom string , stopFn func () error ) (int , error ) { //nolint:unparam
250+ consumer := formatter .NewLogConsumer (ctx , os .Stdout )
251+ var aborting bool
252+ for {
253+ event := <- p .queue
254+ switch event .Type {
255+ case compose .ContainerEventExit :
256+ if ! aborting {
257+ consumer .Status (event .Service , event .Source , fmt .Sprintf ("exited with code %d" , event .ExitCode ))
258+ }
259+ if cascadeStop && ! aborting {
260+ aborting = true
261+ fmt .Println ("Aborting on container exit..." )
262+ err := stopFn ()
263+ if err != nil {
264+ return 0 , err
265+ }
266+ }
267+ if exitCodeFrom == "" || exitCodeFrom == event .Service {
268+ logrus .Error (event .ExitCode )
269+ return event .ExitCode , nil
270+ }
271+ case compose .ContainerEventLog :
272+ if ! aborting {
273+ consumer .Log (event .Service , event .Source , event .Line )
274+ }
275+ }
276+ }
277+ }
0 commit comments