Skip to content

Commit d38342c

Browse files
committed
Restored image layer download progress details on pull.
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 4f419e5 commit d38342c

File tree

3 files changed

+156
-95
lines changed

3 files changed

+156
-95
lines changed

cmd/display/tty.go

Lines changed: 118 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import (
2020
"context"
2121
"fmt"
2222
"io"
23+
"iter"
2324
"strings"
2425
"sync"
2526
"time"
2627

2728
"github.com/buger/goterm"
29+
"github.com/docker/compose/v5/pkg/utils"
2830
"github.com/docker/go-units"
2931
"github.com/morikuni/aec"
3032

@@ -60,7 +62,8 @@ type ttyWriter struct {
6062

6163
type task struct {
6264
ID string
63-
parentID string
65+
parent string // the resource this task receives updates from - other parents will be ignored
66+
parents utils.Set[string] // all resources to depend on this task
6467
startTime time.Time
6568
endTime time.Time
6669
text string
@@ -72,6 +75,64 @@ type task struct {
7275
spinner *Spinner
7376
}
7477

78+
func newTask(e api.Resource) task {
79+
t := task{
80+
ID: e.ID,
81+
parents: utils.NewSet[string](),
82+
startTime: time.Now(),
83+
text: e.Text,
84+
details: e.Details,
85+
status: e.Status,
86+
current: e.Current,
87+
percent: e.Percent,
88+
total: e.Total,
89+
spinner: NewSpinner(),
90+
}
91+
if e.ParentID != "" {
92+
t.parent = e.ParentID
93+
t.parents.Add(e.ParentID)
94+
}
95+
if e.Status == api.Done || e.Status == api.Error {
96+
t.stop()
97+
}
98+
return t
99+
}
100+
101+
// update adjusts task state based on last received event
102+
func (t *task) update(e api.Resource) {
103+
if e.ParentID != "" {
104+
t.parents.Add(e.ParentID)
105+
// we may receive same event from distinct parents (typically: images sharing layers)
106+
// to avoid status to flicker, only accept updates from our first declared parent
107+
if t.parent != e.ParentID {
108+
return
109+
}
110+
}
111+
112+
// update task based on received event
113+
switch e.Status {
114+
case api.Done, api.Error, api.Warning:
115+
if t.status != e.Status {
116+
t.stop()
117+
}
118+
case api.Working:
119+
t.hasMore()
120+
}
121+
t.status = e.Status
122+
t.text = e.Text
123+
t.details = e.Details
124+
// progress can only go up
125+
if e.Total > t.total {
126+
t.total = e.Total
127+
}
128+
if e.Current > t.current {
129+
t.current = e.Current
130+
}
131+
if e.Percent > t.percent {
132+
t.percent = e.Percent
133+
}
134+
}
135+
75136
func (t *task) stop() {
76137
t.endTime = time.Now()
77138
t.spinner.Stop()
@@ -81,6 +142,15 @@ func (t *task) hasMore() {
81142
t.spinner.Restart()
82143
}
83144

145+
func (t *task) Completed() bool {
146+
switch t.status {
147+
case api.Done, api.Error, api.Warning:
148+
return true
149+
default:
150+
return false
151+
}
152+
}
153+
84154
func (w *ttyWriter) Start(ctx context.Context, operation string) {
85155
w.ticker = time.NewTicker(100 * time.Millisecond)
86156
w.operation = operation
@@ -136,49 +206,13 @@ func (w *ttyWriter) event(e api.Resource) {
136206
w.suspended = false
137207
}
138208

139-
if last, ok := w.tasks[e.ID]; ok {
140-
switch e.Status {
141-
case api.Done, api.Error, api.Warning:
142-
if last.status != e.Status {
143-
last.stop()
144-
}
145-
case api.Working:
146-
last.hasMore()
147-
}
148-
last.status = e.Status
149-
last.text = e.Text
150-
last.details = e.Details
151-
// progress can only go up
152-
if e.Total > last.total {
153-
last.total = e.Total
154-
}
155-
if e.Current > last.current {
156-
last.current = e.Current
157-
}
158-
if e.Percent > last.percent {
159-
last.percent = e.Percent
160-
}
161-
// allow set/unset of parent, but not swapping otherwise prompt is flickering
162-
if last.parentID == "" || e.ParentID == "" {
163-
last.parentID = e.ParentID
164-
}
209+
last, knownEvent := w.tasks[e.ID]
210+
switch {
211+
case knownEvent:
212+
last.update(e)
165213
w.tasks[e.ID] = last
166-
} else {
167-
t := task{
168-
ID: e.ID,
169-
parentID: e.ParentID,
170-
startTime: time.Now(),
171-
text: e.Text,
172-
details: e.Details,
173-
status: e.Status,
174-
current: e.Current,
175-
percent: e.Percent,
176-
total: e.Total,
177-
spinner: NewSpinner(),
178-
}
179-
if e.Status == api.Done || e.Status == api.Error {
180-
t.stop()
181-
}
214+
default:
215+
t := newTask(e)
182216
w.tasks[e.ID] = t
183217
w.ids = append(w.ids, e.ID)
184218
}
@@ -205,6 +239,28 @@ func (w *ttyWriter) printEvent(e api.Resource) {
205239
_, _ = fmt.Fprintf(w.out, "%s %s %s\n", e.ID, color(e.Text), e.Details)
206240
}
207241

242+
func (w *ttyWriter) parentTasks() iter.Seq[task] {
243+
return func(yield func(task) bool) {
244+
for _, id := range w.ids { // iterate on ids to enforce a consistent order
245+
t := w.tasks[id]
246+
if len(t.parents) == 0 {
247+
yield(t)
248+
}
249+
}
250+
}
251+
}
252+
253+
func (w *ttyWriter) childrenTasks(parent string) iter.Seq[task] {
254+
return func(yield func(task) bool) {
255+
for _, id := range w.ids { // iterate on ids to enforce a consistent order
256+
t := w.tasks[id]
257+
if t.parents.Has(parent) {
258+
yield(t)
259+
}
260+
}
261+
}
262+
}
263+
208264
func (w *ttyWriter) print() {
209265
w.mtx.Lock()
210266
defer w.mtx.Unlock()
@@ -233,20 +289,25 @@ func (w *ttyWriter) print() {
233289
var statusPadding int
234290
for _, t := range w.tasks {
235291
l := len(t.ID)
236-
if t.parentID == "" && statusPadding < l {
292+
if len(t.parents) == 0 && statusPadding < l {
237293
statusPadding = l
238294
}
239295
}
240296

297+
skipChildEvents := len(w.tasks) > goterm.Height()-2
241298
numLines := 0
242-
for _, id := range w.ids { // iterate on ids to enforce a consistent order
243-
t := w.tasks[id]
244-
if t.parentID != "" {
245-
continue
246-
}
299+
for t := range w.parentTasks() {
247300
line := w.lineText(t, "", terminalWidth, statusPadding, w.dryRun)
248301
_, _ = fmt.Fprint(w.out, line)
249302
numLines++
303+
if skipChildEvents {
304+
continue
305+
}
306+
for child := range w.childrenTasks(t.ID) {
307+
line := w.lineText(child, " ", terminalWidth, statusPadding-2, w.dryRun)
308+
_, _ = fmt.Fprint(w.out, line)
309+
numLines++
310+
}
250311
}
251312
for i := numLines; i < w.numLines; i++ {
252313
if numLines < goterm.Height()-2 {
@@ -281,18 +342,15 @@ func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding in
281342

282343
// only show the aggregated progress while the root operation is in-progress
283344
if parent := t; parent.status == api.Working {
284-
for _, id := range w.ids {
285-
child := w.tasks[id]
286-
if child.parentID == parent.ID {
287-
if child.status == api.Working && child.total == 0 {
288-
// we don't have totals available for all the child events
289-
// so don't show the total progress yet
290-
hideDetails = true
291-
}
292-
total += child.total
293-
current += child.current
294-
completion = append(completion, percentChars[(len(percentChars)-1)*child.percent/100])
345+
for child := range w.childrenTasks(parent.ID) {
346+
if child.status == api.Working && child.total == 0 {
347+
// we don't have totals available for all the child events
348+
// so don't show the total progress yet
349+
hideDetails = true
295350
}
351+
total += child.total
352+
current += child.current
353+
completion = append(completion, percentChars[(len(percentChars)-1)*child.percent/100])
296354
}
297355
}
298356

pkg/api/event.go

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,33 +38,35 @@ const (
3838
const ResourceCompose = "Compose"
3939

4040
const (
41-
StatusError = "Error"
42-
StatusCreating = "Creating"
43-
StatusStarting = "Starting"
44-
StatusStarted = "Started"
45-
StatusWaiting = "Waiting"
46-
StatusHealthy = "Healthy"
47-
StatusExited = "Exited"
48-
StatusRestarting = "Restarting"
49-
StatusRestarted = "Restarted"
50-
StatusRunning = "Running"
51-
StatusCreated = "Created"
52-
StatusStopping = "Stopping"
53-
StatusStopped = "Stopped"
54-
StatusKilling = "Killing"
55-
StatusKilled = "Killed"
56-
StatusRemoving = "Removing"
57-
StatusRemoved = "Removed"
58-
StatusBuilding = "Building"
59-
StatusBuilt = "Built"
60-
StatusPulling = "Pulling"
61-
StatusPulled = "Pulled"
62-
StatusCommitting = "Committing"
63-
StatusCommitted = "Committed"
64-
StatusCopying = "Copying"
65-
StatusCopied = "Copied"
66-
StatusExporting = "Exporting"
67-
StatusExported = "Exported"
41+
StatusError = "Error"
42+
StatusCreating = "Creating"
43+
StatusStarting = "Starting"
44+
StatusStarted = "Started"
45+
StatusWaiting = "Waiting"
46+
StatusHealthy = "Healthy"
47+
StatusExited = "Exited"
48+
StatusRestarting = "Restarting"
49+
StatusRestarted = "Restarted"
50+
StatusRunning = "Running"
51+
StatusCreated = "Created"
52+
StatusStopping = "Stopping"
53+
StatusStopped = "Stopped"
54+
StatusKilling = "Killing"
55+
StatusKilled = "Killed"
56+
StatusRemoving = "Removing"
57+
StatusRemoved = "Removed"
58+
StatusBuilding = "Building"
59+
StatusBuilt = "Built"
60+
StatusPulling = "Pulling"
61+
StatusPulled = "Pulled"
62+
StatusCommitting = "Committing"
63+
StatusCommitted = "Committed"
64+
StatusCopying = "Copying"
65+
StatusCopied = "Copied"
66+
StatusExporting = "Exporting"
67+
StatusExported = "Exported"
68+
StatusDownloading = "Downloading"
69+
StatusDownloadComplete = "Download complete"
6870
)
6971

7072
// Resource represents status change and progress for a compose resource.

pkg/compose/pull.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -398,14 +398,14 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
398398
}
399399

400400
var (
401-
text string
402-
total int64
403-
percent int
404-
current int64
405-
status = api.Working
401+
progress string
402+
total int64
403+
percent int
404+
current int64
405+
status = api.Working
406406
)
407407

408-
text = jm.Progress.String()
408+
progress = jm.Progress.String()
409409

410410
switch jm.Status {
411411
case PreparingPhase, WaitingPhase, PullingFsPhase:
@@ -431,7 +431,7 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
431431

432432
if jm.Error != nil {
433433
status = api.Error
434-
text = jm.Error.Message
434+
progress = jm.Error.Message
435435
}
436436

437437
events.On(api.Resource{
@@ -441,6 +441,7 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
441441
Total: total,
442442
Percent: percent,
443443
Status: status,
444-
Text: text,
444+
Text: jm.Status,
445+
Details: progress,
445446
})
446447
}

0 commit comments

Comments
 (0)