mirror of
https://github.com/docker/compose.git
synced 2025-12-17 02:34:24 +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"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"iter"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/buger/goterm"
|
"github.com/buger/goterm"
|
||||||
|
"github.com/docker/compose/v5/pkg/utils"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
|
|
||||||
@ -60,7 +62,8 @@ type ttyWriter struct {
|
|||||||
|
|
||||||
type task struct {
|
type task struct {
|
||||||
ID string
|
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
|
startTime time.Time
|
||||||
endTime time.Time
|
endTime time.Time
|
||||||
text string
|
text string
|
||||||
@ -72,6 +75,64 @@ type task struct {
|
|||||||
spinner *Spinner
|
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() {
|
func (t *task) stop() {
|
||||||
t.endTime = time.Now()
|
t.endTime = time.Now()
|
||||||
t.spinner.Stop()
|
t.spinner.Stop()
|
||||||
@ -81,6 +142,15 @@ func (t *task) hasMore() {
|
|||||||
t.spinner.Restart()
|
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) {
|
func (w *ttyWriter) Start(ctx context.Context, operation string) {
|
||||||
w.ticker = time.NewTicker(100 * time.Millisecond)
|
w.ticker = time.NewTicker(100 * time.Millisecond)
|
||||||
w.operation = operation
|
w.operation = operation
|
||||||
@ -137,48 +207,10 @@ func (w *ttyWriter) event(e api.Resource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if last, ok := w.tasks[e.ID]; ok {
|
if last, ok := w.tasks[e.ID]; ok {
|
||||||
switch e.Status {
|
last.update(e)
|
||||||
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
|
|
||||||
}
|
|
||||||
w.tasks[e.ID] = last
|
w.tasks[e.ID] = last
|
||||||
} else {
|
} else {
|
||||||
t := task{
|
t := newTask(e)
|
||||||
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()
|
|
||||||
}
|
|
||||||
w.tasks[e.ID] = t
|
w.tasks[e.ID] = t
|
||||||
w.ids = append(w.ids, e.ID)
|
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)
|
_, _ = 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() {
|
func (w *ttyWriter) print() {
|
||||||
w.mtx.Lock()
|
w.mtx.Lock()
|
||||||
defer w.mtx.Unlock()
|
defer w.mtx.Unlock()
|
||||||
@ -233,20 +287,25 @@ func (w *ttyWriter) print() {
|
|||||||
var statusPadding int
|
var statusPadding int
|
||||||
for _, t := range w.tasks {
|
for _, t := range w.tasks {
|
||||||
l := len(t.ID)
|
l := len(t.ID)
|
||||||
if t.parentID == "" && statusPadding < l {
|
if len(t.parents) == 0 && statusPadding < l {
|
||||||
statusPadding = l
|
statusPadding = l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipChildEvents := len(w.tasks) > goterm.Height()-2
|
||||||
numLines := 0
|
numLines := 0
|
||||||
for _, id := range w.ids { // iterate on ids to enforce a consistent order
|
for t := range w.parentTasks() {
|
||||||
t := w.tasks[id]
|
|
||||||
if t.parentID != "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line := w.lineText(t, "", terminalWidth, statusPadding, w.dryRun)
|
line := w.lineText(t, "", terminalWidth, statusPadding, w.dryRun)
|
||||||
_, _ = fmt.Fprint(w.out, line)
|
_, _ = fmt.Fprint(w.out, line)
|
||||||
numLines++
|
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++ {
|
for i := numLines; i < w.numLines; i++ {
|
||||||
if numLines < goterm.Height()-2 {
|
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
|
// only show the aggregated progress while the root operation is in-progress
|
||||||
if parent := t; parent.status == api.Working {
|
if parent := t; parent.status == api.Working {
|
||||||
for _, id := range w.ids {
|
for child := range w.childrenTasks(parent.ID) {
|
||||||
child := w.tasks[id]
|
|
||||||
if child.parentID == parent.ID {
|
|
||||||
if child.status == api.Working && child.total == 0 {
|
if child.status == api.Working && child.total == 0 {
|
||||||
// we don't have totals available for all the child events
|
// we don't have totals available for all the child events
|
||||||
// so don't show the total progress yet
|
// 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])
|
completion = append(completion, percentChars[(len(percentChars)-1)*child.percent/100])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// don't try to show detailed progress if we don't have any idea
|
// don't try to show detailed progress if we don't have any idea
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
|
|||||||
@ -65,6 +65,8 @@ const (
|
|||||||
StatusCopied = "Copied"
|
StatusCopied = "Copied"
|
||||||
StatusExporting = "Exporting"
|
StatusExporting = "Exporting"
|
||||||
StatusExported = "Exported"
|
StatusExported = "Exported"
|
||||||
|
StatusDownloading = "Downloading"
|
||||||
|
StatusDownloadComplete = "Download complete"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resource represents status change and progress for a compose resource.
|
// 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 (
|
var (
|
||||||
text string
|
progress string
|
||||||
total int64
|
total int64
|
||||||
percent int
|
percent int
|
||||||
current int64
|
current int64
|
||||||
status = api.Working
|
status = api.Working
|
||||||
)
|
)
|
||||||
|
|
||||||
text = jm.Progress.String()
|
progress = jm.Progress.String()
|
||||||
|
|
||||||
switch jm.Status {
|
switch jm.Status {
|
||||||
case PreparingPhase, WaitingPhase, PullingFsPhase:
|
case PreparingPhase, WaitingPhase, PullingFsPhase:
|
||||||
@ -431,7 +431,7 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
|
|||||||
|
|
||||||
if jm.Error != nil {
|
if jm.Error != nil {
|
||||||
status = api.Error
|
status = api.Error
|
||||||
text = jm.Error.Message
|
progress = jm.Error.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
events.On(api.Resource{
|
events.On(api.Resource{
|
||||||
@ -441,6 +441,7 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
|
|||||||
Total: total,
|
Total: total,
|
||||||
Percent: percent,
|
Percent: percent,
|
||||||
Status: status,
|
Status: status,
|
||||||
Text: text,
|
Text: jm.Status,
|
||||||
|
Details: progress,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user