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