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 }