Merge pull request #2803 from hashicorp/f-update-ct

Update consul-template
This commit is contained in:
Michael Schurter 2017-07-07 15:03:46 -07:00 committed by GitHub
commit fe69901fb9
15 changed files with 273 additions and 173 deletions

View file

@ -242,58 +242,6 @@ func Parse(s string) (*Config, error) {
} }
} }
// TODO: Deprecations
if vault, ok := parsed["vault"].(map[string]interface{}); ok {
if val, ok := vault["renew"]; ok {
log.Println(`[WARN] vault.renew has been renamed to vault.renew_token. ` +
`Update your configuration files and change "renew" to "renew_token".`)
vault["renew_token"] = val
delete(vault, "renew")
}
}
if auth, ok := parsed["auth"].(map[string]interface{}); ok {
log.Println("[WARN] auth has been moved under the consul stanza. " +
"Update your configuration files and place auth inside consul { }.")
if _, ok := parsed["consul"]; !ok {
parsed["consul"] = make(map[string]interface{})
}
parsed["consul"].(map[string]interface{})["auth"] = auth
delete(parsed, "auth")
}
if retry, ok := parsed["retry"].(string); ok {
log.Println("[WARN] retry has been moved under the consul stanza. " +
"Update your configuration files and place retry inside consul { }.")
if _, ok := parsed["consul"]; !ok {
parsed["consul"] = make(map[string]interface{})
}
parsed["consul"].(map[string]interface{})["retry"] = map[string]interface{}{
"backoff": retry,
}
delete(parsed, "retry")
}
if ssl, ok := parsed["ssl"].(map[string]interface{}); ok {
log.Println("[WARN] ssl has been moved under the consul stanza. " +
"Update your configuration files and place ssl inside consul { }.")
if _, ok := parsed["consul"]; !ok {
parsed["consul"] = make(map[string]interface{})
}
parsed["consul"].(map[string]interface{})["ssl"] = ssl
delete(parsed, "ssl")
}
if token, ok := parsed["token"].(string); ok {
log.Println("[WARN] token has been moved under the consul stanza. " +
"Update your configuration files and place token inside consul { }.")
if _, ok := parsed["consul"]; !ok {
parsed["consul"] = make(map[string]interface{})
}
parsed["consul"].(map[string]interface{})["token"] = token
delete(parsed, "token")
}
// Create a new, empty config // Create a new, empty config
var c Config var c Config

View file

@ -56,6 +56,10 @@ type TemplateConfig struct {
// This is required unless running in debug/dry mode. // This is required unless running in debug/dry mode.
Destination *string `mapstructure:"destination"` Destination *string `mapstructure:"destination"`
// ErrMissingKey is used to control how the template behaves when attempting
// to index a struct or map key that does not exist.
ErrMissingKey *bool `mapstructure:"error_on_missing_key"`
// Exec is the configuration for the command to run when the template renders // Exec is the configuration for the command to run when the template renders
// successfully. // successfully.
Exec *ExecConfig `mapstructure:"exec"` Exec *ExecConfig `mapstructure:"exec"`
@ -105,6 +109,8 @@ func (c *TemplateConfig) Copy() *TemplateConfig {
o.Destination = c.Destination o.Destination = c.Destination
o.ErrMissingKey = c.ErrMissingKey
if c.Exec != nil { if c.Exec != nil {
o.Exec = c.Exec.Copy() o.Exec = c.Exec.Copy()
} }
@ -161,6 +167,10 @@ func (c *TemplateConfig) Merge(o *TemplateConfig) *TemplateConfig {
r.Destination = o.Destination r.Destination = o.Destination
} }
if o.ErrMissingKey != nil {
r.ErrMissingKey = o.ErrMissingKey
}
if o.Exec != nil { if o.Exec != nil {
r.Exec = r.Exec.Merge(o.Exec) r.Exec = r.Exec.Merge(o.Exec)
} }
@ -211,6 +221,10 @@ func (c *TemplateConfig) Finalize() {
c.Destination = String("") c.Destination = String("")
} }
if c.ErrMissingKey == nil {
c.ErrMissingKey = Bool(false)
}
if c.Exec == nil { if c.Exec == nil {
c.Exec = DefaultExecConfig() c.Exec = DefaultExecConfig()
} }
@ -258,6 +272,7 @@ func (c *TemplateConfig) GoString() string {
"CommandTimeout:%s, "+ "CommandTimeout:%s, "+
"Contents:%s, "+ "Contents:%s, "+
"Destination:%s, "+ "Destination:%s, "+
"ErrMissingKey:%s, "+
"Exec:%#v, "+ "Exec:%#v, "+
"Perms:%s, "+ "Perms:%s, "+
"Source:%s, "+ "Source:%s, "+
@ -270,6 +285,7 @@ func (c *TemplateConfig) GoString() string {
TimeDurationGoString(c.CommandTimeout), TimeDurationGoString(c.CommandTimeout),
StringGoString(c.Contents), StringGoString(c.Contents),
StringGoString(c.Destination), StringGoString(c.Destination),
BoolGoString(c.ErrMissingKey),
c.Exec, c.Exec,
FileModeGoString(c.Perms), FileModeGoString(c.Perms),
StringGoString(c.Source), StringGoString(c.Source),

View file

@ -8,6 +8,11 @@ import (
) )
const ( const (
// DefaultVaultGrace is the default grace period before which to read a new
// secret from Vault. If a lease is due to expire in 5 minutes, Consul
// Template will read a new secret at that time minus this value.
DefaultVaultGrace = 15 * time.Second
// DefaultVaultRenewToken is the default value for if the Vault token should // DefaultVaultRenewToken is the default value for if the Vault token should
// be renewed. // be renewed.
DefaultVaultRenewToken = true DefaultVaultRenewToken = true
@ -33,6 +38,10 @@ type VaultConfig struct {
// Enabled controls whether the Vault integration is active. // Enabled controls whether the Vault integration is active.
Enabled *bool `mapstructure:"enabled"` Enabled *bool `mapstructure:"enabled"`
// Grace is the amount of time before a lease is about to expire to force a
// new secret to be read.
Grace *time.Duration `mapstructure:"grace"`
// RenewToken renews the Vault token. // RenewToken renews the Vault token.
RenewToken *bool `mapstructure:"renew_token"` RenewToken *bool `mapstructure:"renew_token"`
@ -80,6 +89,8 @@ func (c *VaultConfig) Copy() *VaultConfig {
o.Enabled = c.Enabled o.Enabled = c.Enabled
o.Grace = c.Grace
o.RenewToken = c.RenewToken o.RenewToken = c.RenewToken
if c.Retry != nil { if c.Retry != nil {
@ -127,6 +138,10 @@ func (c *VaultConfig) Merge(o *VaultConfig) *VaultConfig {
r.Enabled = o.Enabled r.Enabled = o.Enabled
} }
if o.Grace != nil {
r.Grace = o.Grace
}
if o.RenewToken != nil { if o.RenewToken != nil {
r.RenewToken = o.RenewToken r.RenewToken = o.RenewToken
} }
@ -162,6 +177,10 @@ func (c *VaultConfig) Finalize() {
}, "") }, "")
} }
if c.Grace == nil {
c.Grace = TimeDuration(DefaultVaultGrace)
}
if c.RenewToken == nil { if c.RenewToken == nil {
c.RenewToken = boolFromEnv([]string{ c.RenewToken = boolFromEnv([]string{
"VAULT_RENEW_TOKEN", "VAULT_RENEW_TOKEN",
@ -239,6 +258,7 @@ func (c *VaultConfig) GoString() string {
return fmt.Sprintf("&VaultConfig{"+ return fmt.Sprintf("&VaultConfig{"+
"Address:%s, "+ "Address:%s, "+
"Enabled:%s, "+ "Enabled:%s, "+
"Grace:%s, "+
"RenewToken:%s, "+ "RenewToken:%s, "+
"Retry:%#v, "+ "Retry:%#v, "+
"SSL:%#v, "+ "SSL:%#v, "+
@ -247,6 +267,7 @@ func (c *VaultConfig) GoString() string {
"UnwrapToken:%s"+ "UnwrapToken:%s"+
"}", "}",
StringGoString(c.Address), StringGoString(c.Address),
TimeDurationGoString(c.Grace),
BoolGoString(c.Enabled), BoolGoString(c.Enabled),
BoolGoString(c.RenewToken), BoolGoString(c.RenewToken),
c.Retry, c.Retry,

View file

@ -6,6 +6,7 @@ import (
"sort" "sort"
"time" "time"
"github.com/hashicorp/consul/api"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -20,13 +21,16 @@ var (
// CatalogDatacentersQuery is the dependency to query all datacenters // CatalogDatacentersQuery is the dependency to query all datacenters
type CatalogDatacentersQuery struct { type CatalogDatacentersQuery struct {
ignoreFailing bool
stopCh chan struct{} stopCh chan struct{}
} }
// NewCatalogDatacentersQuery creates a new datacenter dependency. // NewCatalogDatacentersQuery creates a new datacenter dependency.
func NewCatalogDatacentersQuery() (*CatalogDatacentersQuery, error) { func NewCatalogDatacentersQuery(ignoreFailing bool) (*CatalogDatacentersQuery, error) {
return &CatalogDatacentersQuery{ return &CatalogDatacentersQuery{
stopCh: make(chan struct{}, 1), ignoreFailing: ignoreFailing,
stopCh: make(chan struct{}, 1),
}, nil }, nil
} }
@ -64,6 +68,22 @@ func (d *CatalogDatacentersQuery) Fetch(clients *ClientSet, opts *QueryOptions)
return nil, nil, errors.Wrapf(err, d.String()) return nil, nil, errors.Wrapf(err, d.String())
} }
// If the user opted in for skipping "down" datacenters, figure out which
// datacenters are down.
if d.ignoreFailing {
dcs := make([]string, 0, len(result))
for _, dc := range result {
if _, _, err := clients.Consul().Catalog().Services(&api.QueryOptions{
Datacenter: dc,
AllowStale: false,
RequireConsistent: true,
}); err == nil {
dcs = append(dcs, dc)
}
}
result = dcs
}
log.Printf("[TRACE] %s: returned %d results", d, len(result)) log.Printf("[TRACE] %s: returned %d results", d, len(result))
sort.Strings(result) sort.Strings(result)

View file

@ -1,7 +1,6 @@
package dependency package dependency
import ( import (
"log"
"net/url" "net/url"
"regexp" "regexp"
"sort" "sort"
@ -42,19 +41,6 @@ type Dependency interface {
// ServiceTags is a slice of tags assigned to a Service // ServiceTags is a slice of tags assigned to a Service
type ServiceTags []string type ServiceTags []string
// Contains returns true if the tags exists in the ServiceTags slice.
// This is deprecated and should not be used.
func (t ServiceTags) Contains(s string) bool {
log.Printf("[WARN] .Tags.Contains is deprecated. Use the built-in\n" +
"functions 'in' or 'contains' with a pipe instead.")
for _, v := range t {
if v == s {
return true
}
}
return false
}
// QueryOptions is a list of options to send with the query. These options are // QueryOptions is a list of options to send with the query. These options are
// client-agnostic, and the dependency determines which, if any, of the options // client-agnostic, and the dependency determines which, if any, of the options
// to use. // to use.
@ -63,6 +49,7 @@ type QueryOptions struct {
Datacenter string Datacenter string
Near string Near string
RequireConsistent bool RequireConsistent bool
VaultGrace time.Duration
WaitIndex uint64 WaitIndex uint64
WaitTime time.Duration WaitTime time.Duration
} }

View file

@ -26,3 +26,14 @@ func leaseDurationOrDefault(d int) int {
} }
return d return d
} }
// vaultRenewDuration accepts a given renew duration (lease duration) and
// returns the cooresponding time.Duration. If the duration is 0 (not provided),
// this falls back to the VaultDefaultLeaseDuration.
func vaultRenewDuration(d int) time.Duration {
dur := time.Duration(d/2.0) * time.Second
if dur == 0 {
dur = VaultDefaultLeaseDuration
}
return dur
}

View file

@ -50,10 +50,7 @@ func (d *VaultReadQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfac
// If this is not the first query and we have a lease duration, sleep until we // If this is not the first query and we have a lease duration, sleep until we
// try to renew. // try to renew.
if opts.WaitIndex != 0 && d.secret != nil && d.secret.LeaseDuration != 0 { if opts.WaitIndex != 0 && d.secret != nil && d.secret.LeaseDuration != 0 {
dur := time.Duration(d.secret.LeaseDuration/2.0) * time.Second dur := vaultRenewDuration(d.secret.LeaseDuration)
if dur == 0 {
dur = VaultDefaultLeaseDuration
}
log.Printf("[TRACE] %s: long polling for %s", d, dur) log.Printf("[TRACE] %s: long polling for %s", d, dur)
@ -76,6 +73,9 @@ func (d *VaultReadQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfac
if err == nil { if err == nil {
log.Printf("[TRACE] %s: successfully renewed %s", d, d.secret.LeaseID) log.Printf("[TRACE] %s: successfully renewed %s", d, d.secret.LeaseID)
// Print any warnings
d.printWarnings(renewal.Warnings)
secret := &Secret{ secret := &Secret{
RequestID: renewal.RequestID, RequestID: renewal.RequestID,
LeaseID: renewal.LeaseID, LeaseID: renewal.LeaseID,
@ -83,8 +83,25 @@ func (d *VaultReadQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfac
Renewable: renewal.Renewable, Renewable: renewal.Renewable,
Data: d.secret.Data, Data: d.secret.Data,
} }
// For some older versions of Vault, the renewal did not include the
// remaining lease duration, so just use the original lease duration,
// because it's the best we can do.
if renewal.LeaseDuration != 0 {
secret.LeaseDuration = renewal.LeaseDuration
}
d.secret = secret d.secret = secret
// If the remaining time on the lease is less than or equal to our
// configured grace period, generate a new credential now. This will help
// minimize downtime, since Vault will revoke credentials immediately
// when their maximum TTL expires.
remaining := time.Duration(d.secret.LeaseDuration) * time.Second
if remaining <= opts.VaultGrace {
log.Printf("[DEBUG] %s: remaining lease (%s) < grace (%s), acquiring new",
d, remaining, opts.VaultGrace)
return d.readSecret(clients, opts)
}
return respWithMetadata(secret) return respWithMetadata(secret)
} }
@ -94,35 +111,7 @@ func (d *VaultReadQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfac
// If we got this far, we either didn't have a secret to renew, the secret was // If we got this far, we either didn't have a secret to renew, the secret was
// not renewable, or the renewal failed, so attempt a fresh read. // not renewable, or the renewal failed, so attempt a fresh read.
log.Printf("[TRACE] %s: GET %s", d, &url.URL{ return d.readSecret(clients, opts)
Path: "/v1/" + d.path,
RawQuery: opts.String(),
})
vaultSecret, err := clients.Vault().Logical().Read(d.path)
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}
// The secret could be nil if it does not exist.
if vaultSecret == nil {
return nil, nil, fmt.Errorf("%s: no secret exists at %s", d, d.path)
}
// Print any warnings.
for _, w := range vaultSecret.Warnings {
log.Printf("[WARN] %s: %s", d, w)
}
// Create our cloned secret.
secret := &Secret{
LeaseID: vaultSecret.LeaseID,
LeaseDuration: leaseDurationOrDefault(vaultSecret.LeaseDuration),
Renewable: vaultSecret.Renewable,
Data: vaultSecret.Data,
}
d.secret = secret
return respWithMetadata(secret)
} }
// CanShare returns if this dependency is shareable. // CanShare returns if this dependency is shareable.
@ -144,3 +133,39 @@ func (d *VaultReadQuery) String() string {
func (d *VaultReadQuery) Type() Type { func (d *VaultReadQuery) Type() Type {
return TypeVault return TypeVault
} }
func (d *VaultReadQuery) printWarnings(warnings []string) {
for _, w := range warnings {
log.Printf("[WARN] %s: %s", d, w)
}
}
func (d *VaultReadQuery) readSecret(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
log.Printf("[TRACE] %s: GET %s", d, &url.URL{
Path: "/v1/" + d.path,
RawQuery: opts.String(),
})
vaultSecret, err := clients.Vault().Logical().Read(d.path)
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}
// The secret could be nil if it does not exist.
if vaultSecret == nil {
return nil, nil, fmt.Errorf("%s: no secret exists at %s", d, d.path)
}
// Print any warnings.
d.printWarnings(vaultSecret.Warnings)
// Create our cloned secret.
secret := &Secret{
LeaseID: vaultSecret.LeaseID,
LeaseDuration: leaseDurationOrDefault(vaultSecret.LeaseDuration),
Renewable: vaultSecret.Renewable,
Data: vaultSecret.Data,
}
d.secret = secret
return respWithMetadata(secret)
}

View file

@ -46,10 +46,7 @@ func (d *VaultTokenQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfa
// If this is not the first query and we have a lease duration, sleep until we // If this is not the first query and we have a lease duration, sleep until we
// try to renew. // try to renew.
if opts.WaitIndex != 0 && d.leaseDuration != 0 { if opts.WaitIndex != 0 && d.leaseDuration != 0 {
dur := time.Duration(d.leaseDuration/2.0) * time.Second dur := vaultRenewDuration(d.leaseDuration)
if dur == 0 {
dur = VaultDefaultLeaseDuration
}
log.Printf("[TRACE] %s: long polling for %s", d, dur) log.Printf("[TRACE] %s: long polling for %s", d, dur)

View file

@ -57,10 +57,7 @@ func (d *VaultWriteQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfa
// If this is not the first query and we have a lease duration, sleep until we // If this is not the first query and we have a lease duration, sleep until we
// try to renew. // try to renew.
if opts.WaitIndex != 0 && d.secret != nil && d.secret.LeaseDuration != 0 { if opts.WaitIndex != 0 && d.secret != nil && d.secret.LeaseDuration != 0 {
dur := time.Duration(d.secret.LeaseDuration/2.0) * time.Second dur := vaultRenewDuration(d.secret.LeaseDuration)
if dur == 0 {
dur = VaultDefaultLeaseDuration
}
log.Printf("[TRACE] %s: long polling for %s", d, dur) log.Printf("[TRACE] %s: long polling for %s", d, dur)
@ -83,15 +80,34 @@ func (d *VaultWriteQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfa
if err == nil { if err == nil {
log.Printf("[TRACE] %s: successfully renewed %s", d, d.secret.LeaseID) log.Printf("[TRACE] %s: successfully renewed %s", d, d.secret.LeaseID)
// Print any warnings
d.printWarnings(renewal.Warnings)
secret := &Secret{ secret := &Secret{
RequestID: renewal.RequestID, RequestID: renewal.RequestID,
LeaseID: renewal.LeaseID, LeaseID: renewal.LeaseID,
LeaseDuration: d.secret.LeaseDuration, Renewable: renewal.Renewable,
Renewable: renewal.Renewable, Data: d.secret.Data,
Data: d.secret.Data, }
// For some older versions of Vault, the renewal did not include the
// remaining lease duration, so just use the original lease duration,
// because it's the best we can do.
if renewal.LeaseDuration != 0 {
secret.LeaseDuration = renewal.LeaseDuration
} }
d.secret = secret d.secret = secret
// If the remaining time on the lease is less than or equal to our
// configured grace period, generate a new credential now. This will help
// minimize downtime, since Vault will revoke credentials immediately
// when their maximum TTL expires.
remaining := time.Duration(d.secret.LeaseDuration) * time.Second
if remaining <= opts.VaultGrace {
log.Printf("[DEBUG] %s: remaining lease (%s) < grace (%s), acquiring new",
d, remaining, opts.VaultGrace)
return d.writeSecret(clients, opts)
}
return respWithMetadata(secret) return respWithMetadata(secret)
} }
@ -101,36 +117,7 @@ func (d *VaultWriteQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interfa
// If we got this far, we either didn't have a secret to renew, the secret was // If we got this far, we either didn't have a secret to renew, the secret was
// not renewable, or the renewal failed, so attempt a fresh write. // not renewable, or the renewal failed, so attempt a fresh write.
log.Printf("[TRACE] %s: PUT %s", d, &url.URL{ return d.writeSecret(clients, opts)
Path: "/v1/" + d.path,
RawQuery: opts.String(),
})
vaultSecret, err := clients.Vault().Logical().Write(d.path, d.data)
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}
// The secret could be nil if it does not exist.
if vaultSecret == nil {
return nil, nil, fmt.Errorf("%s: no secret exists at %s", d, d.path)
}
// Print any warnings.
for _, w := range vaultSecret.Warnings {
log.Printf("[WARN] %s: %s", d, w)
}
// Create our cloned secret.
secret := &Secret{
LeaseID: vaultSecret.LeaseID,
LeaseDuration: leaseDurationOrDefault(vaultSecret.LeaseDuration),
Renewable: vaultSecret.Renewable,
Data: vaultSecret.Data,
}
d.secret = secret
return respWithMetadata(secret)
} }
// CanShare returns if this dependency is shareable. // CanShare returns if this dependency is shareable.
@ -170,3 +157,42 @@ func sha1Map(m map[string]interface{}) string {
return fmt.Sprintf("%.4x", h.Sum(nil)) return fmt.Sprintf("%.4x", h.Sum(nil))
} }
func (d *VaultWriteQuery) printWarnings(warnings []string) {
for _, w := range warnings {
log.Printf("[WARN] %s: %s", d, w)
}
}
func (d *VaultWriteQuery) writeSecret(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
log.Printf("[TRACE] %s: PUT %s", d, &url.URL{
Path: "/v1/" + d.path,
RawQuery: opts.String(),
})
vaultSecret, err := clients.Vault().Logical().Write(d.path, d.data)
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}
// The secret could be nil if it does not exist.
if vaultSecret == nil {
return nil, nil, fmt.Errorf("%s: no secret exists at %s", d, d.path)
}
// Print any warnings.
for _, w := range vaultSecret.Warnings {
log.Printf("[WARN] %s: %s", d, w)
}
// Create our cloned secret.
secret := &Secret{
LeaseID: vaultSecret.LeaseID,
LeaseDuration: leaseDurationOrDefault(vaultSecret.LeaseDuration),
Renewable: vaultSecret.Renewable,
Data: vaultSecret.Data,
}
d.secret = secret
return respWithMetadata(secret)
}

View file

@ -776,10 +776,11 @@ func (r *Runner) init() error {
// destinations. // destinations.
for _, ctmpl := range *r.config.Templates { for _, ctmpl := range *r.config.Templates {
tmpl, err := template.NewTemplate(&template.NewTemplateInput{ tmpl, err := template.NewTemplate(&template.NewTemplateInput{
Source: config.StringVal(ctmpl.Source), Source: config.StringVal(ctmpl.Source),
Contents: config.StringVal(ctmpl.Contents), Contents: config.StringVal(ctmpl.Contents),
LeftDelim: config.StringVal(ctmpl.LeftDelim), ErrMissingKey: config.BoolVal(ctmpl.ErrMissingKey),
RightDelim: config.StringVal(ctmpl.RightDelim), LeftDelim: config.StringVal(ctmpl.LeftDelim),
RightDelim: config.StringVal(ctmpl.RightDelim),
}) })
if err != nil { if err != nil {
return err return err
@ -1173,6 +1174,7 @@ func newWatcher(c *config.Config, clients *dep.ClientSet, once bool) (*watch.Wat
// dependencies like reading a file from disk. // dependencies like reading a file from disk.
RetryFuncDefault: nil, RetryFuncDefault: nil,
RetryFuncVault: watch.RetryFunc(c.Vault.Retry.RetryFunc()), RetryFuncVault: watch.RetryFunc(c.Vault.Retry.RetryFunc()),
VaultGrace: config.TimeDurationVal(c.Vault.Grace),
}) })
if err != nil { if err != nil {
return nil, errors.Wrap(err, "runner") return nil, errors.Wrap(err, "runner")

View file

@ -26,11 +26,22 @@ import (
var now = func() time.Time { return time.Now().UTC() } var now = func() time.Time { return time.Now().UTC() }
// datacentersFunc returns or accumulates datacenter dependencies. // datacentersFunc returns or accumulates datacenter dependencies.
func datacentersFunc(b *Brain, used, missing *dep.Set) func() ([]string, error) { func datacentersFunc(b *Brain, used, missing *dep.Set) func(ignore ...bool) ([]string, error) {
return func() ([]string, error) { return func(i ...bool) ([]string, error) {
result := []string{} result := []string{}
d, err := dep.NewCatalogDatacentersQuery() var ignore bool
switch len(i) {
case 0:
ignore = false
case 1:
ignore = i[0]
default:
return result, fmt.Errorf("datacenters: wrong number of arguments, expected 0 or 1"+
", but got %d", len(i))
}
d, err := dep.NewCatalogDatacentersQuery(ignore)
if err != nil { if err != nil {
return result, err return result, err
} }

View file

@ -41,6 +41,10 @@ type Template struct {
// hexMD5 stores the hex version of the MD5 // hexMD5 stores the hex version of the MD5
hexMD5 string hexMD5 string
// errMissingKey causes the template processing to exit immediately if a map
// is indexed with a key that does not exist.
errMissingKey bool
} }
// NewTemplateInput is used as input when creating the template. // NewTemplateInput is used as input when creating the template.
@ -51,6 +55,10 @@ type NewTemplateInput struct {
// Contents are the raw template contents. // Contents are the raw template contents.
Contents string Contents string
// ErrMissingKey causes the template parser to exit immediately with an error
// when a map is indexed with a key that does not exist.
ErrMissingKey bool
// LeftDelim and RightDelim are the template delimiters. // LeftDelim and RightDelim are the template delimiters.
LeftDelim string LeftDelim string
RightDelim string RightDelim string
@ -77,6 +85,7 @@ func NewTemplate(i *NewTemplateInput) (*Template, error) {
t.contents = i.Contents t.contents = i.Contents
t.leftDelim = i.LeftDelim t.leftDelim = i.LeftDelim
t.rightDelim = i.RightDelim t.rightDelim = i.RightDelim
t.errMissingKey = i.ErrMissingKey
if i.Source != "" { if i.Source != "" {
contents, err := ioutil.ReadFile(i.Source) contents, err := ioutil.ReadFile(i.Source)
@ -152,6 +161,12 @@ func (t *Template) Execute(i *ExecuteInput) (*ExecuteResult, error) {
missing: &missing, missing: &missing,
})) }))
if t.errMissingKey {
tmpl.Option("missingkey=error")
} else {
tmpl.Option("missingkey=zero")
}
tmpl, err := tmpl.Parse(t.contents) tmpl, err := tmpl.Parse(t.contents)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "parse") return nil, errors.Wrap(err, "parse")
@ -246,8 +261,5 @@ func funcMap(i *funcMapInput) template.FuncMap {
"multiply": multiply, "multiply": multiply,
"divide": divide, "divide": divide,
"modulo": modulo, "modulo": modulo,
// Deprecated functions
"key_or_default": keyWithDefaultFunc(i.brain, i.used, i.missing),
} }
} }

View file

@ -44,6 +44,11 @@ type View struct {
// stopCh is used to stop polling on this View // stopCh is used to stop polling on this View
stopCh chan struct{} stopCh chan struct{}
// vaultGrace is the grace period between a lease and the max TTL for which
// Consul Template will generate a new secret instead of renewing an existing
// one.
vaultGrace time.Duration
} }
// NewViewInput is used as input to the NewView function. // NewViewInput is used as input to the NewView function.
@ -65,6 +70,11 @@ type NewViewInput struct {
// RetryFunc is a function which dictates how this view should retry on // RetryFunc is a function which dictates how this view should retry on
// upstream errors. // upstream errors.
RetryFunc RetryFunc RetryFunc RetryFunc
// VaultGrace is the grace period between a lease and the max TTL for which
// Consul Template will generate a new secret instead of renewing an existing
// one.
VaultGrace time.Duration
} }
// NewView constructs a new view with the given inputs. // NewView constructs a new view with the given inputs.
@ -76,6 +86,7 @@ func NewView(i *NewViewInput) (*View, error) {
once: i.Once, once: i.Once,
retryFunc: i.RetryFunc, retryFunc: i.RetryFunc,
stopCh: make(chan struct{}, 1), stopCh: make(chan struct{}, 1),
vaultGrace: i.VaultGrace,
}, nil }, nil
} }
@ -200,6 +211,7 @@ func (v *View) fetch(doneCh, successCh chan<- struct{}, errCh chan<- error) {
AllowStale: allowStale, AllowStale: allowStale,
WaitTime: defaultWaitTime, WaitTime: defaultWaitTime,
WaitIndex: v.lastIndex, WaitIndex: v.lastIndex,
VaultGrace: v.vaultGrace,
}) })
if err != nil { if err != nil {
if err == dep.ErrStopped { if err == dep.ErrStopped {

View file

@ -42,6 +42,11 @@ type Watcher struct {
retryFuncConsul RetryFunc retryFuncConsul RetryFunc
retryFuncDefault RetryFunc retryFuncDefault RetryFunc
retryFuncVault RetryFunc retryFuncVault RetryFunc
// vaultGrace is the grace period between a lease and the max TTL for which
// Consul Template will generate a new secret instead of renewing an existing
// one.
vaultGrace time.Duration
} }
type NewWatcherInput struct { type NewWatcherInput struct {
@ -61,6 +66,11 @@ type NewWatcherInput struct {
RetryFuncConsul RetryFunc RetryFuncConsul RetryFunc
RetryFuncDefault RetryFunc RetryFuncDefault RetryFunc
RetryFuncVault RetryFunc RetryFuncVault RetryFunc
// VaultGrace is the grace period between a lease and the max TTL for which
// Consul Template will generate a new secret instead of renewing an existing
// one.
VaultGrace time.Duration
} }
// NewWatcher creates a new watcher using the given API client. // NewWatcher creates a new watcher using the given API client.
@ -75,6 +85,7 @@ func NewWatcher(i *NewWatcherInput) (*Watcher, error) {
retryFuncConsul: i.RetryFuncConsul, retryFuncConsul: i.RetryFuncConsul,
retryFuncDefault: i.RetryFuncDefault, retryFuncDefault: i.RetryFuncDefault,
retryFuncVault: i.RetryFuncVault, retryFuncVault: i.RetryFuncVault,
vaultGrace: i.VaultGrace,
} }
// Start a watcher for the Vault renew if that config was specified // Start a watcher for the Vault renew if that config was specified
@ -138,6 +149,7 @@ func (w *Watcher) Add(d dep.Dependency) (bool, error) {
MaxStale: w.maxStale, MaxStale: w.maxStale,
Once: w.once, Once: w.once,
RetryFunc: retryFunc, RetryFunc: retryFunc,
VaultGrace: w.vaultGrace,
}) })
if err != nil { if err != nil {
return false, errors.Wrap(err, "watcher") return false, errors.Wrap(err, "watcher")

38
vendor/vendor.json vendored
View file

@ -599,44 +599,44 @@
{ {
"checksumSHA1": "Nu2j1GusM7ZH0uYrGzqr1K7yH7I=", "checksumSHA1": "Nu2j1GusM7ZH0uYrGzqr1K7yH7I=",
"path": "github.com/hashicorp/consul-template/child", "path": "github.com/hashicorp/consul-template/child",
"revision": "92746fc5cf86dbb113558bacec43459a65c8df14", "revision": "ecbc27c1922fed2f562e7fb63e1ad24e818fa60e",
"revisionTime": "2017-05-26T18:30:17Z" "revisionTime": "2017-07-05T14:04:00Z"
}, },
{ {
"checksumSHA1": "7TBPXChZZS84qZbzP7qFYeQding=", "checksumSHA1": "QWcGW3wELSp/YsOVzCW02oEYR7c=",
"path": "github.com/hashicorp/consul-template/config", "path": "github.com/hashicorp/consul-template/config",
"revision": "92746fc5cf86dbb113558bacec43459a65c8df14", "revision": "ecbc27c1922fed2f562e7fb63e1ad24e818fa60e",
"revisionTime": "2017-05-26T18:30:17Z" "revisionTime": "2017-07-05T14:04:00Z"
}, },
{ {
"checksumSHA1": "7rKifM082rlbHN9EcsVyu7VXLoo=", "checksumSHA1": "mV7yjHpIfO4yRAdQaBlAqdGDKO8=",
"path": "github.com/hashicorp/consul-template/dependency", "path": "github.com/hashicorp/consul-template/dependency",
"revision": "92746fc5cf86dbb113558bacec43459a65c8df14", "revision": "ecbc27c1922fed2f562e7fb63e1ad24e818fa60e",
"revisionTime": "2017-05-26T18:30:17Z" "revisionTime": "2017-07-05T14:04:00Z"
}, },
{ {
"checksumSHA1": "Ci5EmLs/h7ke9bUg7a34UfTbB5U=", "checksumSHA1": "ZTlPhrxNzME75A4ydXM88TFt3Qs=",
"path": "github.com/hashicorp/consul-template/manager", "path": "github.com/hashicorp/consul-template/manager",
"revision": "92746fc5cf86dbb113558bacec43459a65c8df14", "revision": "ecbc27c1922fed2f562e7fb63e1ad24e818fa60e",
"revisionTime": "2017-05-26T18:30:17Z" "revisionTime": "2017-07-05T14:04:00Z"
}, },
{ {
"checksumSHA1": "oskgb0WteBKOItG8NNDduM7E/D0=", "checksumSHA1": "oskgb0WteBKOItG8NNDduM7E/D0=",
"path": "github.com/hashicorp/consul-template/signals", "path": "github.com/hashicorp/consul-template/signals",
"revision": "92746fc5cf86dbb113558bacec43459a65c8df14", "revision": "ecbc27c1922fed2f562e7fb63e1ad24e818fa60e",
"revisionTime": "2017-05-26T18:30:17Z" "revisionTime": "2017-07-05T14:04:00Z"
}, },
{ {
"checksumSHA1": "804hk7BQd6V2xjBwz+cE0hdzSlI=", "checksumSHA1": "zSvJlNfZS3fCRlFaZ7r9Q+N17T8=",
"path": "github.com/hashicorp/consul-template/template", "path": "github.com/hashicorp/consul-template/template",
"revision": "92746fc5cf86dbb113558bacec43459a65c8df14", "revision": "ecbc27c1922fed2f562e7fb63e1ad24e818fa60e",
"revisionTime": "2017-05-26T18:30:17Z" "revisionTime": "2017-07-05T14:04:00Z"
}, },
{ {
"checksumSHA1": "KjcelGP7qPh0ObKouBJuHmXUjqk=", "checksumSHA1": "85W96Fo50FmrMaba7Dk12aDfwWs=",
"path": "github.com/hashicorp/consul-template/watch", "path": "github.com/hashicorp/consul-template/watch",
"revision": "92746fc5cf86dbb113558bacec43459a65c8df14", "revision": "ecbc27c1922fed2f562e7fb63e1ad24e818fa60e",
"revisionTime": "2017-05-26T18:30:17Z" "revisionTime": "2017-07-05T14:04:00Z"
}, },
{ {
"checksumSHA1": "jfELEMRhiTcppZmRH+ZwtkVS5Uw=", "checksumSHA1": "jfELEMRhiTcppZmRH+ZwtkVS5Uw=",