mirror of
https://github.com/docker/compose.git
synced 2025-12-16 18:24:00 +01:00
Restored image layer download progress details on pull.
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
4f419e5098
commit
02008a0097
@ -20,11 +20,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/buger/goterm"
|
||||
"github.com/docker/compose/v5/pkg/utils"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/morikuni/aec"
|
||||
|
||||
@ -60,7 +62,8 @@ type ttyWriter struct {
|
||||
|
||||
type task struct {
|
||||
ID string
|
||||
parentID string
|
||||
parent string // the resource this task receives updates from - other parents will be ignored
|
||||
parents utils.Set[string] // all resources to depend on this task
|
||||
startTime time.Time
|
||||
endTime time.Time
|
||||
text string
|
||||
@ -72,6 +75,64 @@ type task struct {
|
||||
spinner *Spinner
|
||||
}
|
||||
|
||||
func newTask(e api.Resource) task {
|
||||
t := task{
|
||||
ID: e.ID,
|
||||
parents: utils.NewSet[string](),
|
||||
startTime: time.Now(),
|
||||
text: e.Text,
|
||||
details: e.Details,
|
||||
status: e.Status,
|
||||
current: e.Current,
|
||||
percent: e.Percent,
|
||||
total: e.Total,
|
||||
spinner: NewSpinner(),
|
||||
}
|
||||
if e.ParentID != "" {
|
||||
t.parent = e.ParentID
|
||||
t.parents.Add(e.ParentID)
|
||||
}
|
||||
if e.Status == api.Done || e.Status == api.Error {
|
||||
t.stop()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// update adjusts task state based on last received event
|
||||
func (t *task) update(e api.Resource) {
|
||||
if e.ParentID != "" {
|
||||
t.parents.Add(e.ParentID)
|
||||
// we may receive same event from distinct parents (typically: images sharing layers)
|
||||
// to avoid status to flicker, only accept updates from our first declared parent
|
||||
if t.parent != e.ParentID {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// update task based on received event
|
||||
switch e.Status {
|
||||
case api.Done, api.Error, api.Warning:
|
||||
if t.status != e.Status {
|
||||
t.stop()
|
||||
}
|
||||
case api.Working:
|
||||
t.hasMore()
|
||||
}
|
||||
t.status = e.Status
|
||||
t.text = e.Text
|
||||
t.details = e.Details
|
||||
// progress can only go up
|
||||
if e.Total > t.total {
|
||||
t.total = e.Total
|
||||
}
|
||||
if e.Current > t.current {
|
||||
t.current = e.Current
|
||||
}
|
||||
if e.Percent > t.percent {
|
||||
t.percent = e.Percent
|
||||
}
|
||||
}
|
||||
|
||||
func (t *task) stop() {
|
||||
t.endTime = time.Now()
|
||||
t.spinner.Stop()
|
||||
@ -81,6 +142,15 @@ func (t *task) hasMore() {
|
||||
t.spinner.Restart()
|
||||
}
|
||||
|
||||
func (t *task) Completed() bool {
|
||||
switch t.status {
|
||||
case api.Done, api.Error, api.Warning:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ttyWriter) Start(ctx context.Context, operation string) {
|
||||
w.ticker = time.NewTicker(100 * time.Millisecond)
|
||||
w.operation = operation
|
||||
@ -137,48 +207,10 @@ func (w *ttyWriter) event(e api.Resource) {
|
||||
}
|
||||
|
||||
if last, ok := w.tasks[e.ID]; ok {
|
||||
switch e.Status {
|
||||
case api.Done, api.Error, api.Warning:
|
||||
if last.status != e.Status {
|
||||
last.stop()
|
||||
}
|
||||
case api.Working:
|
||||
last.hasMore()
|
||||
}
|
||||
last.status = e.Status
|
||||
last.text = e.Text
|
||||
last.details = e.Details
|
||||
// progress can only go up
|
||||
if e.Total > last.total {
|
||||
last.total = e.Total
|
||||
}
|
||||
if e.Current > last.current {
|
||||
last.current = e.Current
|
||||
}
|
||||
if e.Percent > last.percent {
|
||||
last.percent = e.Percent
|
||||
}
|
||||
// allow set/unset of parent, but not swapping otherwise prompt is flickering
|
||||
if last.parentID == "" || e.ParentID == "" {
|
||||
last.parentID = e.ParentID
|
||||
}
|
||||
last.update(e)
|
||||
w.tasks[e.ID] = last
|
||||
} else {
|
||||
t := task{
|
||||
ID: e.ID,
|
||||
parentID: e.ParentID,
|
||||
startTime: time.Now(),
|
||||
text: e.Text,
|
||||
details: e.Details,
|
||||
status: e.Status,
|
||||
current: e.Current,
|
||||
percent: e.Percent,
|
||||
total: e.Total,
|
||||
spinner: NewSpinner(),
|
||||
}
|
||||
if e.Status == api.Done || e.Status == api.Error {
|
||||
t.stop()
|
||||
}
|
||||
t := newTask(e)
|
||||
w.tasks[e.ID] = t
|
||||
w.ids = append(w.ids, e.ID)
|
||||
}
|
||||
@ -205,6 +237,28 @@ func (w *ttyWriter) printEvent(e api.Resource) {
|
||||
_, _ = fmt.Fprintf(w.out, "%s %s %s\n", e.ID, color(e.Text), e.Details)
|
||||
}
|
||||
|
||||
func (w *ttyWriter) parentTasks() iter.Seq[task] {
|
||||
return func(yield func(task) bool) {
|
||||
for _, id := range w.ids { // iterate on ids to enforce a consistent order
|
||||
t := w.tasks[id]
|
||||
if len(t.parents) == 0 {
|
||||
yield(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ttyWriter) childrenTasks(parent string) iter.Seq[task] {
|
||||
return func(yield func(task) bool) {
|
||||
for _, id := range w.ids { // iterate on ids to enforce a consistent order
|
||||
t := w.tasks[id]
|
||||
if t.parents.Has(parent) {
|
||||
yield(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ttyWriter) print() {
|
||||
w.mtx.Lock()
|
||||
defer w.mtx.Unlock()
|
||||
@ -233,20 +287,25 @@ func (w *ttyWriter) print() {
|
||||
var statusPadding int
|
||||
for _, t := range w.tasks {
|
||||
l := len(t.ID)
|
||||
if t.parentID == "" && statusPadding < l {
|
||||
if len(t.parents) == 0 && statusPadding < l {
|
||||
statusPadding = l
|
||||
}
|
||||
}
|
||||
|
||||
skipChildEvents := len(w.tasks) > goterm.Height()-2
|
||||
numLines := 0
|
||||
for _, id := range w.ids { // iterate on ids to enforce a consistent order
|
||||
t := w.tasks[id]
|
||||
if t.parentID != "" {
|
||||
continue
|
||||
}
|
||||
for t := range w.parentTasks() {
|
||||
line := w.lineText(t, "", terminalWidth, statusPadding, w.dryRun)
|
||||
_, _ = fmt.Fprint(w.out, line)
|
||||
numLines++
|
||||
if skipChildEvents {
|
||||
continue
|
||||
}
|
||||
for child := range w.childrenTasks(t.ID) {
|
||||
line := w.lineText(child, " ", terminalWidth, statusPadding-2, w.dryRun)
|
||||
_, _ = fmt.Fprint(w.out, line)
|
||||
numLines++
|
||||
}
|
||||
}
|
||||
for i := numLines; i < w.numLines; i++ {
|
||||
if numLines < goterm.Height()-2 {
|
||||
@ -281,9 +340,7 @@ func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding in
|
||||
|
||||
// only show the aggregated progress while the root operation is in-progress
|
||||
if parent := t; parent.status == api.Working {
|
||||
for _, id := range w.ids {
|
||||
child := w.tasks[id]
|
||||
if child.parentID == parent.ID {
|
||||
for child := range w.childrenTasks(parent.ID) {
|
||||
if child.status == api.Working && child.total == 0 {
|
||||
// we don't have totals available for all the child events
|
||||
// so don't show the total progress yet
|
||||
@ -294,7 +351,6 @@ func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding in
|
||||
completion = append(completion, percentChars[(len(percentChars)-1)*child.percent/100])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't try to show detailed progress if we don't have any idea
|
||||
if total == 0 {
|
||||
|
||||
@ -65,6 +65,8 @@ const (
|
||||
StatusCopied = "Copied"
|
||||
StatusExporting = "Exporting"
|
||||
StatusExported = "Exported"
|
||||
StatusDownloading = "Downloading"
|
||||
StatusDownloadComplete = "Download complete"
|
||||
)
|
||||
|
||||
// Resource represents status change and progress for a compose resource.
|
||||
|
||||
@ -398,14 +398,14 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
|
||||
}
|
||||
|
||||
var (
|
||||
text string
|
||||
progress string
|
||||
total int64
|
||||
percent int
|
||||
current int64
|
||||
status = api.Working
|
||||
)
|
||||
|
||||
text = jm.Progress.String()
|
||||
progress = jm.Progress.String()
|
||||
|
||||
switch jm.Status {
|
||||
case PreparingPhase, WaitingPhase, PullingFsPhase:
|
||||
@ -431,7 +431,7 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
|
||||
|
||||
if jm.Error != nil {
|
||||
status = api.Error
|
||||
text = jm.Error.Message
|
||||
progress = jm.Error.Message
|
||||
}
|
||||
|
||||
events.On(api.Resource{
|
||||
@ -441,6 +441,7 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
|
||||
Total: total,
|
||||
Percent: percent,
|
||||
Status: status,
|
||||
Text: text,
|
||||
Text: jm.Status,
|
||||
Details: progress,
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user