github.com/rawahars/moby@v24.0.4+incompatible/daemon/containerd/progress.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/containerd/containerd/content"
    10  	cerrdefs "github.com/containerd/containerd/errdefs"
    11  	"github.com/containerd/containerd/remotes"
    12  	"github.com/containerd/containerd/remotes/docker"
    13  	"github.com/docker/docker/pkg/progress"
    14  	"github.com/docker/docker/pkg/stringid"
    15  	"github.com/opencontainers/go-digest"
    16  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  type progressUpdater interface {
    21  	UpdateProgress(context.Context, *jobs, progress.Output, time.Time) error
    22  }
    23  
    24  type jobs struct {
    25  	descs map[digest.Digest]ocispec.Descriptor
    26  	mu    sync.Mutex
    27  }
    28  
    29  // newJobs creates a new instance of the job status tracker
    30  func newJobs() *jobs {
    31  	return &jobs{
    32  		descs: map[digest.Digest]ocispec.Descriptor{},
    33  	}
    34  }
    35  
    36  func (j *jobs) showProgress(ctx context.Context, out progress.Output, updater progressUpdater) func() {
    37  	ctx, cancelProgress := context.WithCancel(ctx)
    38  
    39  	start := time.Now()
    40  	lastUpdate := make(chan struct{})
    41  
    42  	go func() {
    43  		ticker := time.NewTicker(100 * time.Millisecond)
    44  		defer ticker.Stop()
    45  
    46  		for {
    47  			select {
    48  			case <-ticker.C:
    49  				if err := updater.UpdateProgress(ctx, j, out, start); err != nil {
    50  					if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) {
    51  						logrus.WithError(err).Error("Updating progress failed")
    52  					}
    53  				}
    54  			case <-ctx.Done():
    55  				ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
    56  				defer cancel()
    57  				updater.UpdateProgress(ctx, j, out, start)
    58  				close(lastUpdate)
    59  				return
    60  			}
    61  		}
    62  	}()
    63  
    64  	return func() {
    65  		cancelProgress()
    66  		// Wait for the last update to finish.
    67  		// UpdateProgress may still write progress to output and we need
    68  		// to keep the caller from closing it before we finish.
    69  		<-lastUpdate
    70  	}
    71  }
    72  
    73  // Add adds a descriptor to be tracked
    74  func (j *jobs) Add(desc ...ocispec.Descriptor) {
    75  	j.mu.Lock()
    76  	defer j.mu.Unlock()
    77  
    78  	for _, d := range desc {
    79  		if _, ok := j.descs[d.Digest]; ok {
    80  			continue
    81  		}
    82  		j.descs[d.Digest] = d
    83  	}
    84  }
    85  
    86  // Remove removes a descriptor
    87  func (j *jobs) Remove(desc ocispec.Descriptor) {
    88  	j.mu.Lock()
    89  	defer j.mu.Unlock()
    90  
    91  	delete(j.descs, desc.Digest)
    92  }
    93  
    94  // Jobs returns a list of all tracked descriptors
    95  func (j *jobs) Jobs() []ocispec.Descriptor {
    96  	j.mu.Lock()
    97  	defer j.mu.Unlock()
    98  
    99  	descs := make([]ocispec.Descriptor, 0, len(j.descs))
   100  	for _, d := range j.descs {
   101  		descs = append(descs, d)
   102  	}
   103  	return descs
   104  }
   105  
   106  type pullProgress struct {
   107  	Store      content.Store
   108  	ShowExists bool
   109  }
   110  
   111  func (p pullProgress) UpdateProgress(ctx context.Context, ongoing *jobs, out progress.Output, start time.Time) error {
   112  	actives, err := p.Store.ListStatuses(ctx, "")
   113  	if err != nil {
   114  		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
   115  			return err
   116  		}
   117  		logrus.WithError(err).Error("status check failed")
   118  		return nil
   119  	}
   120  	pulling := make(map[string]content.Status, len(actives))
   121  
   122  	// update status of status entries!
   123  	for _, status := range actives {
   124  		pulling[status.Ref] = status
   125  	}
   126  
   127  	for _, j := range ongoing.Jobs() {
   128  		key := remotes.MakeRefKey(ctx, j)
   129  		if info, ok := pulling[key]; ok {
   130  			out.WriteProgress(progress.Progress{
   131  				ID:      stringid.TruncateID(j.Digest.Encoded()),
   132  				Action:  "Downloading",
   133  				Current: info.Offset,
   134  				Total:   info.Total,
   135  			})
   136  			continue
   137  		}
   138  
   139  		info, err := p.Store.Info(ctx, j.Digest)
   140  		if err != nil {
   141  			if !cerrdefs.IsNotFound(err) {
   142  				return err
   143  			}
   144  		} else if info.CreatedAt.After(start) {
   145  			out.WriteProgress(progress.Progress{
   146  				ID:         stringid.TruncateID(j.Digest.Encoded()),
   147  				Action:     "Download complete",
   148  				HideCounts: true,
   149  				LastUpdate: true,
   150  			})
   151  			ongoing.Remove(j)
   152  		} else if p.ShowExists {
   153  			out.WriteProgress(progress.Progress{
   154  				ID:         stringid.TruncateID(j.Digest.Encoded()),
   155  				Action:     "Already exists",
   156  				HideCounts: true,
   157  				LastUpdate: true,
   158  			})
   159  			ongoing.Remove(j)
   160  		}
   161  	}
   162  	return nil
   163  }
   164  
   165  type pushProgress struct {
   166  	Tracker   docker.StatusTracker
   167  	mountable map[digest.Digest]struct{}
   168  	mutex     sync.Mutex
   169  }
   170  
   171  func (p *pushProgress) addMountable(dgst digest.Digest) {
   172  	p.mutex.Lock()
   173  	defer p.mutex.Unlock()
   174  
   175  	if p.mountable == nil {
   176  		p.mountable = map[digest.Digest]struct{}{}
   177  	}
   178  	p.mountable[dgst] = struct{}{}
   179  }
   180  
   181  func (p *pushProgress) isMountable(dgst digest.Digest) bool {
   182  	p.mutex.Lock()
   183  	defer p.mutex.Unlock()
   184  
   185  	if p.mountable == nil {
   186  		return false
   187  	}
   188  	_, has := p.mountable[dgst]
   189  	return has
   190  }
   191  
   192  func (p *pushProgress) UpdateProgress(ctx context.Context, ongoing *jobs, out progress.Output, start time.Time) error {
   193  	for _, j := range ongoing.Jobs() {
   194  		key := remotes.MakeRefKey(ctx, j)
   195  		id := stringid.TruncateID(j.Digest.Encoded())
   196  
   197  		status, err := p.Tracker.GetStatus(key)
   198  		if err != nil {
   199  			if cerrdefs.IsNotFound(err) {
   200  				progress.Update(out, id, "Waiting")
   201  				continue
   202  			}
   203  		}
   204  
   205  		if status.Committed && status.Offset >= status.Total {
   206  			if p.isMountable(j.Digest) {
   207  				progress.Update(out, id, "Mounted")
   208  			} else {
   209  				progress.Update(out, id, "Pushed")
   210  			}
   211  			ongoing.Remove(j)
   212  			continue
   213  		}
   214  
   215  		out.WriteProgress(progress.Progress{
   216  			ID:      id,
   217  			Action:  "Pushing",
   218  			Current: status.Offset,
   219  			Total:   status.Total,
   220  		})
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  type combinedProgress []progressUpdater
   227  
   228  func (combined combinedProgress) UpdateProgress(ctx context.Context, ongoing *jobs, out progress.Output, start time.Time) error {
   229  	for _, p := range combined {
   230  		err := p.UpdateProgress(ctx, ongoing, out, start)
   231  		if err != nil {
   232  			return err
   233  		}
   234  	}
   235  	return nil
   236  }