github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/command/catalog/list/nodes/catalog_list_nodes.go (about) 1 package nodes 2 3 import ( 4 "flag" 5 "fmt" 6 "sort" 7 "strings" 8 9 "github.com/hashicorp/consul/api" 10 "github.com/hashicorp/consul/command/flags" 11 "github.com/mitchellh/cli" 12 "github.com/ryanuber/columnize" 13 ) 14 15 func New(ui cli.Ui) *cmd { 16 c := &cmd{UI: ui} 17 c.init() 18 return c 19 } 20 21 type cmd struct { 22 UI cli.Ui 23 flags *flag.FlagSet 24 http *flags.HTTPFlags 25 help string 26 27 // flags 28 detailed bool 29 near string 30 nodeMeta map[string]string 31 service string 32 } 33 34 // init sets up command flags and help text 35 func (c *cmd) init() { 36 c.flags = flag.NewFlagSet("", flag.ContinueOnError) 37 c.flags.BoolVar(&c.detailed, "detailed", false, "Output detailed information about "+ 38 "the nodes including their addresses and metadata.") 39 c.flags.StringVar(&c.near, "near", "", "Node name to sort the node list in ascending "+ 40 "order based on estimated round-trip time from that node. "+ 41 "Passing \"_agent\" will use this agent's node for sorting.") 42 c.flags.Var((*flags.FlagMapValue)(&c.nodeMeta), "node-meta", "Metadata to "+ 43 "filter nodes with the given `key=value` pairs. This flag may be "+ 44 "specified multiple times to filter on multiple sources of metadata.") 45 c.flags.StringVar(&c.service, "service", "", "Service `id or name` to filter nodes. "+ 46 "Only nodes which are providing the given service will be returned.") 47 48 c.http = &flags.HTTPFlags{} 49 flags.Merge(c.flags, c.http.ClientFlags()) 50 flags.Merge(c.flags, c.http.ServerFlags()) 51 c.help = flags.Usage(help, c.flags) 52 } 53 54 func (c *cmd) Run(args []string) int { 55 if err := c.flags.Parse(args); err != nil { 56 return 1 57 } 58 59 if l := len(c.flags.Args()); l > 0 { 60 c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", l)) 61 return 1 62 } 63 64 // Create and test the HTTP client 65 client, err := c.http.APIClient() 66 if err != nil { 67 c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) 68 return 1 69 } 70 71 var nodes []*api.Node 72 if c.service != "" { 73 services, _, err := client.Catalog().Service(c.service, "", &api.QueryOptions{ 74 Near: c.near, 75 NodeMeta: c.nodeMeta, 76 }) 77 if err != nil { 78 c.UI.Error(fmt.Sprintf("Error listing nodes for service: %s", err)) 79 return 1 80 } 81 82 nodes = make([]*api.Node, len(services)) 83 for i, s := range services { 84 nodes[i] = &api.Node{ 85 ID: s.ID, 86 Node: s.Node, 87 Address: s.Address, 88 Datacenter: s.Datacenter, 89 TaggedAddresses: s.TaggedAddresses, 90 Meta: s.NodeMeta, 91 CreateIndex: s.CreateIndex, 92 ModifyIndex: s.ModifyIndex, 93 } 94 } 95 } else { 96 nodes, _, err = client.Catalog().Nodes(&api.QueryOptions{ 97 Near: c.near, 98 NodeMeta: c.nodeMeta, 99 }) 100 if err != nil { 101 c.UI.Error(fmt.Sprintf("Error listing nodes: %s", err)) 102 return 1 103 } 104 } 105 106 // Handle the edge case where there are no nodes that match the query. 107 if len(nodes) == 0 { 108 c.UI.Error("No nodes match the given query - try expanding your search.") 109 return 0 110 } 111 112 output, err := printNodes(nodes, c.detailed) 113 if err != nil { 114 c.UI.Error(fmt.Sprintf("Error printing nodes: %s", err)) 115 return 1 116 } 117 118 c.UI.Info(output) 119 120 return 0 121 } 122 123 // printNodes accepts a list of nodes and prints information in a tabular 124 // format about the nodes. 125 func printNodes(nodes []*api.Node, detailed bool) (string, error) { 126 var result []string 127 if detailed { 128 result = detailedNodes(nodes) 129 } else { 130 result = simpleNodes(nodes) 131 } 132 133 return columnize.SimpleFormat(result), nil 134 } 135 136 func detailedNodes(nodes []*api.Node) []string { 137 result := make([]string, 0, len(nodes)+1) 138 header := "Node|ID|Address|DC|TaggedAddresses|Meta" 139 result = append(result, header) 140 141 for _, node := range nodes { 142 result = append(result, fmt.Sprintf("%s|%s|%s|%s|%s|%s", 143 node.Node, node.ID, node.Address, node.Datacenter, 144 mapToKV(node.TaggedAddresses, ", "), mapToKV(node.Meta, ", "))) 145 } 146 147 return result 148 } 149 150 func simpleNodes(nodes []*api.Node) []string { 151 result := make([]string, 0, len(nodes)+1) 152 header := "Node|ID|Address|DC" 153 result = append(result, header) 154 155 for _, node := range nodes { 156 // Shorten the ID in non-detailed mode to just the first octet. 157 id := node.ID 158 idx := strings.Index(id, "-") 159 if idx > 0 { 160 id = id[0:idx] 161 } 162 result = append(result, fmt.Sprintf("%s|%s|%s|%s", 163 node.Node, id, node.Address, node.Datacenter)) 164 } 165 166 return result 167 } 168 169 // mapToKV converts a map[string]string into a human-friendly key=value list, 170 // sorted by name. 171 func mapToKV(m map[string]string, joiner string) string { 172 keys := make([]string, 0, len(m)) 173 for k := range m { 174 keys = append(keys, k) 175 } 176 sort.Strings(keys) 177 178 r := make([]string, len(keys)) 179 for i, k := range keys { 180 r[i] = fmt.Sprintf("%s=%s", k, m[k]) 181 } 182 return strings.Join(r, joiner) 183 } 184 185 func (c *cmd) Synopsis() string { 186 return synopsis 187 } 188 189 func (c *cmd) Help() string { 190 return c.help 191 } 192 193 const synopsis = "Lists all nodes in the given datacenter" 194 const help = ` 195 Usage: consul catalog nodes [options] 196 197 Retrieves the list nodes registered in a given datacenter. By default, the 198 datacenter of the local agent is queried. 199 200 To retrieve the list of nodes: 201 202 $ consul catalog nodes 203 204 To print detailed information including full node IDs, tagged addresses, and 205 metadata information: 206 207 $ consul catalog nodes -detailed 208 209 To list nodes which are running a particular service: 210 211 $ consul catalog nodes -service=web 212 213 To filter by node metadata: 214 215 $ consul catalog nodes -node-meta="foo=bar" 216 217 To sort nodes by estimated round-trip time from node-web: 218 219 $ consul catalog nodes -near=node-web 220 221 For a full list of options and examples, please see the Consul documentation. 222 `