github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/status/output_tabular.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  	"regexp"
    10  	"sort"
    11  	"strings"
    12  	"text/tabwriter"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/utils/set"
    16  	"gopkg.in/juju/charm.v6-unstable/hooks"
    17  
    18  	"github.com/juju/juju/cmd/juju/common"
    19  	"github.com/juju/juju/instance"
    20  	"github.com/juju/juju/status"
    21  )
    22  
    23  type statusRelation struct {
    24  	service1    string
    25  	service2    string
    26  	relation    string
    27  	subordinate bool
    28  }
    29  
    30  func (s *statusRelation) relationType() string {
    31  	if s.subordinate {
    32  		return "subordinate"
    33  	} else if s.service1 == s.service2 {
    34  		return "peer"
    35  	}
    36  	return "regular"
    37  }
    38  
    39  type relationFormatter struct {
    40  	relationIndex set.Strings
    41  	relations     map[string]*statusRelation
    42  }
    43  
    44  func newRelationFormatter() *relationFormatter {
    45  	return &relationFormatter{
    46  		relationIndex: set.NewStrings(),
    47  		relations:     make(map[string]*statusRelation),
    48  	}
    49  }
    50  
    51  func (r *relationFormatter) len() int {
    52  	return r.relationIndex.Size()
    53  }
    54  
    55  func (r *relationFormatter) add(rel1, rel2, relation string, is2SubOf1 bool) {
    56  	rel := []string{rel1, rel2}
    57  	if !is2SubOf1 {
    58  		sort.Sort(sort.StringSlice(rel))
    59  	}
    60  	k := strings.Join(rel, "\t")
    61  	r.relations[k] = &statusRelation{
    62  		service1:    rel[0],
    63  		service2:    rel[1],
    64  		relation:    relation,
    65  		subordinate: is2SubOf1,
    66  	}
    67  	r.relationIndex.Add(k)
    68  }
    69  
    70  func (r *relationFormatter) sorted() []string {
    71  	return r.relationIndex.SortedValues()
    72  }
    73  
    74  func (r *relationFormatter) get(k string) *statusRelation {
    75  	return r.relations[k]
    76  }
    77  
    78  // FormatTabular returns a tabular summary of machines, services, and
    79  // units. Any subordinate items are indented by two spaces beneath
    80  // their superior.
    81  func FormatTabular(value interface{}) ([]byte, error) {
    82  	fs, valueConverted := value.(formattedStatus)
    83  	if !valueConverted {
    84  		return nil, errors.Errorf("expected value of type %T, got %T", fs, value)
    85  	}
    86  	var out bytes.Buffer
    87  	// To format things into columns.
    88  	tw := tabwriter.NewWriter(&out, 0, 1, 1, ' ', 0)
    89  	p := func(values ...interface{}) {
    90  		for _, v := range values {
    91  			fmt.Fprintf(tw, "%s\t", v)
    92  		}
    93  		fmt.Fprintln(tw)
    94  	}
    95  
    96  	if envStatus := fs.ModelStatus; envStatus != nil {
    97  		p("[Model]")
    98  		if envStatus.AvailableVersion != "" {
    99  			p("UPGRADE-AVAILABLE")
   100  			p(envStatus.AvailableVersion)
   101  		}
   102  		p()
   103  		tw.Flush()
   104  	}
   105  
   106  	units := make(map[string]unitStatus)
   107  	metering := false
   108  	relations := newRelationFormatter()
   109  	p("[Services]")
   110  	p("NAME\tSTATUS\tEXPOSED\tCHARM")
   111  	for _, svcName := range common.SortStringsNaturally(stringKeysFromMap(fs.Services)) {
   112  		svc := fs.Services[svcName]
   113  		for un, u := range svc.Units {
   114  			units[un] = u
   115  			if u.MeterStatus != nil {
   116  				metering = true
   117  			}
   118  		}
   119  
   120  		subs := set.NewStrings(svc.SubordinateTo...)
   121  		p(svcName, svc.StatusInfo.Current, fmt.Sprintf("%t", svc.Exposed), svc.Charm)
   122  		for relType, relatedUnits := range svc.Relations {
   123  			for _, related := range relatedUnits {
   124  				relations.add(related, svcName, relType, subs.Contains(related))
   125  			}
   126  		}
   127  
   128  	}
   129  	if relations.len() > 0 {
   130  		p()
   131  		p("[Relations]")
   132  		p("SERVICE1\tSERVICE2\tRELATION\tTYPE")
   133  		for _, k := range relations.sorted() {
   134  			r := relations.get(k)
   135  			if r != nil {
   136  				p(r.service1, r.service2, r.relation, r.relationType())
   137  			}
   138  		}
   139  	}
   140  	tw.Flush()
   141  
   142  	pUnit := func(name string, u unitStatus, level int) {
   143  		message := u.WorkloadStatusInfo.Message
   144  		agentDoing := agentDoing(u.JujuStatusInfo)
   145  		if agentDoing != "" {
   146  			message = fmt.Sprintf("(%s) %s", agentDoing, message)
   147  		}
   148  		p(
   149  			indent("", level*2, name),
   150  			u.WorkloadStatusInfo.Current,
   151  			u.JujuStatusInfo.Current,
   152  			u.JujuStatusInfo.Version,
   153  			u.Machine,
   154  			strings.Join(u.OpenedPorts, ","),
   155  			u.PublicAddress,
   156  			message,
   157  		)
   158  	}
   159  
   160  	header := []string{"ID", "WORKLOAD-STATUS", "JUJU-STATUS", "VERSION", "MACHINE", "PORTS", "PUBLIC-ADDRESS", "MESSAGE"}
   161  
   162  	p("\n[Units]")
   163  	p(strings.Join(header, "\t"))
   164  	for _, name := range common.SortStringsNaturally(stringKeysFromMap(units)) {
   165  		u := units[name]
   166  		pUnit(name, u, 0)
   167  		const indentationLevel = 1
   168  		recurseUnits(u, indentationLevel, pUnit)
   169  	}
   170  	tw.Flush()
   171  
   172  	if metering {
   173  		p("\n[Metering]")
   174  		p("ID\tSTATUS\tMESSAGE")
   175  		for _, name := range common.SortStringsNaturally(stringKeysFromMap(units)) {
   176  			u := units[name]
   177  			if u.MeterStatus != nil {
   178  				p(name, u.MeterStatus.Color, u.MeterStatus.Message)
   179  			}
   180  		}
   181  	}
   182  
   183  	p("\n[Machines]")
   184  	p("ID\tSTATE\tDNS\tINS-ID\tSERIES\tAZ")
   185  	for _, name := range common.SortStringsNaturally(stringKeysFromMap(fs.Machines)) {
   186  		m := fs.Machines[name]
   187  		// We want to display availability zone so extract from hardware info".
   188  		hw, err := instance.ParseHardware(m.Hardware)
   189  		if err != nil {
   190  			logger.Warningf("invalid hardware info %s for machine %v", m.Hardware, m)
   191  		}
   192  		az := ""
   193  		if hw.AvailabilityZone != nil {
   194  			az = *hw.AvailabilityZone
   195  		}
   196  		p(m.Id, m.JujuStatus.Current, m.DNSName, m.InstanceId, m.Series, az)
   197  	}
   198  	tw.Flush()
   199  	return out.Bytes(), nil
   200  }
   201  
   202  // FormatMachineTabular returns a tabular summary of machine
   203  func FormatMachineTabular(value interface{}) ([]byte, error) {
   204  	fs, valueConverted := value.(formattedMachineStatus)
   205  	if !valueConverted {
   206  		return nil, errors.Errorf("expected value of type %T, got %T", fs, value)
   207  	}
   208  	var out bytes.Buffer
   209  	// To format things into columns.
   210  	tw := tabwriter.NewWriter(&out, 0, 1, 1, ' ', 0)
   211  	p := func(values ...interface{}) {
   212  		for _, v := range values {
   213  			fmt.Fprintf(tw, "%s\t", v)
   214  		}
   215  		fmt.Fprintln(tw)
   216  	}
   217  
   218  	p("\n[Machines]")
   219  	p("ID\tSTATE\tDNS\tINS-ID\tSERIES\tAZ")
   220  	for _, name := range common.SortStringsNaturally(stringKeysFromMap(fs.Machines)) {
   221  		m := fs.Machines[name]
   222  		// We want to display availability zone so extract from hardware info".
   223  		hw, err := instance.ParseHardware(m.Hardware)
   224  		if err != nil {
   225  			logger.Warningf("invalid hardware info %s for machine %v", m.Hardware, m)
   226  		}
   227  		az := ""
   228  		if hw.AvailabilityZone != nil {
   229  			az = *hw.AvailabilityZone
   230  		}
   231  		p(m.Id, m.JujuStatus.Current, m.DNSName, m.InstanceId, m.Series, az)
   232  	}
   233  	tw.Flush()
   234  
   235  	return out.Bytes(), nil
   236  }
   237  
   238  // agentDoing returns what hook or action, if any,
   239  // the agent is currently executing.
   240  // The hook name or action is extracted from the agent message.
   241  func agentDoing(agentStatus statusInfoContents) string {
   242  	if agentStatus.Current != status.StatusExecuting {
   243  		return ""
   244  	}
   245  	// First see if we can determine a hook name.
   246  	var hookNames []string
   247  	for _, h := range hooks.UnitHooks() {
   248  		hookNames = append(hookNames, string(h))
   249  	}
   250  	for _, h := range hooks.RelationHooks() {
   251  		hookNames = append(hookNames, string(h))
   252  	}
   253  	hookExp := regexp.MustCompile(fmt.Sprintf(`running (?P<hook>%s?) hook`, strings.Join(hookNames, "|")))
   254  	match := hookExp.FindStringSubmatch(agentStatus.Message)
   255  	if len(match) > 0 {
   256  		return match[1]
   257  	}
   258  	// Now try for an action name.
   259  	actionExp := regexp.MustCompile(`running action (?P<action>.*)`)
   260  	match = actionExp.FindStringSubmatch(agentStatus.Message)
   261  	if len(match) > 0 {
   262  		return match[1]
   263  	}
   264  	return ""
   265  }