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 }