github.com/moby/docker@v26.1.3+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 if info.Offset == 0 { 139 continue 140 } 141 out.WriteProgress(progress.Progress{ 142 ID: stringid.TruncateID(j.Digest.Encoded()), 143 Action: "Downloading", 144 Current: info.Offset, 145 Total: info.Total, 146 }) 147 continue 148 } 149 150 info, err := p.store.Info(ctx, j.Digest) 151 if err != nil { 152 if !cerrdefs.IsNotFound(err) { 153 return err 154 } 155 } else if info.CreatedAt.After(start) { 156 out.WriteProgress(progress.Progress{ 157 ID: stringid.TruncateID(j.Digest.Encoded()), 158 Action: "Download complete", 159 HideCounts: true, 160 LastUpdate: true, 161 }) 162 ongoing.Remove(j) 163 } else if p.showExists { 164 out.WriteProgress(progress.Progress{ 165 ID: stringid.TruncateID(j.Digest.Encoded()), 166 Action: "Already exists", 167 HideCounts: true, 168 LastUpdate: true, 169 }) 170 ongoing.Remove(j) 171 } 172 } 173 return nil 174 } 175 176 type pushProgress struct { 177 Tracker docker.StatusTracker 178 } 179 180 func (p *pushProgress) UpdateProgress(ctx context.Context, ongoing *jobs, out progress.Output, start time.Time) error { 181 for _, j := range ongoing.Jobs() { 182 key := remotes.MakeRefKey(ctx, j) 183 id := stringid.TruncateID(j.Digest.Encoded()) 184 185 status, err := p.Tracker.GetStatus(key) 186 if err != nil { 187 if cerrdefs.IsNotFound(err) { 188 progress.Update(out, id, "Waiting") 189 continue 190 } 191 } 192 193 if status.Committed && status.Offset >= status.Total { 194 if status.MountedFrom != "" { 195 from := status.MountedFrom 196 if ref, err := reference.ParseNormalizedNamed(from); err == nil { 197 from = reference.Path(ref) 198 } 199 progress.Update(out, id, "Mounted from "+from) 200 } else if status.Exists { 201 if images.IsLayerType(j.MediaType) { 202 progress.Update(out, id, "Layer already exists") 203 } else { 204 progress.Update(out, id, "Already exists") 205 } 206 } else { 207 progress.Update(out, id, "Pushed") 208 } 209 ongoing.Remove(j) 210 continue 211 } 212 213 out.WriteProgress(progress.Progress{ 214 ID: id, 215 Action: "Pushing", 216 Current: status.Offset, 217 Total: status.Total, 218 }) 219 } 220 221 return nil 222 } 223 224 type combinedProgress []progressUpdater 225 226 func (combined combinedProgress) UpdateProgress(ctx context.Context, ongoing *jobs, out progress.Output, start time.Time) error { 227 for _, p := range combined { 228 err := p.UpdateProgress(ctx, ongoing, out, start) 229 if err != nil { 230 return err 231 } 232 } 233 return nil 234 }