hub.fastgit.org/hashicorp/consul.git@v1.4.5/command/members/members.go (about) 1 package members 2 3 import ( 4 "flag" 5 "fmt" 6 "net" 7 "regexp" 8 "sort" 9 "strings" 10 11 consulapi "github.com/hashicorp/consul/api" 12 "github.com/hashicorp/consul/command/flags" 13 "github.com/hashicorp/serf/serf" 14 "github.com/mitchellh/cli" 15 "github.com/ryanuber/columnize" 16 ) 17 18 // cmd is a Command implementation that queries a running 19 // Consul agent what members are part of the cluster currently. 20 type cmd struct { 21 UI cli.Ui 22 help string 23 flags *flag.FlagSet 24 http *flags.HTTPFlags 25 26 // flags 27 detailed bool 28 wan bool 29 statusFilter string 30 segment string 31 } 32 33 func New(ui cli.Ui) *cmd { 34 c := &cmd{UI: ui} 35 c.init() 36 return c 37 } 38 39 func (c *cmd) init() { 40 c.flags = flag.NewFlagSet("", flag.ContinueOnError) 41 c.flags.BoolVar(&c.detailed, "detailed", false, 42 "Provides detailed information about nodes.") 43 c.flags.BoolVar(&c.wan, "wan", false, 44 "If the agent is in server mode, this can be used to return the other "+ 45 "peers in the WAN pool.") 46 c.flags.StringVar(&c.statusFilter, "status", ".*", 47 "If provided, output is filtered to only nodes matching the regular "+ 48 "expression for status.") 49 c.flags.StringVar(&c.segment, "segment", consulapi.AllSegments, 50 "(Enterprise-only) If provided, output is filtered to only nodes in"+ 51 "the given segment.") 52 53 c.http = &flags.HTTPFlags{} 54 flags.Merge(c.flags, c.http.ClientFlags()) 55 c.help = flags.Usage(help, c.flags) 56 } 57 58 func (c *cmd) Run(args []string) int { 59 if err := c.flags.Parse(args); err != nil { 60 return 1 61 } 62 63 // Compile the regexp 64 statusRe, err := regexp.Compile(c.statusFilter) 65 if err != nil { 66 c.UI.Error(fmt.Sprintf("Failed to compile status regexp: %v", err)) 67 return 1 68 } 69 70 client, err := c.http.APIClient() 71 if err != nil { 72 c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) 73 return 1 74 } 75 76 // Make the request. 77 opts := consulapi.MembersOpts{ 78 Segment: c.segment, 79 WAN: c.wan, 80 } 81 members, err := client.Agent().MembersOpts(opts) 82 if err != nil { 83 c.UI.Error(fmt.Sprintf("Error retrieving members: %s", err)) 84 return 1 85 } 86 87 // Filter the results 88 n := len(members) 89 for i := 0; i < n; i++ { 90 member := members[i] 91 if member.Tags["segment"] == "" { 92 member.Tags["segment"] = "<default>" 93 } 94 if c.segment == consulapi.AllSegments && member.Tags["role"] == "consul" { 95 member.Tags["segment"] = "<all>" 96 } 97 statusString := serf.MemberStatus(member.Status).String() 98 if !statusRe.MatchString(statusString) { 99 members[i], members[n-1] = members[n-1], members[i] 100 i-- 101 n-- 102 continue 103 } 104 } 105 members = members[:n] 106 107 // No matching members 108 if len(members) == 0 { 109 return 2 110 } 111 112 sort.Sort(ByMemberNameAndSegment(members)) 113 114 // Generate the output 115 var result []string 116 if c.detailed { 117 result = c.detailedOutput(members) 118 } else { 119 result = c.standardOutput(members) 120 } 121 122 // Generate the columnized version 123 output := columnize.SimpleFormat(result) 124 c.UI.Output(output) 125 126 return 0 127 } 128 129 // so we can sort members by name 130 type ByMemberNameAndSegment []*consulapi.AgentMember 131 132 func (m ByMemberNameAndSegment) Len() int { return len(m) } 133 func (m ByMemberNameAndSegment) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 134 func (m ByMemberNameAndSegment) Less(i, j int) bool { 135 switch { 136 case m[i].Tags["segment"] < m[j].Tags["segment"]: 137 return true 138 case m[i].Tags["segment"] > m[j].Tags["segment"]: 139 return false 140 default: 141 return m[i].Name < m[j].Name 142 } 143 } 144 145 // standardOutput is used to dump the most useful information about nodes 146 // in a more human-friendly format 147 func (c *cmd) standardOutput(members []*consulapi.AgentMember) []string { 148 result := make([]string, 0, len(members)) 149 header := "Node|Address|Status|Type|Build|Protocol|DC|Segment" 150 result = append(result, header) 151 for _, member := range members { 152 addr := net.TCPAddr{IP: net.ParseIP(member.Addr), Port: int(member.Port)} 153 protocol := member.Tags["vsn"] 154 build := member.Tags["build"] 155 if build == "" { 156 build = "< 0.3" 157 } else if idx := strings.Index(build, ":"); idx != -1 { 158 build = build[:idx] 159 } 160 dc := member.Tags["dc"] 161 segment := member.Tags["segment"] 162 163 statusString := serf.MemberStatus(member.Status).String() 164 switch member.Tags["role"] { 165 case "node": 166 line := fmt.Sprintf("%s|%s|%s|client|%s|%s|%s|%s", 167 member.Name, addr.String(), statusString, build, protocol, dc, segment) 168 result = append(result, line) 169 case "consul": 170 line := fmt.Sprintf("%s|%s|%s|server|%s|%s|%s|%s", 171 member.Name, addr.String(), statusString, build, protocol, dc, segment) 172 result = append(result, line) 173 default: 174 line := fmt.Sprintf("%s|%s|%s|unknown||||", 175 member.Name, addr.String(), statusString) 176 result = append(result, line) 177 } 178 } 179 return result 180 } 181 182 // detailedOutput is used to dump all known information about nodes in 183 // their raw format 184 func (c *cmd) detailedOutput(members []*consulapi.AgentMember) []string { 185 result := make([]string, 0, len(members)) 186 header := "Node|Address|Status|Tags" 187 result = append(result, header) 188 for _, member := range members { 189 // Get the tags sorted by key 190 tagKeys := make([]string, 0, len(member.Tags)) 191 for key := range member.Tags { 192 tagKeys = append(tagKeys, key) 193 } 194 sort.Strings(tagKeys) 195 196 // Format the tags as tag1=v1,tag2=v2,... 197 var tagPairs []string 198 for _, key := range tagKeys { 199 tagPairs = append(tagPairs, fmt.Sprintf("%s=%s", key, member.Tags[key])) 200 } 201 202 tags := strings.Join(tagPairs, ",") 203 204 addr := net.TCPAddr{IP: net.ParseIP(member.Addr), Port: int(member.Port)} 205 line := fmt.Sprintf("%s|%s|%s|%s", 206 member.Name, addr.String(), serf.MemberStatus(member.Status).String(), tags) 207 result = append(result, line) 208 } 209 return result 210 } 211 212 func (c *cmd) Synopsis() string { 213 return synopsis 214 } 215 216 func (c *cmd) Help() string { 217 return c.help 218 } 219 220 const synopsis = "Lists the members of a Consul cluster" 221 const help = ` 222 Usage: consul members [options] 223 224 Outputs the members of a running Consul agent. 225 `