github.com/criteo-forks/consul@v1.4.5-criteonogrpc/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  `