github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/status/output_summary.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package status 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "net" 11 "strings" 12 13 "github.com/juju/ansiterm" 14 "github.com/juju/ansiterm/tabwriter" 15 "github.com/juju/collections/set" 16 "github.com/juju/errors" 17 "github.com/juju/naturalsort" 18 19 "github.com/juju/juju/cmd/output" 20 "github.com/juju/juju/core/status" 21 ) 22 23 // FormatSummary writes a summary of the current environment 24 // including the following information: 25 // - Headers: 26 // - All subnets the environment occupies. 27 // - All ports the environment utilizes. 28 // - Sections: 29 // - Machines: Displays total #, and then the # in each state. 30 // - Units: Displays total #, and then # in each state. 31 // - Applications: Displays total #, their names, and how many of each 32 // are exposed. 33 // - RemoteApplications: Displays total #, their names and URLs. 34 func FormatSummary(writer io.Writer, value interface{}) error { 35 fs, valueConverted := value.(formattedStatus) 36 if !valueConverted { 37 return errors.Errorf("expected value of type %T, got %T", fs, value) 38 } 39 40 f := newSummaryFormatter(writer) 41 stateToMachine := f.aggregateMachineStates(fs.Machines) 42 appExposure := f.aggregateApplicationAndUnitStates(fs.Applications) 43 p := f.delimitValuesWithTabs 44 45 // Print everything out 46 p("Running on subnets:", strings.Join(f.netStrings, ", ")) 47 p(" Utilizing ports:", f.portsInColumnsOf(3)) 48 f.tw.Flush() 49 50 // Right align summary information 51 f.tw.Init(writer, 0, 1, 2, ' ', tabwriter.AlignRight) 52 p("# Machines:", fmt.Sprintf("(%d)", len(fs.Machines))) 53 f.printStateToCount(stateToMachine) 54 p(" ") 55 56 p("# Units:", fmt.Sprintf("(%d)", f.numUnits)) 57 f.printStateToCount(f.stateToUnit) 58 p(" ") 59 60 p("# Applications:", fmt.Sprintf("(%d)", len(fs.Applications))) 61 for _, appName := range naturalsort.Sort(stringKeysFromMap(appExposure)) { 62 s := appExposure[appName] 63 p(appName, fmt.Sprintf("%d/%d\texposed", s[true], s[true]+s[false])) 64 } 65 p(" ") 66 67 p("# Remote:", fmt.Sprintf("(%d)", len(fs.RemoteApplications))) 68 for _, svcName := range naturalsort.Sort(stringKeysFromMap(fs.RemoteApplications)) { 69 s := fs.RemoteApplications[svcName] 70 p(svcName, "", s.OfferURL) 71 } 72 f.tw.Flush() 73 74 return nil 75 } 76 77 func newSummaryFormatter(writer io.Writer) *summaryFormatter { 78 f := &summaryFormatter{ 79 ipAddrs: make([]net.IPNet, 0), 80 netStrings: make([]string, 0), 81 openPorts: set.NewStrings(), 82 stateToUnit: make(map[status.Status]int), 83 } 84 f.tw = output.TabWriter(writer) 85 return f 86 } 87 88 type summaryFormatter struct { 89 ipAddrs []net.IPNet 90 netStrings []string 91 numUnits int 92 openPorts set.Strings 93 // status -> count 94 stateToUnit map[status.Status]int 95 tw *ansiterm.TabWriter 96 } 97 98 func (f *summaryFormatter) delimitValuesWithTabs(values ...string) { 99 for _, v := range values { 100 fmt.Fprintf(f.tw, "%s\t", v) 101 } 102 fmt.Fprintln(f.tw) 103 } 104 105 func (f *summaryFormatter) portsInColumnsOf(col int) string { 106 107 var b bytes.Buffer 108 for i, p := range f.openPorts.SortedValues() { 109 if i != 0 && i%col == 0 { 110 fmt.Fprintf(&b, "\n\t") 111 } 112 fmt.Fprintf(&b, "%s, ", p) 113 } 114 // Elide the last delimiter 115 portList := b.String() 116 if len(portList) >= 2 { 117 return portList[:b.Len()-2] 118 } 119 return portList 120 } 121 122 func (f *summaryFormatter) trackUnit(name string, status unitStatus, indentLevel int) { 123 f.resolveAndTrackIp(status.PublicAddress) 124 125 for _, p := range status.OpenedPorts { 126 if p != "" { 127 f.openPorts.Add(p) 128 } 129 } 130 f.numUnits++ 131 f.stateToUnit[status.WorkloadStatusInfo.Current]++ 132 } 133 134 func (f *summaryFormatter) printStateToCount(m map[status.Status]int) { 135 for _, stateToCount := range naturalsort.Sort(stringKeysFromMap(m)) { 136 numInStatus := m[status.Status(stateToCount)] 137 f.delimitValuesWithTabs(stateToCount+":", fmt.Sprintf(" %d ", numInStatus)) 138 } 139 } 140 141 func (f *summaryFormatter) trackIp(ip net.IP) { 142 for _, net := range f.ipAddrs { 143 if net.Contains(ip) { 144 return 145 } 146 } 147 148 ipNet := net.IPNet{IP: ip, Mask: ip.DefaultMask()} 149 f.ipAddrs = append(f.ipAddrs, ipNet) 150 f.netStrings = append(f.netStrings, ipNet.String()) 151 } 152 153 func (f *summaryFormatter) resolveAndTrackIp(publicDns string) { 154 // TODO(katco-): We may be able to utilize upcoming work which will expose these addresses outright. 155 ip, err := net.ResolveIPAddr("ip4", publicDns) 156 if err != nil { 157 logger.Warningf( 158 "unable to resolve %s to an IP address. Status may be incorrect: %v", 159 publicDns, 160 err, 161 ) 162 return 163 } 164 f.trackIp(ip.IP) 165 } 166 167 func (f *summaryFormatter) aggregateMachineStates(machines map[string]machineStatus) map[status.Status]int { 168 stateToMachine := make(map[status.Status]int) 169 for _, name := range naturalsort.Sort(stringKeysFromMap(machines)) { 170 m := machines[name] 171 f.resolveAndTrackIp(m.DNSName) 172 173 if agentState := m.JujuStatus.Current; agentState == "" { 174 agentState = status.Pending 175 } else { 176 stateToMachine[agentState]++ 177 } 178 } 179 return stateToMachine 180 } 181 182 func (f *summaryFormatter) aggregateApplicationAndUnitStates(applications map[string]applicationStatus) map[string]map[bool]int { 183 svcExposure := make(map[string]map[bool]int) 184 for _, name := range naturalsort.Sort(stringKeysFromMap(applications)) { 185 s := applications[name] 186 // Grab unit states 187 for _, un := range naturalsort.Sort(stringKeysFromMap(s.Units)) { 188 u := s.Units[un] 189 f.trackUnit(un, u, 0) 190 recurseUnits(u, 1, f.trackUnit) 191 } 192 193 if _, ok := svcExposure[name]; !ok { 194 svcExposure[name] = make(map[bool]int) 195 } 196 197 svcExposure[name][s.Exposed]++ 198 } 199 return svcExposure 200 }