github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/internal/containerizedengine/progress.go (about) 1 package containerizedengine 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/containerd/containerd/content" 13 "github.com/containerd/containerd/errdefs" 14 "github.com/containerd/containerd/remotes" 15 "github.com/docker/docker/pkg/jsonmessage" 16 digest "github.com/opencontainers/go-digest" 17 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 18 "github.com/sirupsen/logrus" 19 ) 20 21 func showProgress(ctx context.Context, ongoing *jobs, cs content.Store, out io.WriteCloser) { 22 var ( 23 ticker = time.NewTicker(100 * time.Millisecond) 24 start = time.Now() 25 enc = json.NewEncoder(out) 26 statuses = map[string]statusInfo{} 27 done bool 28 ) 29 defer ticker.Stop() 30 31 outer: 32 for { 33 select { 34 case <-ticker.C: 35 36 resolved := "resolved" 37 if !ongoing.isResolved() { 38 resolved = "resolving" 39 } 40 statuses[ongoing.name] = statusInfo{ 41 Ref: ongoing.name, 42 Status: resolved, 43 } 44 keys := []string{ongoing.name} 45 46 activeSeen := map[string]struct{}{} 47 if !done { 48 active, err := cs.ListStatuses(ctx, "") 49 if err != nil { 50 logrus.Debugf("active check failed: %s", err) 51 continue 52 } 53 // update status of active entries! 54 for _, active := range active { 55 statuses[active.Ref] = statusInfo{ 56 Ref: active.Ref, 57 Status: "downloading", 58 Offset: active.Offset, 59 Total: active.Total, 60 StartedAt: active.StartedAt, 61 UpdatedAt: active.UpdatedAt, 62 } 63 activeSeen[active.Ref] = struct{}{} 64 } 65 } 66 67 err := updateNonActive(ctx, ongoing, cs, statuses, &keys, activeSeen, &done, start) 68 if err != nil { 69 continue outer 70 } 71 72 var ordered []statusInfo 73 for _, key := range keys { 74 ordered = append(ordered, statuses[key]) 75 } 76 77 for _, si := range ordered { 78 jm := si.JSONMessage() 79 err := enc.Encode(jm) 80 if err != nil { 81 logrus.Debugf("failed to encode progress message: %s", err) 82 } 83 } 84 85 if done { 86 out.Close() 87 return 88 } 89 case <-ctx.Done(): 90 done = true // allow ui to update once more 91 } 92 } 93 } 94 95 func updateNonActive(ctx context.Context, ongoing *jobs, cs content.Store, statuses map[string]statusInfo, keys *[]string, activeSeen map[string]struct{}, done *bool, start time.Time) error { 96 97 for _, j := range ongoing.jobs() { 98 key := remotes.MakeRefKey(ctx, j) 99 *keys = append(*keys, key) 100 if _, ok := activeSeen[key]; ok { 101 continue 102 } 103 104 status, ok := statuses[key] 105 if !*done && (!ok || status.Status == "downloading") { 106 info, err := cs.Info(ctx, j.Digest) 107 if err != nil { 108 if !errdefs.IsNotFound(err) { 109 logrus.Debugf("failed to get content info: %s", err) 110 return err 111 } 112 statuses[key] = statusInfo{ 113 Ref: key, 114 Status: "waiting", 115 } 116 } else if info.CreatedAt.After(start) { 117 statuses[key] = statusInfo{ 118 Ref: key, 119 Status: "done", 120 Offset: info.Size, 121 Total: info.Size, 122 UpdatedAt: info.CreatedAt, 123 } 124 } else { 125 statuses[key] = statusInfo{ 126 Ref: key, 127 Status: "exists", 128 } 129 } 130 } else if *done { 131 if ok { 132 if status.Status != "done" && status.Status != "exists" { 133 status.Status = "done" 134 statuses[key] = status 135 } 136 } else { 137 statuses[key] = statusInfo{ 138 Ref: key, 139 Status: "done", 140 } 141 } 142 } 143 } 144 return nil 145 } 146 147 type jobs struct { 148 name string 149 added map[digest.Digest]struct{} 150 descs []ocispec.Descriptor 151 mu sync.Mutex 152 resolved bool 153 } 154 155 func newJobs(name string) *jobs { 156 return &jobs{ 157 name: name, 158 added: map[digest.Digest]struct{}{}, 159 } 160 } 161 162 func (j *jobs) add(desc ocispec.Descriptor) { 163 j.mu.Lock() 164 defer j.mu.Unlock() 165 j.resolved = true 166 167 if _, ok := j.added[desc.Digest]; ok { 168 return 169 } 170 j.descs = append(j.descs, desc) 171 j.added[desc.Digest] = struct{}{} 172 } 173 174 func (j *jobs) jobs() []ocispec.Descriptor { 175 j.mu.Lock() 176 defer j.mu.Unlock() 177 178 var descs []ocispec.Descriptor 179 return append(descs, j.descs...) 180 } 181 182 func (j *jobs) isResolved() bool { 183 j.mu.Lock() 184 defer j.mu.Unlock() 185 return j.resolved 186 } 187 188 // statusInfo holds the status info for an upload or download 189 type statusInfo struct { 190 Ref string 191 Status string 192 Offset int64 193 Total int64 194 StartedAt time.Time 195 UpdatedAt time.Time 196 } 197 198 func (s statusInfo) JSONMessage() jsonmessage.JSONMessage { 199 // Shorten the ID to use up less width on the display 200 id := s.Ref 201 if strings.Contains(id, ":") { 202 split := strings.SplitN(id, ":", 2) 203 id = split[1] 204 } 205 id = fmt.Sprintf("%.12s", id) 206 207 return jsonmessage.JSONMessage{ 208 ID: id, 209 Status: s.Status, 210 Progress: &jsonmessage.JSONProgress{ 211 Current: s.Offset, 212 Total: s.Total, 213 }, 214 } 215 }