Bläddra i källkod

Restored image layer download progress details on pull.

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 2 veckor sedan
förälder
incheckning
02008a0097
3 ändrade filer med 152 tillägg och 93 borttagningar
  1. 114 58
      cmd/display/tty.go
  2. 29 27
      pkg/api/event.go
  3. 9 8
      pkg/compose/pull.go

+ 114 - 58
cmd/display/tty.go

@@ -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,18 +340,15 @@ 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 {
-				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
-					hideDetails = true
-				}
-				total += child.total
-				current += child.current
-				completion = append(completion, percentChars[(len(percentChars)-1)*child.percent/100])
+		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
+				hideDetails = true
 			}
+			total += child.total
+			current += child.current
+			completion = append(completion, percentChars[(len(percentChars)-1)*child.percent/100])
 		}
 	}
 

+ 29 - 27
pkg/api/event.go

@@ -38,33 +38,35 @@ const (
 const ResourceCompose = "Compose"
 
 const (
-	StatusError      = "Error"
-	StatusCreating   = "Creating"
-	StatusStarting   = "Starting"
-	StatusStarted    = "Started"
-	StatusWaiting    = "Waiting"
-	StatusHealthy    = "Healthy"
-	StatusExited     = "Exited"
-	StatusRestarting = "Restarting"
-	StatusRestarted  = "Restarted"
-	StatusRunning    = "Running"
-	StatusCreated    = "Created"
-	StatusStopping   = "Stopping"
-	StatusStopped    = "Stopped"
-	StatusKilling    = "Killing"
-	StatusKilled     = "Killed"
-	StatusRemoving   = "Removing"
-	StatusRemoved    = "Removed"
-	StatusBuilding   = "Building"
-	StatusBuilt      = "Built"
-	StatusPulling    = "Pulling"
-	StatusPulled     = "Pulled"
-	StatusCommitting = "Committing"
-	StatusCommitted  = "Committed"
-	StatusCopying    = "Copying"
-	StatusCopied     = "Copied"
-	StatusExporting  = "Exporting"
-	StatusExported   = "Exported"
+	StatusError            = "Error"
+	StatusCreating         = "Creating"
+	StatusStarting         = "Starting"
+	StatusStarted          = "Started"
+	StatusWaiting          = "Waiting"
+	StatusHealthy          = "Healthy"
+	StatusExited           = "Exited"
+	StatusRestarting       = "Restarting"
+	StatusRestarted        = "Restarted"
+	StatusRunning          = "Running"
+	StatusCreated          = "Created"
+	StatusStopping         = "Stopping"
+	StatusStopped          = "Stopped"
+	StatusKilling          = "Killing"
+	StatusKilled           = "Killed"
+	StatusRemoving         = "Removing"
+	StatusRemoved          = "Removed"
+	StatusBuilding         = "Building"
+	StatusBuilt            = "Built"
+	StatusPulling          = "Pulling"
+	StatusPulled           = "Pulled"
+	StatusCommitting       = "Committing"
+	StatusCommitted        = "Committed"
+	StatusCopying          = "Copying"
+	StatusCopied           = "Copied"
+	StatusExporting        = "Exporting"
+	StatusExported         = "Exported"
+	StatusDownloading      = "Downloading"
+	StatusDownloadComplete = "Download complete"
 )
 
 // Resource represents status change and progress for a compose resource.

+ 9 - 8
pkg/compose/pull.go

@@ -398,14 +398,14 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.E
 	}
 
 	var (
-		text    string
-		total   int64
-		percent int
-		current int64
-		status  = api.Working
+		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,
 	})
 }