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 }