1package dependency
2
3import (
4	"encoding/gob"
5	"fmt"
6	"log"
7	"net/url"
8	"regexp"
9	"strings"
10
11	"github.com/pkg/errors"
12)
13
14var (
15	// Ensure implements
16	_ Dependency = (*KVListQuery)(nil)
17
18	// KVListQueryRe is the regular expression to use.
19	KVListQueryRe = regexp.MustCompile(`\A` + prefixRe + dcRe + `\z`)
20)
21
22func init() {
23	gob.Register([]*KeyPair{})
24}
25
26// KeyPair is a simple Key-Value pair
27type KeyPair struct {
28	Path  string
29	Key   string
30	Value string
31
32	// Lesser-used, but still valuable keys from api.KV
33	CreateIndex uint64
34	ModifyIndex uint64
35	LockIndex   uint64
36	Flags       uint64
37	Session     string
38}
39
40// KVListQuery queries the KV store for a single key.
41type KVListQuery struct {
42	stopCh chan struct{}
43
44	dc     string
45	prefix string
46}
47
48// NewKVListQuery parses a string into a dependency.
49func NewKVListQuery(s string) (*KVListQuery, error) {
50	if s != "" && !KVListQueryRe.MatchString(s) {
51		return nil, fmt.Errorf("kv.list: invalid format: %q", s)
52	}
53
54	m := regexpMatch(KVListQueryRe, s)
55	return &KVListQuery{
56		stopCh: make(chan struct{}, 1),
57		dc:     m["dc"],
58		prefix: m["prefix"],
59	}, nil
60}
61
62// Fetch queries the Consul API defined by the given client.
63func (d *KVListQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
64	select {
65	case <-d.stopCh:
66		return nil, nil, ErrStopped
67	default:
68	}
69
70	opts = opts.Merge(&QueryOptions{
71		Datacenter: d.dc,
72	})
73
74	log.Printf("[TRACE] %s: GET %s", d, &url.URL{
75		Path:     "/v1/kv/" + d.prefix,
76		RawQuery: opts.String(),
77	})
78
79	list, qm, err := clients.Consul().KV().List(d.prefix, opts.ToConsulOpts())
80	if err != nil {
81		return nil, nil, errors.Wrap(err, d.String())
82	}
83
84	log.Printf("[TRACE] %s: returned %d pairs", d, len(list))
85
86	pairs := make([]*KeyPair, 0, len(list))
87	for _, pair := range list {
88		key := strings.TrimPrefix(pair.Key, d.prefix)
89		key = strings.TrimLeft(key, "/")
90
91		pairs = append(pairs, &KeyPair{
92			Path:        pair.Key,
93			Key:         key,
94			Value:       string(pair.Value),
95			CreateIndex: pair.CreateIndex,
96			ModifyIndex: pair.ModifyIndex,
97			LockIndex:   pair.LockIndex,
98			Flags:       pair.Flags,
99			Session:     pair.Session,
100		})
101	}
102
103	rm := &ResponseMetadata{
104		LastIndex:   qm.LastIndex,
105		LastContact: qm.LastContact,
106	}
107
108	return pairs, rm, nil
109}
110
111// CanShare returns a boolean if this dependency is shareable.
112func (d *KVListQuery) CanShare() bool {
113	return true
114}
115
116// String returns the human-friendly version of this dependency.
117func (d *KVListQuery) String() string {
118	prefix := d.prefix
119	if d.dc != "" {
120		prefix = prefix + "@" + d.dc
121	}
122	return fmt.Sprintf("kv.list(%s)", prefix)
123}
124
125// Stop halts the dependency's fetch function.
126func (d *KVListQuery) Stop() {
127	close(d.stopCh)
128}
129
130// Type returns the type of this dependency.
131func (d *KVListQuery) Type() Type {
132	return TypeConsul
133}
134