1package dependency
2
3import (
4	"fmt"
5	"log"
6	"net/url"
7	"sort"
8	"strings"
9	"time"
10
11	"github.com/pkg/errors"
12)
13
14var (
15	// Ensure implements
16	_ Dependency = (*VaultListQuery)(nil)
17)
18
19// VaultListQuery is the dependency to Vault for a secret
20type VaultListQuery struct {
21	stopCh chan struct{}
22
23	path string
24}
25
26// NewVaultListQuery creates a new datacenter dependency.
27func NewVaultListQuery(s string) (*VaultListQuery, error) {
28	s = strings.TrimSpace(s)
29	s = strings.Trim(s, "/")
30	if s == "" {
31		return nil, fmt.Errorf("vault.list: invalid format: %q", s)
32	}
33
34	return &VaultListQuery{
35		stopCh: make(chan struct{}, 1),
36		path:   s,
37	}, nil
38}
39
40// Fetch queries the Vault API
41func (d *VaultListQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
42	select {
43	case <-d.stopCh:
44		return nil, nil, ErrStopped
45	default:
46	}
47
48	opts = opts.Merge(&QueryOptions{})
49
50	// If this is not the first query, poll to simulate blocking-queries.
51	if opts.WaitIndex != 0 {
52		dur := VaultDefaultLeaseDuration
53		log.Printf("[TRACE] %s: long polling for %s", d, dur)
54
55		select {
56		case <-d.stopCh:
57			return nil, nil, ErrStopped
58		case <-time.After(dur):
59		}
60	}
61
62	// If we got this far, we either didn't have a secret to renew, the secret was
63	// not renewable, or the renewal failed, so attempt a fresh list.
64	log.Printf("[TRACE] %s: LIST %s", d, &url.URL{
65		Path:     "/v1/" + d.path,
66		RawQuery: opts.String(),
67	})
68	secret, err := clients.Vault().Logical().List(d.path)
69	if err != nil {
70		return nil, nil, errors.Wrap(err, d.String())
71	}
72
73	var result []string
74
75	// The secret could be nil if it does not exist.
76	if secret == nil || secret.Data == nil {
77		log.Printf("[TRACE] %s: no data", d)
78		return respWithMetadata(result)
79	}
80
81	// This is a weird thing that happened once...
82	keys, ok := secret.Data["keys"]
83	if !ok {
84		log.Printf("[TRACE] %s: no keys", d)
85		return respWithMetadata(result)
86	}
87
88	list, ok := keys.([]interface{})
89	if !ok {
90		log.Printf("[TRACE] %s: not list", d)
91		return nil, nil, fmt.Errorf("%s: unexpected response", d)
92	}
93
94	for _, v := range list {
95		typed, ok := v.(string)
96		if !ok {
97			return nil, nil, fmt.Errorf("%s: non-string in list", d)
98		}
99		result = append(result, typed)
100	}
101	sort.Strings(result)
102
103	log.Printf("[TRACE] %s: returned %d results", d, len(result))
104
105	return respWithMetadata(result)
106}
107
108// CanShare returns if this dependency is shareable.
109func (d *VaultListQuery) CanShare() bool {
110	return false
111}
112
113// Stop halts the given dependency's fetch.
114func (d *VaultListQuery) Stop() {
115	close(d.stopCh)
116}
117
118// String returns the human-friendly version of this dependency.
119func (d *VaultListQuery) String() string {
120	return fmt.Sprintf("vault.list(%s)", d.path)
121}
122
123// Type returns the type of this dependency.
124func (d *VaultListQuery) Type() Type {
125	return TypeVault
126}
127