github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/resource/formatter.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package resource
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  	charmresource "gopkg.in/juju/charm.v6/resource"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/resource"
    17  )
    18  
    19  type charmResourcesFormatter struct {
    20  	resources []charmresource.Resource
    21  }
    22  
    23  func newCharmResourcesFormatter(resources []charmresource.Resource) *charmResourcesFormatter {
    24  	// It's a lot easier to read and to digest a list of resources
    25  	// when  they are ordered.
    26  	sort.Sort(charmResourceList(resources))
    27  
    28  	// Note that unlike the "juju status" code, we don't worry
    29  	// about "compatVersion".
    30  	crf := charmResourcesFormatter{
    31  		resources: resources,
    32  	}
    33  	return &crf
    34  }
    35  
    36  func (crf *charmResourcesFormatter) format() []FormattedCharmResource {
    37  	if crf.resources == nil {
    38  		return nil
    39  	}
    40  
    41  	var formatted []FormattedCharmResource
    42  	for _, res := range crf.resources {
    43  		formatted = append(formatted, FormatCharmResource(res))
    44  	}
    45  	return formatted
    46  }
    47  
    48  // FormatCharmResource converts the resource info into a FormattedCharmResource.
    49  func FormatCharmResource(res charmresource.Resource) FormattedCharmResource {
    50  	return FormattedCharmResource{
    51  		Name:        res.Name,
    52  		Type:        res.Type.String(),
    53  		Path:        res.Path,
    54  		Description: res.Description,
    55  		Revision:    res.Revision,
    56  		Origin:      res.Origin.String(),
    57  		Fingerprint: res.Fingerprint.String(), // ...the hex string.
    58  		Size:        res.Size,
    59  	}
    60  }
    61  
    62  // FormatAppResource converts the resource info into a FormattedAppResource.
    63  func FormatAppResource(res resource.Resource) FormattedAppResource {
    64  	used := !res.IsPlaceholder()
    65  	result := FormattedAppResource{
    66  		ID:               res.ID,
    67  		ApplicationID:    res.ApplicationID,
    68  		Name:             res.Name,
    69  		Type:             res.Type.String(),
    70  		Path:             res.Path,
    71  		Description:      res.Description,
    72  		Origin:           res.Origin.String(),
    73  		Fingerprint:      res.Fingerprint.String(),
    74  		Size:             res.Size,
    75  		Used:             used,
    76  		Timestamp:        res.Timestamp,
    77  		Username:         res.Username,
    78  		CombinedRevision: combinedRevision(res),
    79  		CombinedOrigin:   combinedOrigin(used, res),
    80  		UsedYesNo:        usedYesNo(used),
    81  	}
    82  	// Have to check since revision 0 is still a valid revision.
    83  	if res.Revision >= 0 {
    84  		result.Revision = fmt.Sprintf("%v", res.Revision)
    85  	} else {
    86  		result.Revision = "-"
    87  	}
    88  	return result
    89  }
    90  
    91  func formatApplicationResources(sr resource.ApplicationResources) (FormattedApplicationInfo, error) {
    92  	var formatted FormattedApplicationInfo
    93  	updates, err := sr.Updates()
    94  	if err != nil {
    95  		return formatted, errors.Trace(err)
    96  	}
    97  	formatted = FormattedApplicationInfo{
    98  		Resources: make([]FormattedAppResource, len(sr.Resources)),
    99  		Updates:   make([]FormattedCharmResource, len(updates)),
   100  	}
   101  
   102  	for i, r := range sr.Resources {
   103  		formatted.Resources[i] = FormatAppResource(r)
   104  	}
   105  	for i, u := range updates {
   106  		formatted.Updates[i] = FormatCharmResource(u)
   107  	}
   108  	return formatted, nil
   109  }
   110  
   111  // FormatApplicationDetails converts a ApplicationResources value into a formatted value
   112  // for display on the command line.
   113  func FormatApplicationDetails(sr resource.ApplicationResources) (FormattedApplicationDetails, error) {
   114  	var formatted FormattedApplicationDetails
   115  	details, err := detailedResources("", sr)
   116  	if err != nil {
   117  		return formatted, errors.Trace(err)
   118  	}
   119  	updates, err := sr.Updates()
   120  	if err != nil {
   121  		return formatted, errors.Trace(err)
   122  	}
   123  	formatted = FormattedApplicationDetails{
   124  		Resources: details,
   125  		Updates:   make([]FormattedCharmResource, len(updates)),
   126  	}
   127  	for i, u := range updates {
   128  		formatted.Updates[i] = FormatCharmResource(u)
   129  	}
   130  	return formatted, nil
   131  }
   132  
   133  // FormatDetailResource converts the arguments into a FormattedApplicationResource.
   134  func FormatDetailResource(tag names.UnitTag, svc, unit resource.Resource, progress int64) (FormattedDetailResource, error) {
   135  	// note that the unit resource can be a zero value here, to indicate that
   136  	// the unit has not downloaded that resource yet.
   137  
   138  	unitNum, err := unitNum(tag)
   139  	if err != nil {
   140  		return FormattedDetailResource{}, errors.Trace(err)
   141  	}
   142  	progressStr := ""
   143  	fUnit := FormatAppResource(unit)
   144  	expected := FormatAppResource(svc)
   145  	revProgress := expected.CombinedRevision
   146  	if progress >= 0 {
   147  		progressStr = "100%"
   148  		if expected.Size > 0 {
   149  			progressStr = fmt.Sprintf("%.f%%", float64(progress)*100.0/float64(expected.Size))
   150  		}
   151  		if fUnit.CombinedRevision != expected.CombinedRevision {
   152  			revProgress = fmt.Sprintf("%s (fetching: %s)", expected.CombinedRevision, progressStr)
   153  		}
   154  	}
   155  	return FormattedDetailResource{
   156  		UnitID:      tag.Id(),
   157  		UnitNumber:  unitNum,
   158  		Unit:        fUnit,
   159  		Expected:    expected,
   160  		Progress:    progress,
   161  		RevProgress: revProgress,
   162  	}, nil
   163  }
   164  
   165  func combinedRevision(r resource.Resource) string {
   166  	switch r.Origin {
   167  	case charmresource.OriginStore:
   168  		// Have to check since 0+ is a valid revision number
   169  		if r.Revision >= 0 {
   170  			return fmt.Sprintf("%d", r.Revision)
   171  		}
   172  	case charmresource.OriginUpload:
   173  		if !r.Timestamp.IsZero() {
   174  			return r.Timestamp.Format("2006-02-01T15:04")
   175  		}
   176  	}
   177  	return "-"
   178  }
   179  
   180  func combinedOrigin(used bool, r resource.Resource) string {
   181  	if r.Origin == charmresource.OriginUpload && used && r.Username != "" {
   182  		return r.Username
   183  	}
   184  	if r.Origin == charmresource.OriginStore {
   185  		return "charmstore"
   186  	}
   187  	return r.Origin.String()
   188  }
   189  
   190  func usedYesNo(used bool) string {
   191  	if used {
   192  		return "yes"
   193  	}
   194  	return "no"
   195  }
   196  
   197  func unitNum(unit names.UnitTag) (int, error) {
   198  	vals := strings.SplitN(unit.Id(), "/", 2)
   199  	if len(vals) != 2 {
   200  		return 0, errors.Errorf("%q is not a valid unit ID", unit.Id())
   201  	}
   202  	num, err := strconv.Atoi(vals[1])
   203  	if err != nil {
   204  		return 0, errors.Annotatef(err, "%q is not a valid unit ID", unit.Id())
   205  	}
   206  	return num, nil
   207  }
   208  
   209  // detailedResources shows the version of each resource on each unit, with the
   210  // corresponding version of the resource that exists in the controller. if unit
   211  // is non-empty, only units matching that unitID will be returned.
   212  func detailedResources(unit string, sr resource.ApplicationResources) ([]FormattedDetailResource, error) {
   213  	var formatted []FormattedDetailResource
   214  	for _, ur := range sr.UnitResources {
   215  		if unit == "" || unit == ur.Tag.Id() {
   216  			units := resourceMap(ur.Resources)
   217  			for _, svc := range sr.Resources {
   218  				progress, ok := ur.DownloadProgress[svc.Name]
   219  				if !ok {
   220  					progress = -1
   221  				}
   222  				f, err := FormatDetailResource(ur.Tag, svc, units[svc.Name], progress)
   223  				if err != nil {
   224  					return nil, errors.Trace(err)
   225  				}
   226  				formatted = append(formatted, f)
   227  			}
   228  			if unit != "" {
   229  				break
   230  			}
   231  		}
   232  	}
   233  	return formatted, nil
   234  }
   235  
   236  func resourceMap(resources []resource.Resource) map[string]resource.Resource {
   237  	m := make(map[string]resource.Resource, len(resources))
   238  	for _, res := range resources {
   239  		m[res.Name] = res
   240  	}
   241  	return m
   242  }
   243  
   244  // charmResourceList is a convenience type enabling to sort
   245  // a collection of charmresource.Resource by Name.
   246  type charmResourceList []charmresource.Resource
   247  
   248  // Len implements sort.Interface
   249  func (m charmResourceList) Len() int {
   250  	return len(m)
   251  }
   252  
   253  // Less implements sort.Interface and sorts resources by Name.
   254  func (m charmResourceList) Less(i, j int) bool {
   255  	return m[i].Name < m[j].Name
   256  }
   257  
   258  // Swap implements sort.Interface
   259  func (m charmResourceList) Swap(i, j int) {
   260  	m[i], m[j] = m[j], m[i]
   261  }
   262  
   263  // resourceList is a convenience type enabling to sort
   264  // a collection of resource.Resource by Name.
   265  type resourceList []resource.Resource
   266  
   267  // Len implements sort.Interface
   268  func (m resourceList) Len() int {
   269  	return len(m)
   270  }
   271  
   272  // Less implements sort.Interface and sorts resources by Name.
   273  func (m resourceList) Less(i, j int) bool {
   274  	return m[i].Name < m[j].Name
   275  }
   276  
   277  // Swap implements sort.Interface
   278  func (m resourceList) Swap(i, j int) {
   279  	m[i], m[j] = m[j], m[i]
   280  }