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