github.com/lmb/consul@v1.4.1/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  `