1package dependency 2 3import ( 4 "encoding/gob" 5 "fmt" 6 "log" 7 "net/url" 8 "regexp" 9 "sort" 10 11 "github.com/pkg/errors" 12) 13 14var ( 15 // Ensure implements 16 _ Dependency = (*CatalogNodesQuery)(nil) 17 18 // CatalogNodesQueryRe is the regular expression to use. 19 CatalogNodesQueryRe = regexp.MustCompile(`\A` + dcRe + nearRe + `\z`) 20) 21 22func init() { 23 gob.Register([]*Node{}) 24} 25 26// Node is a node entry in Consul 27type Node struct { 28 ID string 29 Node string 30 Address string 31 Datacenter string 32 TaggedAddresses map[string]string 33 Meta map[string]string 34} 35 36// CatalogNodesQuery is the representation of all registered nodes in Consul. 37type CatalogNodesQuery struct { 38 stopCh chan struct{} 39 40 dc string 41 near string 42} 43 44// NewCatalogNodesQuery parses the given string into a dependency. If the name is 45// empty then the name of the local agent is used. 46func NewCatalogNodesQuery(s string) (*CatalogNodesQuery, error) { 47 if !CatalogNodesQueryRe.MatchString(s) { 48 return nil, fmt.Errorf("catalog.nodes: invalid format: %q", s) 49 } 50 51 m := regexpMatch(CatalogNodesQueryRe, s) 52 return &CatalogNodesQuery{ 53 dc: m["dc"], 54 near: m["near"], 55 stopCh: make(chan struct{}, 1), 56 }, nil 57} 58 59// Fetch queries the Consul API defined by the given client and returns a slice 60// of Node objects 61func (d *CatalogNodesQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { 62 select { 63 case <-d.stopCh: 64 return nil, nil, ErrStopped 65 default: 66 } 67 68 opts = opts.Merge(&QueryOptions{ 69 Datacenter: d.dc, 70 Near: d.near, 71 }) 72 73 log.Printf("[TRACE] %s: GET %s", d, &url.URL{ 74 Path: "/v1/catalog/nodes", 75 RawQuery: opts.String(), 76 }) 77 n, qm, err := clients.Consul().Catalog().Nodes(opts.ToConsulOpts()) 78 if err != nil { 79 return nil, nil, errors.Wrap(err, d.String()) 80 } 81 82 log.Printf("[TRACE] %s: returned %d results", d, len(n)) 83 84 nodes := make([]*Node, 0, len(n)) 85 for _, node := range n { 86 nodes = append(nodes, &Node{ 87 ID: node.ID, 88 Node: node.Node, 89 Address: node.Address, 90 Datacenter: node.Datacenter, 91 TaggedAddresses: node.TaggedAddresses, 92 Meta: node.Meta, 93 }) 94 } 95 96 // Sort unless the user explicitly asked for nearness 97 if d.near == "" { 98 sort.Stable(ByNode(nodes)) 99 } 100 101 rm := &ResponseMetadata{ 102 LastIndex: qm.LastIndex, 103 LastContact: qm.LastContact, 104 } 105 106 return nodes, rm, nil 107} 108 109// CanShare returns a boolean if this dependency is shareable. 110func (d *CatalogNodesQuery) CanShare() bool { 111 return true 112} 113 114// String returns the human-friendly version of this dependency. 115func (d *CatalogNodesQuery) String() string { 116 name := "" 117 if d.dc != "" { 118 name = name + "@" + d.dc 119 } 120 if d.near != "" { 121 name = name + "~" + d.near 122 } 123 124 if name == "" { 125 return "catalog.nodes" 126 } 127 return fmt.Sprintf("catalog.nodes(%s)", name) 128} 129 130// Stop halts the dependency's fetch function. 131func (d *CatalogNodesQuery) Stop() { 132 close(d.stopCh) 133} 134 135// Type returns the type of this dependency. 136func (d *CatalogNodesQuery) Type() Type { 137 return TypeConsul 138} 139 140// ByNode is a sortable list of nodes by name and then IP address. 141type ByNode []*Node 142 143func (s ByNode) Len() int { return len(s) } 144func (s ByNode) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 145func (s ByNode) Less(i, j int) bool { 146 if s[i].Node == s[j].Node { 147 return s[i].Address <= s[j].Address 148 } 149 return s[i].Node <= s[j].Node 150} 151