github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	"strings"
    11  	"text/tabwriter"
    12  
    13  	"github.com/juju/errors"
    14  	"gopkg.in/juju/charm.v6-unstable/hooks"
    15  
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/cmd/juju/common"
    18  )
    19  
    20  // FormatTabular returns a tabular summary of machines, services, and
    21  // units. Any subordinate items are indented by two spaces beneath
    22  // their superior.
    23  func FormatTabular(value interface{}) ([]byte, error) {
    24  	fs, valueConverted := value.(formattedStatus)
    25  	if !valueConverted {
    26  		return nil, errors.Errorf("expected value of type %T, got %T", fs, value)
    27  	}
    28  	var out bytes.Buffer
    29  	// To format things into columns.
    30  	tw := tabwriter.NewWriter(&out, 0, 1, 1, ' ', 0)
    31  	p := func(values ...interface{}) {
    32  		for _, v := range values {
    33  			fmt.Fprintf(tw, "%s\t", v)
    34  		}
    35  		fmt.Fprintln(tw)
    36  	}
    37  
    38  	units := make(map[string]unitStatus)
    39  	p("[Services]")
    40  	p("NAME\tSTATUS\tEXPOSED\tCHARM")
    41  	for _, svcName := range common.SortStringsNaturally(stringKeysFromMap(fs.Services)) {
    42  		svc := fs.Services[svcName]
    43  		for un, u := range svc.Units {
    44  			units[un] = u
    45  		}
    46  		p(svcName, svc.StatusInfo.Current, fmt.Sprintf("%t", svc.Exposed), svc.Charm)
    47  	}
    48  	tw.Flush()
    49  
    50  	pUnit := func(name string, u unitStatus, level int) {
    51  		message := u.WorkloadStatusInfo.Message
    52  		agentDoing := agentDoing(u.AgentStatusInfo)
    53  		if agentDoing != "" {
    54  			message = fmt.Sprintf("(%s) %s", agentDoing, message)
    55  		}
    56  		p(
    57  			indent("", level*2, name),
    58  			u.WorkloadStatusInfo.Current,
    59  			u.AgentStatusInfo.Current,
    60  			u.AgentStatusInfo.Version,
    61  			u.Machine,
    62  			strings.Join(u.OpenedPorts, ","),
    63  			u.PublicAddress,
    64  			message,
    65  		)
    66  	}
    67  
    68  	// See if we have new or old data; that determines what data we can display.
    69  	newStatus := false
    70  	for _, u := range units {
    71  		if u.AgentStatusInfo.Current != "" {
    72  			newStatus = true
    73  			break
    74  		}
    75  	}
    76  	var header []string
    77  	if newStatus {
    78  		header = []string{"ID", "WORKLOAD-STATE", "AGENT-STATE", "VERSION", "MACHINE", "PORTS", "PUBLIC-ADDRESS", "MESSAGE"}
    79  	} else {
    80  		header = []string{"ID", "STATE", "VERSION", "MACHINE", "PORTS", "PUBLIC-ADDRESS"}
    81  	}
    82  
    83  	p("\n[Units]")
    84  	p(strings.Join(header, "\t"))
    85  	for _, name := range common.SortStringsNaturally(stringKeysFromMap(units)) {
    86  		u := units[name]
    87  		pUnit(name, u, 0)
    88  		const indentationLevel = 1
    89  		recurseUnits(u, indentationLevel, pUnit)
    90  	}
    91  	tw.Flush()
    92  
    93  	p("\n[Machines]")
    94  	p("ID\tSTATE\tVERSION\tDNS\tINS-ID\tSERIES\tHARDWARE")
    95  	for _, name := range common.SortStringsNaturally(stringKeysFromMap(fs.Machines)) {
    96  		m := fs.Machines[name]
    97  		p(m.Id, m.AgentState, m.AgentVersion, m.DNSName, m.InstanceId, m.Series, m.Hardware)
    98  	}
    99  	tw.Flush()
   100  
   101  	if fs.AvailableVersion != "" {
   102  		p("\n[Juju]")
   103  		p("UPGRADE-AVAILABLE")
   104  		p(fs.AvailableVersion)
   105  	}
   106  	tw.Flush()
   107  
   108  	return out.Bytes(), nil
   109  }
   110  
   111  // agentDoing returns what hook or action, if any,
   112  // the agent is currently executing.
   113  // The hook name or action is extracted from the agent message.
   114  func agentDoing(status statusInfoContents) string {
   115  	if status.Current != params.StatusExecuting {
   116  		return ""
   117  	}
   118  	// First see if we can determine a hook name.
   119  	var hookNames []string
   120  	for _, h := range hooks.UnitHooks() {
   121  		hookNames = append(hookNames, string(h))
   122  	}
   123  	for _, h := range hooks.RelationHooks() {
   124  		hookNames = append(hookNames, string(h))
   125  	}
   126  	hookExp := regexp.MustCompile(fmt.Sprintf(`running (?P<hook>%s?) hook`, strings.Join(hookNames, "|")))
   127  	match := hookExp.FindStringSubmatch(status.Message)
   128  	if len(match) > 0 {
   129  		return match[1]
   130  	}
   131  	// Now try for an action name.
   132  	actionExp := regexp.MustCompile(`running action (?P<action>.*)`)
   133  	match = actionExp.FindStringSubmatch(status.Message)
   134  	if len(match) > 0 {
   135  		return match[1]
   136  	}
   137  	return ""
   138  }