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 }