@@ -18,18 +18,21 @@ package compose
18
18
19
19
import (
20
20
"context"
21
- "errors"
22
21
"fmt"
23
22
"os"
23
+ "os/signal"
24
24
"path/filepath"
25
+ "syscall"
25
26
26
27
"github.com/docker/compose-cli/api/client"
27
28
"github.com/docker/compose-cli/api/compose"
28
29
"github.com/docker/compose-cli/api/context/store"
29
30
"github.com/docker/compose-cli/api/progress"
31
+ "github.com/docker/compose-cli/cli/cmd"
30
32
"github.com/docker/compose-cli/cli/formatter"
31
33
32
34
"github.com/compose-spec/compose-go/types"
35
+ "github.com/sirupsen/logrus"
33
36
"github.com/spf13/cobra"
34
37
)
35
38
@@ -49,6 +52,8 @@ type upOptions struct {
49
52
forceRecreate bool
50
53
noRecreate bool
51
54
noStart bool
55
+ cascadeStop bool
56
+ exitCodeFrom string
52
57
}
53
58
54
59
func (o upOptions ) recreateStrategy () string {
@@ -73,6 +78,12 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
73
78
RunE : func (cmd * cobra.Command , args []string ) error {
74
79
switch contextType {
75
80
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
+ }
76
87
if opts .forceRecreate && opts .noRecreate {
77
88
return fmt .Errorf ("--force-recreate and --no-recreate are incompatible" )
78
89
}
@@ -95,6 +106,8 @@ func upCommand(p *projectOptions, contextType string) *cobra.Command {
95
106
flags .BoolVar (& opts .forceRecreate , "force-recreate" , false , "Recreate containers even if their configuration and image haven't changed." )
96
107
flags .BoolVar (& opts .noRecreate , "no-recreate" , false , "If containers already exist, don't recreate them. Incompatible with --force-recreate." )
97
108
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" )
98
111
}
99
112
100
113
return upCmd
@@ -120,6 +133,13 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
120
133
return err
121
134
}
122
135
136
+ if opts .exitCodeFrom != "" {
137
+ _ , err := project .GetService (opts .exitCodeFrom )
138
+ if err != nil {
139
+ return err
140
+ }
141
+ }
142
+
123
143
_ , err = progress .Run (ctx , func (ctx context.Context ) (string , error ) {
124
144
err := c .ComposeService ().Create (ctx , project , compose.CreateOptions {
125
145
RemoveOrphans : opts .removeOrphans ,
@@ -129,7 +149,7 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
129
149
return "" , err
130
150
}
131
151
if opts .Detach {
132
- err = c .ComposeService ().Start (ctx , project , nil )
152
+ err = c .ComposeService ().Start (ctx , project , compose. StartOptions {} )
133
153
}
134
154
return "" , err
135
155
})
@@ -145,13 +165,38 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
145
165
return nil
146
166
}
147
167
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 ) {
153
176
return "" , c .ComposeService ().Stop (ctx , project )
154
177
})
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 }
155
200
}
156
201
return err
157
202
}
@@ -196,3 +241,37 @@ func setup(ctx context.Context, opts composeOptions, services []string) (*client
196
241
197
242
return c , project , nil
198
243
}
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