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