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  }