Skip to content

Commit 29308cb

Browse files
committed
keep containers attached on stop to capture termination logs
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 0b0242d commit 29308cb

21 files changed

+352
-92
lines changed

cmd/formatter/ansi.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,73 +28,73 @@ func ansi(code string) string {
2828
return fmt.Sprintf("\033%s", code)
2929
}
3030

31-
func SaveCursor() {
31+
func saveCursor() {
3232
if disableAnsi {
3333
return
3434
}
3535
fmt.Print(ansi("7"))
3636
}
3737

38-
func RestoreCursor() {
38+
func restoreCursor() {
3939
if disableAnsi {
4040
return
4141
}
4242
fmt.Print(ansi("8"))
4343
}
4444

45-
func HideCursor() {
45+
func hideCursor() {
4646
if disableAnsi {
4747
return
4848
}
4949
fmt.Print(ansi("[?25l"))
5050
}
5151

52-
func ShowCursor() {
52+
func showCursor() {
5353
if disableAnsi {
5454
return
5555
}
5656
fmt.Print(ansi("[?25h"))
5757
}
5858

59-
func MoveCursor(y, x int) {
59+
func moveCursor(y, x int) {
6060
if disableAnsi {
6161
return
6262
}
6363
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
6464
}
6565

66-
func MoveCursorX(pos int) {
66+
func carriageReturn() {
6767
if disableAnsi {
6868
return
6969
}
70-
fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
70+
fmt.Print(ansi(fmt.Sprintf("[%dG", 0)))
7171
}
7272

73-
func ClearLine() {
73+
func clearLine() {
7474
if disableAnsi {
7575
return
7676
}
7777
// Does not move cursor from its current position
7878
fmt.Print(ansi("[2K"))
7979
}
8080

81-
func MoveCursorUp(lines int) {
81+
func moveCursorUp(lines int) {
8282
if disableAnsi {
8383
return
8484
}
8585
// Does not add new lines
8686
fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
8787
}
8888

89-
func MoveCursorDown(lines int) {
89+
func moveCursorDown(lines int) {
9090
if disableAnsi {
9191
return
9292
}
9393
// Does not add new lines
9494
fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
9595
}
9696

97-
func NewLine() {
97+
func newLine() {
9898
// Like \n
9999
fmt.Print("\012")
100100
}

cmd/formatter/logs.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ func (l *logConsumer) register(name string) *presenter {
7373
} else {
7474
cf := monochrome
7575
if l.color {
76-
if name == api.WatchLogger {
76+
switch name {
77+
case "":
78+
cf = monochrome
79+
case api.WatchLogger:
7780
cf = makeColorFunc("92")
78-
} else {
81+
default:
7982
cf = nextColor()
8083
}
8184
}

cmd/formatter/shortcut.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ func (ke *KeyboardError) printError(height int, info string) {
4848
if ke.shouldDisplay() {
4949
errMessage := ke.err.Error()
5050

51-
MoveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
52-
ClearLine()
51+
moveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
52+
clearLine()
5353

5454
fmt.Print(errMessage)
5555
}
@@ -133,7 +133,7 @@ func (lk *LogKeyboard) createBuffer(lines int) {
133133

134134
if lines > 0 {
135135
allocateSpace(lines)
136-
MoveCursorUp(lines)
136+
moveCursorUp(lines)
137137
}
138138
}
139139

@@ -146,17 +146,17 @@ func (lk *LogKeyboard) printNavigationMenu() {
146146
height := goterm.Height()
147147
menu := lk.navigationMenu()
148148

149-
MoveCursorX(0)
150-
SaveCursor()
149+
carriageReturn()
150+
saveCursor()
151151

152152
lk.kError.printError(height, menu)
153153

154-
MoveCursor(height-extraLines(menu), 0)
155-
ClearLine()
154+
moveCursor(height-extraLines(menu), 0)
155+
clearLine()
156156
fmt.Print(menu)
157157

158-
MoveCursorX(0)
159-
RestoreCursor()
158+
carriageReturn()
159+
restoreCursor()
160160
}
161161
}
162162

@@ -188,15 +188,15 @@ func (lk *LogKeyboard) navigationMenu() string {
188188

189189
func (lk *LogKeyboard) clearNavigationMenu() {
190190
height := goterm.Height()
191-
MoveCursorX(0)
192-
SaveCursor()
191+
carriageReturn()
192+
saveCursor()
193193

194-
// ClearLine()
194+
// clearLine()
195195
for i := 0; i < height; i++ {
196-
MoveCursorDown(1)
197-
ClearLine()
196+
moveCursorDown(1)
197+
clearLine()
198198
}
199-
RestoreCursor()
199+
restoreCursor()
200200
}
201201

202202
func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Project) {
@@ -316,13 +316,13 @@ func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEv
316316
case keyboard.KeyCtrlC:
317317
_ = keyboard.Close()
318318
lk.clearNavigationMenu()
319-
ShowCursor()
319+
showCursor()
320320

321321
lk.logLevel = NONE
322322
// will notify main thread to kill and will handle gracefully
323323
lk.signalChannel <- syscall.SIGINT
324324
case keyboard.KeyEnter:
325-
NewLine()
325+
newLine()
326326
lk.printNavigationMenu()
327327
}
328328
}
@@ -336,9 +336,9 @@ func (lk *LogKeyboard) EnableWatch(enabled bool, watcher Feature) {
336336

337337
func allocateSpace(lines int) {
338338
for i := 0; i < lines; i++ {
339-
ClearLine()
340-
NewLine()
341-
MoveCursorX(0)
339+
clearLine()
340+
newLine()
341+
carriageReturn()
342342
}
343343
}
344344

cmd/formatter/stopping.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Copyright 2024 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package formatter
18+
19+
import (
20+
"fmt"
21+
"strings"
22+
"time"
23+
24+
"github.com/buger/goterm"
25+
"github.com/docker/compose/v2/pkg/api"
26+
"github.com/docker/compose/v2/pkg/progress"
27+
)
28+
29+
type Stopping struct {
30+
api.LogConsumer
31+
enabled bool
32+
spinner *progress.Spinner
33+
ticker *time.Ticker
34+
startedAt time.Time
35+
}
36+
37+
func NewStopping(l api.LogConsumer) *Stopping {
38+
s := &Stopping{}
39+
s.LogConsumer = logDecorator{
40+
decorated: l,
41+
Before: s.clear,
42+
After: s.print,
43+
}
44+
return s
45+
}
46+
47+
func (s *Stopping) ApplicationTermination() {
48+
if progress.Mode != progress.ModeAuto {
49+
// User explicitly opted for output format
50+
return
51+
}
52+
if disableAnsi {
53+
return
54+
}
55+
s.enabled = true
56+
s.spinner = progress.NewSpinner()
57+
hideCursor()
58+
s.startedAt = time.Now()
59+
s.ticker = time.NewTicker(100 * time.Millisecond)
60+
go func() {
61+
for {
62+
<-s.ticker.C
63+
s.print()
64+
}
65+
}()
66+
}
67+
68+
func (s *Stopping) Close() {
69+
showCursor()
70+
if s.ticker != nil {
71+
s.ticker.Stop()
72+
}
73+
s.clear()
74+
}
75+
76+
func (s *Stopping) clear() {
77+
if !s.enabled {
78+
return
79+
}
80+
81+
height := goterm.Height()
82+
carriageReturn()
83+
saveCursor()
84+
85+
// clearLine()
86+
for i := 0; i < height; i++ {
87+
moveCursorDown(1)
88+
clearLine()
89+
}
90+
restoreCursor()
91+
}
92+
93+
const stoppingBanner = "Gracefully Stopping... (press Ctrl+C again to force)"
94+
95+
func (s *Stopping) print() {
96+
if !s.enabled {
97+
return
98+
}
99+
100+
height := goterm.Height()
101+
width := goterm.Width()
102+
carriageReturn()
103+
saveCursor()
104+
105+
moveCursor(height, 0)
106+
clearLine()
107+
elapsed := time.Since(s.startedAt).Seconds()
108+
timer := fmt.Sprintf("%.1fs ", elapsed)
109+
pad := width - len(timer) - len(stoppingBanner) - 5
110+
fmt.Printf("%s %s %s %s",
111+
progress.CountColor(s.spinner.String()),
112+
stoppingBanner,
113+
strings.Repeat(" ", pad),
114+
progress.TimerColor(timer),
115+
)
116+
117+
carriageReturn()
118+
restoreCursor()
119+
}

pkg/compose/convergence.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ func (c *convergence) stopDependentContainers(ctx context.Context, project *type
236236
err := c.service.stop(ctx, project.Name, api.StopOptions{
237237
Services: dependents,
238238
Project: project,
239-
})
239+
}, nil)
240240
if err != nil {
241241
return err
242242
}

pkg/compose/create.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,7 +1428,7 @@ func (s *composeService) removeDivergedNetwork(ctx context.Context, project *typ
14281428
err := s.stop(ctx, project.Name, api.StopOptions{
14291429
Services: services,
14301430
Project: project,
1431-
})
1431+
}, nil)
14321432
if err != nil {
14331433
return nil, err
14341434
}
@@ -1599,7 +1599,7 @@ func (s *composeService) removeDivergedVolume(ctx context.Context, name string,
15991599
err := s.stop(ctx, project.Name, api.StopOptions{
16001600
Services: services,
16011601
Project: project,
1602-
})
1602+
}, nil)
16031603
if err != nil {
16041604
return err
16051605
}

pkg/compose/down.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,17 @@ func (s *composeService) removeVolume(ctx context.Context, id string, w progress
298298
return err
299299
}
300300

301-
func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, service *types.ServiceConfig, ctr containerType.Summary, timeout *time.Duration) error {
301+
func (s *composeService) stopContainer(
302+
ctx context.Context, w progress.Writer,
303+
service *types.ServiceConfig, ctr containerType.Summary,
304+
timeout *time.Duration, listener api.ContainerEventListener,
305+
) error {
302306
eventName := getContainerProgressName(ctr)
303307
w.Event(progress.StoppingEvent(eventName))
304308

305309
if service != nil {
306310
for _, hook := range service.PreStop {
307-
err := s.runHook(ctx, ctr, *service, hook, nil)
311+
err := s.runHook(ctx, ctr, *service, hook, listener)
308312
if err != nil {
309313
// Ignore errors indicating that some containers were already stopped or removed.
310314
if cerrdefs.IsNotFound(err) || cerrdefs.IsConflict(err) {
@@ -325,11 +329,15 @@ func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, s
325329
return nil
326330
}
327331

328-
func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, serv *types.ServiceConfig, containers []containerType.Summary, timeout *time.Duration) error {
332+
func (s *composeService) stopContainers(
333+
ctx context.Context, w progress.Writer,
334+
serv *types.ServiceConfig, containers []containerType.Summary,
335+
timeout *time.Duration, listener api.ContainerEventListener,
336+
) error {
329337
eg, ctx := errgroup.WithContext(ctx)
330338
for _, ctr := range containers {
331339
eg.Go(func() error {
332-
return s.stopContainer(ctx, w, serv, ctr, timeout)
340+
return s.stopContainer(ctx, w, serv, ctr, timeout, listener)
333341
})
334342
}
335343
return eg.Wait()
@@ -348,7 +356,7 @@ func (s *composeService) removeContainers(ctx context.Context, containers []cont
348356
func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr containerType.Summary, service *types.ServiceConfig, timeout *time.Duration, volumes bool) error {
349357
w := progress.ContextWriter(ctx)
350358
eventName := getContainerProgressName(ctr)
351-
err := s.stopContainer(ctx, w, service, ctr, timeout)
359+
err := s.stopContainer(ctx, w, service, ctr, timeout, nil)
352360
if cerrdefs.IsNotFound(err) {
353361
w.Event(progress.RemovedEvent(eventName))
354362
return nil

pkg/compose/printer.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,7 @@ func (p *printer) Run(cascade api.Cascade, exitCodeFrom string, stopFn func() er
149149
return exitCode, nil
150150
}
151151
case api.ContainerEventLog, api.HookEventLog:
152-
if !aborting {
153-
p.consumer.Log(container, event.Line)
154-
}
152+
p.consumer.Log(container, event.Line)
155153
case api.ContainerEventErr:
156154
if !aborting {
157155
p.consumer.Err(container, event.Line)

0 commit comments

Comments
 (0)