1package dependency
2
3import (
4	"net/url"
5	"regexp"
6	"sort"
7	"strconv"
8	"time"
9
10	consulapi "github.com/hashicorp/consul/api"
11)
12
13const (
14	dcRe          = `(@(?P<dc>[[:word:]\.\-\_]+))?`
15	keyRe         = `/?(?P<key>[^@]+)`
16	filterRe      = `(\|(?P<filter>[[:word:]\,]+))?`
17	serviceNameRe = `(?P<name>[[:word:]\-\_]+)`
18	nodeNameRe    = `(?P<name>[[:word:]\.\-\_]+)`
19	nearRe        = `(~(?P<near>[[:word:]\.\-\_]+))?`
20	prefixRe      = `/?(?P<prefix>[^@]+)`
21	tagRe         = `((?P<tag>[[:word:]=:\.\-\_]+)\.)?`
22)
23
24type Type int
25
26const (
27	TypeConsul Type = iota
28	TypeVault
29	TypeLocal
30)
31
32// Dependency is an interface for a dependency that Consul Template is capable
33// of watching.
34type Dependency interface {
35	Fetch(*ClientSet, *QueryOptions) (interface{}, *ResponseMetadata, error)
36	CanShare() bool
37	String() string
38	Stop()
39	Type() Type
40}
41
42// ServiceTags is a slice of tags assigned to a Service
43type ServiceTags []string
44
45// QueryOptions is a list of options to send with the query. These options are
46// client-agnostic, and the dependency determines which, if any, of the options
47// to use.
48type QueryOptions struct {
49	AllowStale        bool
50	Datacenter        string
51	Near              string
52	RequireConsistent bool
53	VaultGrace        time.Duration
54	WaitIndex         uint64
55	WaitTime          time.Duration
56}
57
58func (q *QueryOptions) Merge(o *QueryOptions) *QueryOptions {
59	var r QueryOptions
60
61	if q == nil {
62		if o == nil {
63			return &QueryOptions{}
64		}
65		r = *o
66		return &r
67	}
68
69	r = *q
70
71	if o == nil {
72		return &r
73	}
74
75	if o.AllowStale != false {
76		r.AllowStale = o.AllowStale
77	}
78
79	if o.Datacenter != "" {
80		r.Datacenter = o.Datacenter
81	}
82
83	if o.Near != "" {
84		r.Near = o.Near
85	}
86
87	if o.RequireConsistent != false {
88		r.RequireConsistent = o.RequireConsistent
89	}
90
91	if o.WaitIndex != 0 {
92		r.WaitIndex = o.WaitIndex
93	}
94
95	if o.WaitTime != 0 {
96		r.WaitTime = o.WaitTime
97	}
98
99	return &r
100}
101
102func (q *QueryOptions) ToConsulOpts() *consulapi.QueryOptions {
103	return &consulapi.QueryOptions{
104		AllowStale:        q.AllowStale,
105		Datacenter:        q.Datacenter,
106		Near:              q.Near,
107		RequireConsistent: q.RequireConsistent,
108		WaitIndex:         q.WaitIndex,
109		WaitTime:          q.WaitTime,
110	}
111}
112
113func (q *QueryOptions) String() string {
114	u := &url.Values{}
115
116	if q.AllowStale {
117		u.Add("stale", strconv.FormatBool(q.AllowStale))
118	}
119
120	if q.Datacenter != "" {
121		u.Add("dc", q.Datacenter)
122	}
123
124	if q.Near != "" {
125		u.Add("near", q.Near)
126	}
127
128	if q.RequireConsistent {
129		u.Add("consistent", strconv.FormatBool(q.RequireConsistent))
130	}
131
132	if q.WaitIndex != 0 {
133		u.Add("index", strconv.FormatUint(q.WaitIndex, 10))
134	}
135
136	if q.WaitTime != 0 {
137		u.Add("wait", q.WaitTime.String())
138	}
139
140	return u.Encode()
141}
142
143// ResponseMetadata is a struct that contains metadata about the response. This
144// is returned from a Fetch function call.
145type ResponseMetadata struct {
146	LastIndex   uint64
147	LastContact time.Duration
148	Block       bool
149}
150
151// deepCopyAndSortTags deep copies the tags in the given string slice and then
152// sorts and returns the copied result.
153func deepCopyAndSortTags(tags []string) []string {
154	newTags := make([]string, 0, len(tags))
155	for _, tag := range tags {
156		newTags = append(newTags, tag)
157	}
158	sort.Strings(newTags)
159	return newTags
160}
161
162// respWithMetadata is a short wrapper to return the given interface with fake
163// response metadata for non-Consul dependencies.
164func respWithMetadata(i interface{}) (interface{}, *ResponseMetadata, error) {
165	return i, &ResponseMetadata{
166		LastContact: 0,
167		LastIndex:   uint64(time.Now().Unix()),
168	}, nil
169}
170
171// regexpMatch matches the given regexp and extracts the match groups into a
172// named map.
173func regexpMatch(re *regexp.Regexp, q string) map[string]string {
174	names := re.SubexpNames()
175	match := re.FindAllStringSubmatch(q, -1)
176
177	if len(match) == 0 {
178		return map[string]string{}
179	}
180
181	m := map[string]string{}
182	for i, n := range match[0] {
183		if names[i] != "" {
184			m[names[i]] = n
185		}
186	}
187
188	return m
189}
190