Skip to content

Commit 02008a0

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

File tree

3 files changed

+152
-93
lines changed

3 files changed

+152
-93
lines changed

cmd/display/tty.go

Lines changed: 114 additions & 58 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
@@ -137,48 +207,10 @@ func (w *ttyWriter) event(e api.Resource) {
137207
}
138208

139209
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-
}
210+
last.update(e)
165211
w.tasks[e.ID] = last
166212
} 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-
}
213+
t := newTask(e)
182214
w.tasks[e.ID] = t
183215
w.ids = append(w.ids, e.ID)
184216
}
@@ -205,6 +237,28 @@ func (w *ttyWriter) printEvent(e api.Resource) {
205237
_, _ = fmt.Fprintf(w.out, "%s %s %s\n", e.ID, color(e.Text), e.Details)
206238
}
207239

240+
func (w *ttyWriter) parentTasks() iter.Seq[task] {
241+
return func(yield func(task) bool) {
242+
for _, id := range w.ids { // iterate on ids to enforce a consistent order
243+
t := w.tasks[id]
244+
if len(t.parents) == 0 {
245+
yield(t)
246+
}
247+
}
248+
}
249+
}
250+
251+
func (w *ttyWriter) childrenTasks(parent string) iter.Seq[task] {
252+
return func(yield func(task) bool) {
253+
for _, id := range w.ids { // iterate on ids to enforce a consistent order
254+
t := w.tasks[id]
255+
if t.parents.Has(parent) {
256+
yield(t)
257+
}
258+
}
259+
}
260+
}
261+
208262
func (w *ttyWriter) print() {
209263
w.mtx.Lock()
210264
defer w.mtx.Unlock()
@@ -233,20 +287,25 @@ func (w *ttyWriter) print() {
233287
var statusPadding int
234288
for _, t := range w.tasks {
235289
l := len(t.ID)
236-
if t.parentID == "" && statusPadding < l {
290+
if len(t.parents) == 0 && statusPadding < l {
237291
statusPadding = l
238292
}
239293
}
240294

295+
skipChildEvents := len(w.tasks) > goterm.Height()-2
241296
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-
}
297+
for t := range w.parentTasks() {
247298
line := w.lineText(t, "", terminalWidth, statusPadding, w.dryRun)
248299
_, _ = fmt.Fprint(w.out, line)
249300
numLines++
301+
if skipChildEvents {
302+
continue
303+
}
304+
for child := range w.childrenTasks(t.ID) {
305+
line := w.lineText(child, " ", terminalWidth, statusPadding-2, w.dryRun)
306+
_, _ = fmt.Fprint(w.out, line)
307+
numLines++
308+
}
250309
}
251310
for i := numLines; i < w.numLines; i++ {
252311
if numLines < goterm.Height()-2 {
@@ -281,18 +340,15 @@ func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding in
281340

282341
// only show the aggregated progress while the root operation is in-progress
283342
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])
343+
for child := range w.childrenTasks(parent.ID) {
344+
if child.status == api.Working && child.total == 0 {
345+
// we don't have totals available for all the child events
346+
// so don't show the total progress yet
347+
hideDetails = true
295348
}
349+
total += child.total
350+
current += child.current
351+
completion = append(completion, percentChars[(len(percentChars)-1)*child.percent/100])
296352
}
297353
}
298354

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)