github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/resource/cmd/formatter.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cmd
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"strings"
    10  
    11  	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/juju/resource"
    15  	"gopkg.in/juju/names.v2"
    16  )
    17  
    18  type charmResourcesFormatter struct {
    19  	resources []charmresource.Resource
    20  }
    21  
    22  func newCharmResourcesFormatter(resources []charmresource.Resource) *charmResourcesFormatter {
    23  	// Note that unlike the "juju status" code, we don't worry
    24  	// about "compatVersion".
    25  	crf := charmResourcesFormatter{
    26  		resources: resources,
    27  	}
    28  	return &crf
    29  }
    30  
    31  func (crf *charmResourcesFormatter) format() []FormattedCharmResource {
    32  	if crf.resources == nil {
    33  		return nil
    34  	}
    35  
    36  	var formatted []FormattedCharmResource
    37  	for _, res := range crf.resources {
    38  		formatted = append(formatted, FormatCharmResource(res))
    39  	}
    40  	return formatted
    41  }
    42  
    43  // FormatCharmResource converts the resource info into a FormattedCharmResource.
    44  func FormatCharmResource(res charmresource.Resource) FormattedCharmResource {
    45  	return FormattedCharmResource{
    46  		Name:        res.Name,
    47  		Type:        res.Type.String(),
    48  		Path:        res.Path,
    49  		Description: res.Description,
    50  		Revision:    res.Revision,
    51  		Origin:      res.Origin.String(),
    52  		Fingerprint: res.Fingerprint.String(), // ...the hex string.
    53  		Size:        res.Size,
    54  	}
    55  }
    56  
    57  // FormatSvcResource converts the resource info into a FormattedServiceResource.
    58  func FormatSvcResource(res resource.Resource) FormattedSvcResource {
    59  	used := !res.IsPlaceholder()
    60  	return FormattedSvcResource{
    61  		ID:               res.ID,
    62  		ApplicationID:    res.ApplicationID,
    63  		Name:             res.Name,
    64  		Type:             res.Type.String(),
    65  		Path:             res.Path,
    66  		Description:      res.Description,
    67  		Revision:         res.Revision,
    68  		Origin:           res.Origin.String(),
    69  		Fingerprint:      res.Fingerprint.String(),
    70  		Size:             res.Size,
    71  		Used:             used,
    72  		Timestamp:        res.Timestamp,
    73  		Username:         res.Username,
    74  		combinedRevision: combinedRevision(res),
    75  		combinedOrigin:   combinedOrigin(used, res),
    76  		usedYesNo:        usedYesNo(used),
    77  	}
    78  }
    79  
    80  func formatServiceResources(sr resource.ServiceResources) (FormattedServiceInfo, error) {
    81  	var formatted FormattedServiceInfo
    82  	updates, err := sr.Updates()
    83  	if err != nil {
    84  		return formatted, errors.Trace(err)
    85  	}
    86  	formatted = FormattedServiceInfo{
    87  		Resources: make([]FormattedSvcResource, len(sr.Resources)),
    88  		Updates:   make([]FormattedCharmResource, len(updates)),
    89  	}
    90  
    91  	for i, r := range sr.Resources {
    92  		formatted.Resources[i] = FormatSvcResource(r)
    93  	}
    94  	for i, u := range updates {
    95  		formatted.Updates[i] = FormatCharmResource(u)
    96  	}
    97  	return formatted, nil
    98  }
    99  
   100  // FormatServiceDetails converts a ServiceResources value into a formatted value
   101  // for display on the command line.
   102  func FormatServiceDetails(sr resource.ServiceResources) (FormattedServiceDetails, error) {
   103  	var formatted FormattedServiceDetails
   104  	details, err := detailedResources("", sr)
   105  	if err != nil {
   106  		return formatted, errors.Trace(err)
   107  	}
   108  	updates, err := sr.Updates()
   109  	if err != nil {
   110  		return formatted, errors.Trace(err)
   111  	}
   112  	formatted = FormattedServiceDetails{
   113  		Resources: details,
   114  		Updates:   make([]FormattedCharmResource, len(updates)),
   115  	}
   116  	for i, u := range updates {
   117  		formatted.Updates[i] = FormatCharmResource(u)
   118  	}
   119  	return formatted, nil
   120  }
   121  
   122  // FormatDetailResource converts the arguments into a FormattedServiceResource.
   123  func FormatDetailResource(tag names.UnitTag, svc, unit resource.Resource, progress int64) (FormattedDetailResource, error) {
   124  	// note that the unit resource can be a zero value here, to indicate that
   125  	// the unit has not downloaded that resource yet.
   126  
   127  	unitNum, err := unitNum(tag)
   128  	if err != nil {
   129  		return FormattedDetailResource{}, errors.Trace(err)
   130  	}
   131  	progressStr := ""
   132  	fUnit := FormatSvcResource(unit)
   133  	expected := FormatSvcResource(svc)
   134  	revProgress := expected.combinedRevision
   135  	if progress >= 0 {
   136  		progressStr = "100%"
   137  		if expected.Size > 0 {
   138  			progressStr = fmt.Sprintf("%.f%%", float64(progress)*100.0/float64(expected.Size))
   139  		}
   140  		if fUnit.combinedRevision != expected.combinedRevision {
   141  			revProgress = fmt.Sprintf("%s (fetching: %s)", expected.combinedRevision, progressStr)
   142  		}
   143  	}
   144  	return FormattedDetailResource{
   145  		UnitID:      tag.Id(),
   146  		unitNumber:  unitNum,
   147  		Unit:        fUnit,
   148  		Expected:    expected,
   149  		Progress:    progress,
   150  		progress:    progressStr,
   151  		revProgress: revProgress,
   152  	}, nil
   153  }
   154  
   155  func combinedRevision(r resource.Resource) string {
   156  	switch r.Origin {
   157  	case charmresource.OriginStore:
   158  		return fmt.Sprintf("%d", r.Revision)
   159  	case charmresource.OriginUpload:
   160  		if !r.Timestamp.IsZero() {
   161  			return r.Timestamp.Format("2006-02-01T15:04")
   162  		}
   163  	}
   164  	return "-"
   165  }
   166  
   167  func combinedOrigin(used bool, r resource.Resource) string {
   168  	if r.Origin == charmresource.OriginUpload && used && r.Username != "" {
   169  		return r.Username
   170  	}
   171  	if r.Origin == charmresource.OriginStore {
   172  		return "charmstore"
   173  	}
   174  	return r.Origin.String()
   175  }
   176  
   177  func usedYesNo(used bool) string {
   178  	if used {
   179  		return "yes"
   180  	}
   181  	return "no"
   182  }
   183  
   184  func unitNum(unit names.UnitTag) (int, error) {
   185  	vals := strings.SplitN(unit.Id(), "/", 2)
   186  	if len(vals) != 2 {
   187  		return 0, errors.Errorf("%q is not a valid unit ID", unit.Id())
   188  	}
   189  	num, err := strconv.Atoi(vals[1])
   190  	if err != nil {
   191  		return 0, errors.Annotatef(err, "%q is not a valid unit ID", unit.Id())
   192  	}
   193  	return num, nil
   194  }
   195  
   196  // detailedResources shows the version of each resource on each unit, with the
   197  // corresponding version of the resource that exists in the controller. if unit
   198  // is non-empty, only units matching that unitID will be returned.
   199  func detailedResources(unit string, sr resource.ServiceResources) ([]FormattedDetailResource, error) {
   200  	var formatted []FormattedDetailResource
   201  	for _, ur := range sr.UnitResources {
   202  		if unit == "" || unit == ur.Tag.Id() {
   203  			units := resourceMap(ur.Resources)
   204  			for _, svc := range sr.Resources {
   205  				progress, ok := ur.DownloadProgress[svc.Name]
   206  				if !ok {
   207  					progress = -1
   208  				}
   209  				f, err := FormatDetailResource(ur.Tag, svc, units[svc.Name], progress)
   210  				if err != nil {
   211  					return nil, errors.Trace(err)
   212  				}
   213  				formatted = append(formatted, f)
   214  			}
   215  			if unit != "" {
   216  				break
   217  			}
   218  		}
   219  	}
   220  	return formatted, nil
   221  }
   222  
   223  func resourceMap(resources []resource.Resource) map[string]resource.Resource {
   224  	m := make(map[string]resource.Resource, len(resources))
   225  	for _, res := range resources {
   226  		m[res.Name] = res
   227  	}
   228  	return m
   229  }