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 = (*CatalogNodeQuery)(nil)
17
18	// CatalogNodeQueryRe is the regular expression to use.
19	CatalogNodeQueryRe = regexp.MustCompile(`\A` + nodeNameRe + dcRe + `\z`)
20)
21
22func init() {
23	gob.Register([]*CatalogNode{})
24	gob.Register([]*CatalogNodeService{})
25}
26
27// CatalogNodeQuery represents a single node from the Consul catalog.
28type CatalogNodeQuery struct {
29	stopCh chan struct{}
30
31	dc   string
32	name string
33}
34
35// CatalogNode is a wrapper around the node and its services.
36type CatalogNode struct {
37	Node     *Node
38	Services []*CatalogNodeService
39}
40
41// CatalogNodeService is a service on a single node.
42type CatalogNodeService struct {
43	ID                string
44	Service           string
45	Tags              ServiceTags
46	Meta              map[string]string
47	Port              int
48	Address           string
49	EnableTagOverride bool
50}
51
52// NewCatalogNodeQuery parses the given string into a dependency. If the name is
53// empty then the name of the local agent is used.
54func NewCatalogNodeQuery(s string) (*CatalogNodeQuery, error) {
55	if s != "" && !CatalogNodeQueryRe.MatchString(s) {
56		return nil, fmt.Errorf("catalog.node: invalid format: %q", s)
57	}
58
59	m := regexpMatch(CatalogNodeQueryRe, s)
60	return &CatalogNodeQuery{
61		dc:     m["dc"],
62		name:   m["name"],
63		stopCh: make(chan struct{}, 1),
64	}, nil
65}
66
67// Fetch queries the Consul API defined by the given client and returns a
68// of CatalogNode object.
69func (d *CatalogNodeQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
70	select {
71	case <-d.stopCh:
72		return nil, nil, ErrStopped
73	default:
74	}
75
76	opts = opts.Merge(&QueryOptions{
77		Datacenter: d.dc,
78	})
79
80	// Grab the name
81	name := d.name
82
83	if name == "" {
84		log.Printf("[TRACE] %s: getting local agent name", d)
85		var err error
86		name, err = clients.Consul().Agent().NodeName()
87		if err != nil {
88			return nil, nil, errors.Wrapf(err, d.String())
89		}
90	}
91
92	log.Printf("[TRACE] %s: GET %s", d, &url.URL{
93		Path:     "/v1/catalog/node/" + name,
94		RawQuery: opts.String(),
95	})
96	node, qm, err := clients.Consul().Catalog().Node(name, opts.ToConsulOpts())
97	if err != nil {
98		return nil, nil, errors.Wrap(err, d.String())
99	}
100
101	log.Printf("[TRACE] %s: returned response", d)
102
103	rm := &ResponseMetadata{
104		LastIndex:   qm.LastIndex,
105		LastContact: qm.LastContact,
106	}
107
108	if node == nil {
109		log.Printf("[WARN] %s: no node exists with the name %q", d, name)
110		var node CatalogNode
111		return &node, rm, nil
112	}
113
114	services := make([]*CatalogNodeService, 0, len(node.Services))
115	for _, v := range node.Services {
116		services = append(services, &CatalogNodeService{
117			ID:                v.ID,
118			Service:           v.Service,
119			Tags:              ServiceTags(deepCopyAndSortTags(v.Tags)),
120			Meta:              v.Meta,
121			Port:              v.Port,
122			Address:           v.Address,
123			EnableTagOverride: v.EnableTagOverride,
124		})
125	}
126	sort.Stable(ByService(services))
127
128	detail := &CatalogNode{
129		Node: &Node{
130			ID:              node.Node.ID,
131			Node:            node.Node.Node,
132			Address:         node.Node.Address,
133			Datacenter:      node.Node.Datacenter,
134			TaggedAddresses: node.Node.TaggedAddresses,
135			Meta:            node.Node.Meta,
136		},
137		Services: services,
138	}
139
140	return detail, rm, nil
141}
142
143// CanShare returns a boolean if this dependency is shareable.
144func (d *CatalogNodeQuery) CanShare() bool {
145	return false
146}
147
148// String returns the human-friendly version of this dependency.
149func (d *CatalogNodeQuery) String() string {
150	name := d.name
151	if d.dc != "" {
152		name = name + "@" + d.dc
153	}
154
155	if name == "" {
156		return "catalog.node"
157	}
158	return fmt.Sprintf("catalog.node(%s)", name)
159}
160
161// Stop halts the dependency's fetch function.
162func (d *CatalogNodeQuery) Stop() {
163	close(d.stopCh)
164}
165
166// Type returns the type of this dependency.
167func (d *CatalogNodeQuery) Type() Type {
168	return TypeConsul
169}
170
171// ByService is a sorter of node services by their service name and then ID.
172type ByService []*CatalogNodeService
173
174func (s ByService) Len() int      { return len(s) }
175func (s ByService) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
176func (s ByService) Less(i, j int) bool {
177	if s[i].Service == s[j].Service {
178		return s[i].ID <= s[j].ID
179	}
180	return s[i].Service <= s[j].Service
181}
182