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