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  }