2017-10-11 14:45:29 +00:00
|
|
|
package catlistnodes
|
2017-07-14 19:45:08 +00:00
|
|
|
|
|
|
|
import (
|
2017-10-11 14:45:29 +00:00
|
|
|
"flag"
|
2017-07-14 19:45:08 +00:00
|
|
|
"fmt"
|
2017-10-11 14:45:29 +00:00
|
|
|
"sort"
|
2017-07-14 19:45:08 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/consul/api"
|
2017-10-11 12:51:19 +00:00
|
|
|
"github.com/hashicorp/consul/command/flags"
|
2017-07-14 19:45:08 +00:00
|
|
|
"github.com/mitchellh/cli"
|
|
|
|
"github.com/ryanuber/columnize"
|
|
|
|
)
|
|
|
|
|
2017-10-11 14:45:29 +00:00
|
|
|
func New(ui cli.Ui) *cmd {
|
|
|
|
c := &cmd{UI: ui}
|
2017-10-11 17:43:17 +00:00
|
|
|
c.init()
|
2017-10-11 14:45:29 +00:00
|
|
|
return c
|
|
|
|
}
|
2017-07-14 19:45:08 +00:00
|
|
|
|
2017-10-11 14:45:29 +00:00
|
|
|
type cmd struct {
|
2017-10-11 18:58:19 +00:00
|
|
|
UI cli.Ui
|
|
|
|
flags *flag.FlagSet
|
|
|
|
http *flags.HTTPFlags
|
|
|
|
usage string
|
2017-10-11 12:51:18 +00:00
|
|
|
|
|
|
|
// flags
|
|
|
|
detailed bool
|
|
|
|
near string
|
|
|
|
nodeMeta map[string]string
|
|
|
|
service string
|
|
|
|
}
|
|
|
|
|
2017-10-11 17:43:17 +00:00
|
|
|
// init sets up command flags and help text
|
|
|
|
func (c *cmd) init() {
|
2017-10-11 14:45:29 +00:00
|
|
|
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
|
|
|
c.flags.BoolVar(&c.detailed, "detailed", false, "Output detailed information about "+
|
2017-10-11 12:51:18 +00:00
|
|
|
"the nodes including their addresses and metadata.")
|
2017-10-11 14:45:29 +00:00
|
|
|
c.flags.StringVar(&c.near, "near", "", "Node name to sort the node list in ascending "+
|
2017-10-11 12:51:18 +00:00
|
|
|
"order based on estimated round-trip time from that node. "+
|
|
|
|
"Passing \"_agent\" will use this agent's node for sorting.")
|
2017-10-11 14:45:29 +00:00
|
|
|
c.flags.Var((*flags.FlagMapValue)(&c.nodeMeta), "node-meta", "Metadata to "+
|
2017-10-11 12:51:18 +00:00
|
|
|
"filter nodes with the given `key=value` pairs. This flag may be "+
|
|
|
|
"specified multiple times to filter on multiple sources of metadata.")
|
2017-10-11 14:45:29 +00:00
|
|
|
c.flags.StringVar(&c.service, "service", "", "Service `id or name` to filter nodes. "+
|
2017-10-11 12:51:18 +00:00
|
|
|
"Only nodes which are providing the given service will be returned.")
|
2017-07-14 19:45:08 +00:00
|
|
|
|
2017-10-11 14:45:29 +00:00
|
|
|
c.http = &flags.HTTPFlags{}
|
|
|
|
flags.Merge(c.flags, c.http.ClientFlags())
|
|
|
|
flags.Merge(c.flags, c.http.ServerFlags())
|
2017-10-11 17:43:17 +00:00
|
|
|
|
2017-10-11 18:58:19 +00:00
|
|
|
c.usage = flags.Usage(usage, c.flags, c.http.ClientFlags(), c.http.ServerFlags())
|
2017-07-14 19:45:08 +00:00
|
|
|
}
|
|
|
|
|
2017-10-11 14:45:29 +00:00
|
|
|
func (c *cmd) Run(args []string) int {
|
|
|
|
if err := c.flags.Parse(args); err != nil {
|
2017-07-14 19:45:08 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-10-11 14:45:29 +00:00
|
|
|
if l := len(c.flags.Args()); l > 0 {
|
2017-07-14 19:45:08 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", l))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create and test the HTTP client
|
2017-10-11 14:45:29 +00:00
|
|
|
client, err := c.http.APIClient()
|
2017-07-14 19:45:08 +00:00
|
|
|
if err != nil {
|
|
|
|
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
var nodes []*api.Node
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.service != "" {
|
|
|
|
services, _, err := client.Catalog().Service(c.service, "", &api.QueryOptions{
|
|
|
|
Near: c.near,
|
|
|
|
NodeMeta: c.nodeMeta,
|
2017-07-14 19:45:08 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
c.UI.Error(fmt.Sprintf("Error listing nodes for service: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes = make([]*api.Node, len(services))
|
|
|
|
for i, s := range services {
|
|
|
|
nodes[i] = &api.Node{
|
|
|
|
ID: s.ID,
|
|
|
|
Node: s.Node,
|
|
|
|
Address: s.Address,
|
|
|
|
Datacenter: s.Datacenter,
|
|
|
|
TaggedAddresses: s.TaggedAddresses,
|
|
|
|
Meta: s.NodeMeta,
|
|
|
|
CreateIndex: s.CreateIndex,
|
|
|
|
ModifyIndex: s.ModifyIndex,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
nodes, _, err = client.Catalog().Nodes(&api.QueryOptions{
|
2017-10-11 12:51:18 +00:00
|
|
|
Near: c.near,
|
|
|
|
NodeMeta: c.nodeMeta,
|
2017-07-14 19:45:08 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
c.UI.Error(fmt.Sprintf("Error listing nodes: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle the edge case where there are no nodes that match the query.
|
|
|
|
if len(nodes) == 0 {
|
|
|
|
c.UI.Error("No nodes match the given query - try expanding your search.")
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2017-10-11 12:51:18 +00:00
|
|
|
output, err := printNodes(nodes, c.detailed)
|
2017-07-14 19:45:08 +00:00
|
|
|
if err != nil {
|
|
|
|
c.UI.Error(fmt.Sprintf("Error printing nodes: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
c.UI.Info(output)
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2017-10-11 14:45:29 +00:00
|
|
|
func (c *cmd) Synopsis() string {
|
2017-07-14 19:45:08 +00:00
|
|
|
return "Lists all nodes in the given datacenter"
|
|
|
|
}
|
|
|
|
|
2017-10-11 14:45:29 +00:00
|
|
|
func (c *cmd) Help() string {
|
2017-10-11 18:58:19 +00:00
|
|
|
return c.usage
|
2017-10-11 14:45:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-14 19:45:08 +00:00
|
|
|
// printNodes accepts a list of nodes and prints information in a tabular
|
|
|
|
// format about the nodes.
|
|
|
|
func printNodes(nodes []*api.Node, detailed bool) (string, error) {
|
|
|
|
var result []string
|
|
|
|
if detailed {
|
|
|
|
result = detailedNodes(nodes)
|
|
|
|
} else {
|
|
|
|
result = simpleNodes(nodes)
|
|
|
|
}
|
|
|
|
|
|
|
|
return columnize.SimpleFormat(result), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func detailedNodes(nodes []*api.Node) []string {
|
|
|
|
result := make([]string, 0, len(nodes)+1)
|
|
|
|
header := "Node|ID|Address|DC|TaggedAddresses|Meta"
|
|
|
|
result = append(result, header)
|
|
|
|
|
|
|
|
for _, node := range nodes {
|
|
|
|
result = append(result, fmt.Sprintf("%s|%s|%s|%s|%s|%s",
|
|
|
|
node.Node, node.ID, node.Address, node.Datacenter,
|
|
|
|
mapToKV(node.TaggedAddresses, ", "), mapToKV(node.Meta, ", ")))
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func simpleNodes(nodes []*api.Node) []string {
|
|
|
|
result := make([]string, 0, len(nodes)+1)
|
|
|
|
header := "Node|ID|Address|DC"
|
|
|
|
result = append(result, header)
|
|
|
|
|
|
|
|
for _, node := range nodes {
|
|
|
|
// Shorten the ID in non-detailed mode to just the first octet.
|
|
|
|
id := node.ID
|
|
|
|
idx := strings.Index(id, "-")
|
|
|
|
if idx > 0 {
|
|
|
|
id = id[0:idx]
|
|
|
|
}
|
|
|
|
result = append(result, fmt.Sprintf("%s|%s|%s|%s",
|
|
|
|
node.Node, id, node.Address, node.Datacenter))
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
2017-10-11 14:45:29 +00:00
|
|
|
|
|
|
|
// mapToKV converts a map[string]string into a human-friendly key=value list,
|
|
|
|
// sorted by name.
|
|
|
|
func mapToKV(m map[string]string, joiner string) string {
|
|
|
|
keys := make([]string, 0, len(m))
|
|
|
|
for k := range m {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
r := make([]string, len(keys))
|
|
|
|
for i, k := range keys {
|
|
|
|
r[i] = fmt.Sprintf("%s=%s", k, m[k])
|
|
|
|
}
|
|
|
|
return strings.Join(r, joiner)
|
|
|
|
}
|
2017-10-11 18:58:19 +00:00
|
|
|
|
|
|
|
const usage = `Usage: consul catalog nodes [options]
|
|
|
|
|
|
|
|
Retrieves the list nodes registered in a given datacenter. By default, the
|
|
|
|
datacenter of the local agent is queried.
|
|
|
|
|
|
|
|
To retrieve the list of nodes:
|
|
|
|
|
|
|
|
$ consul catalog nodes
|
|
|
|
|
|
|
|
To print detailed information including full node IDs, tagged addresses, and
|
|
|
|
metadata information:
|
|
|
|
|
|
|
|
$ consul catalog nodes -detailed
|
|
|
|
|
|
|
|
To list nodes which are running a particular service:
|
|
|
|
|
|
|
|
$ consul catalog nodes -service=web
|
|
|
|
|
|
|
|
To filter by node metadata:
|
|
|
|
|
|
|
|
$ consul catalog nodes -node-meta="foo=bar"
|
|
|
|
|
|
|
|
To sort nodes by estimated round-trip time from node-web:
|
|
|
|
|
|
|
|
$ consul catalog nodes -near=node-web
|
|
|
|
|
|
|
|
For a full list of options and examples, please see the Consul documentation.`
|