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 = (*CatalogServicesQuery)(nil)
17
18	// CatalogServicesQueryRe is the regular expression to use for CatalogNodesQuery.
19	CatalogServicesQueryRe = regexp.MustCompile(`\A` + dcRe + `\z`)
20)
21
22func init() {
23	gob.Register([]*CatalogSnippet{})
24}
25
26// CatalogSnippet is a catalog entry in Consul.
27type CatalogSnippet struct {
28	Name string
29	Tags ServiceTags
30}
31
32// CatalogServicesQuery is the representation of a requested catalog service
33// dependency from inside a template.
34type CatalogServicesQuery struct {
35	stopCh chan struct{}
36
37	dc string
38}
39
40// NewCatalogServicesQuery parses a string of the format @dc.
41func NewCatalogServicesQuery(s string) (*CatalogServicesQuery, error) {
42	if !CatalogServicesQueryRe.MatchString(s) {
43		return nil, fmt.Errorf("catalog.services: invalid format: %q", s)
44	}
45
46	m := regexpMatch(CatalogServicesQueryRe, s)
47	return &CatalogServicesQuery{
48		stopCh: make(chan struct{}, 1),
49		dc:     m["dc"],
50	}, nil
51}
52
53// Fetch queries the Consul API defined by the given client and returns a slice
54// of CatalogService objects.
55func (d *CatalogServicesQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
56	select {
57	case <-d.stopCh:
58		return nil, nil, ErrStopped
59	default:
60	}
61
62	opts = opts.Merge(&QueryOptions{
63		Datacenter: d.dc,
64	})
65
66	log.Printf("[TRACE] %s: GET %s", d, &url.URL{
67		Path:     "/v1/catalog/services",
68		RawQuery: opts.String(),
69	})
70
71	entries, qm, err := clients.Consul().Catalog().Services(opts.ToConsulOpts())
72	if err != nil {
73		return nil, nil, errors.Wrap(err, d.String())
74	}
75
76	log.Printf("[TRACE] %s: returned %d results", d, len(entries))
77
78	var catalogServices []*CatalogSnippet
79	for name, tags := range entries {
80		catalogServices = append(catalogServices, &CatalogSnippet{
81			Name: name,
82			Tags: ServiceTags(deepCopyAndSortTags(tags)),
83		})
84	}
85
86	sort.Stable(ByName(catalogServices))
87
88	rm := &ResponseMetadata{
89		LastIndex:   qm.LastIndex,
90		LastContact: qm.LastContact,
91	}
92
93	return catalogServices, rm, nil
94}
95
96// CanShare returns a boolean if this dependency is shareable.
97func (d *CatalogServicesQuery) CanShare() bool {
98	return true
99}
100
101// String returns the human-friendly version of this dependency.
102func (d *CatalogServicesQuery) String() string {
103	if d.dc != "" {
104		return fmt.Sprintf("catalog.services(@%s)", d.dc)
105	}
106	return "catalog.services"
107}
108
109// Stop halts the dependency's fetch function.
110func (d *CatalogServicesQuery) Stop() {
111	close(d.stopCh)
112}
113
114// Type returns the type of this dependency.
115func (d *CatalogServicesQuery) Type() Type {
116	return TypeConsul
117}
118
119// ByName is a sortable slice of CatalogService structs.
120type ByName []*CatalogSnippet
121
122func (s ByName) Len() int      { return len(s) }
123func (s ByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
124func (s ByName) Less(i, j int) bool {
125	if s[i].Name <= s[j].Name {
126		return true
127	}
128	return false
129}
130