Rework agent retry config, extend it to cover proxy cache as well (#11113)

Remove template_retry config section.  Add new vault.retry section which only has num_retries field; if num_retries is 0 or absent, default it to 12 for backwards compat with pre-1.7 template retrying.  Setting num_retries=-1 disables retries.

Configured retries are used for both templating and api proxy, though if template requests go through proxy (currently requires persistence enabled) we'll only configure retries for the latter to avoid duplicate retrying.  Though there is some duplicate retrying already because whenever the template server does a retry when not going through the proxy, the Vault client it uses allows for 2 behind-the-scenes retries for some 400/500 http error codes.
This commit is contained in:
Nick Cabatoff 2021-03-18 14:14:09 -04:00 committed by GitHub
parent 5353279e75
commit 9c5f018938
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 593 additions and 96 deletions

3
changelog/11113.txt Normal file
View File

@ -0,0 +1,3 @@
```changelog:enhancement
agent: Add a vault.retry stanza that allows specifying number of retries on failure; this applies both to templating and proxied requests.
```

View File

@ -234,13 +234,6 @@ func (c *AgentCommand) Run(args []string) int {
c.UI.Info("No auto_auth block found in config file, not starting automatic authentication feature")
}
// create an empty Vault configuration if none was loaded from file. The
// follow-up setStringFlag calls will populate with defaults if otherwise
// omitted
if config.Vault == nil {
config.Vault = new(agentConfig.Vault)
}
exitAfterAuth := config.ExitAfterAuth
f.Visit(func(fl *flag.Flag) {
if fl.Name == "exit-after-auth" {
@ -421,6 +414,13 @@ func (c *AgentCommand) Run(args []string) int {
}
}
// We do this after auto-auth has been configured, because we don't want to
// confuse the issue of retries for auth failures which have their own
// config and are handled a bit differently.
if os.Getenv(api.EnvVaultMaxRetries) == "" {
client.SetMaxRetries(config.Vault.Retry.NumRetries)
}
enforceConsistency := cache.EnforceConsistencyNever
whenInconsistent := cache.WhenInconsistentFail
if config.Cache != nil {
@ -721,9 +721,8 @@ func (c *AgentCommand) Run(args []string) int {
}
// Inform any tests that the server is ready
select {
case c.startedCh <- struct{}{}:
default:
if c.startedCh != nil {
close(c.startedCh)
}
// Listen for signals

View File

@ -28,7 +28,10 @@ type Config struct {
Cache *Cache `hcl:"cache"`
Vault *Vault `hcl:"vault"`
Templates []*ctconfig.TemplateConfig `hcl:"templates"`
TemplateRetry *TemplateRetry `hcl:"template_retry"`
}
type Retry struct {
NumRetries int `hcl:"num_retries"`
}
// Vault contains configuration for connecting to Vault servers
@ -41,6 +44,7 @@ type Vault struct {
ClientCert string `hcl:"client_cert"`
ClientKey string `hcl:"client_key"`
TLSServerName string `hcl:"tls_server_name"`
Retry *Retry `hcl:"retry"`
}
// Cache contains any configuration needed for Cache mode
@ -97,15 +101,6 @@ type Sink struct {
Config map[string]interface{}
}
type TemplateRetry struct {
Enabled bool `hcl:"enabled"`
Attempts int `hcl:"attempts"`
BackoffRaw interface{} `hcl:"backoff"`
Backoff time.Duration `hcl:"-"`
MaxBackoffRaw interface{} `hcl:"max_backoff"`
MaxBackoff time.Duration `hcl:"-"`
}
func NewConfig() *Config {
return &Config{
SharedConfig: new(configutil.SharedConfig),
@ -193,8 +188,19 @@ func LoadConfig(path string) (*Config, error) {
return nil, errwrap.Wrapf("error parsing 'vault':{{err}}", err)
}
if err := parseRetry(result, list); err != nil {
return nil, errwrap.Wrapf("error parsing 'retry': {{err}}", err)
if result.Vault == nil {
result.Vault = &Vault{}
}
// Set defaults
if result.Vault.Retry == nil {
result.Vault.Retry = &Retry{}
}
switch result.Vault.Retry.NumRetries {
case 0:
result.Vault.Retry.NumRetries = ctconfig.DefaultRetryAttempts
case -1:
result.Vault.Retry.NumRetries = 0
}
return result, nil
@ -229,11 +235,20 @@ func parseVault(result *Config, list *ast.ObjectList) error {
result.Vault = &v
subs, ok := item.Val.(*ast.ObjectType)
if !ok {
return fmt.Errorf("could not parse %q as an object", name)
}
if err := parseRetry(result, subs.List); err != nil {
return errwrap.Wrapf("error parsing 'retry': {{err}}", err)
}
return nil
}
func parseRetry(result *Config, list *ast.ObjectList) error {
name := "template_retry"
name := "retry"
retryList := list.Filter(name)
if len(retryList.Items) == 0 {
@ -246,36 +261,13 @@ func parseRetry(result *Config, list *ast.ObjectList) error {
item := retryList.Items[0]
var r TemplateRetry
var r Retry
err := hcl.DecodeObject(&r, item.Val)
if err != nil {
return err
}
// Set defaults
if r.Attempts < 1 {
r.Attempts = ctconfig.DefaultRetryAttempts
}
r.Backoff = ctconfig.DefaultRetryBackoff
r.MaxBackoff = ctconfig.DefaultRetryMaxBackoff
if r.BackoffRaw != nil {
var err error
if r.Backoff, err = parseutil.ParseDurationSecond(r.BackoffRaw); err != nil {
return err
}
r.BackoffRaw = nil
}
if r.MaxBackoffRaw != nil {
var err error
if r.MaxBackoff, err = parseutil.ParseDurationSecond(r.MaxBackoffRaw); err != nil {
return err
}
r.MaxBackoffRaw = nil
}
result.TemplateRetry = &r
result.Vault.Retry = &r
return nil
}

View File

@ -82,6 +82,9 @@ func TestLoadConfigFile_AgentCache(t *testing.T) {
TLSSkipVerify: true,
ClientCert: "config_client_cert",
ClientKey: "config_client_key",
Retry: &Retry{
NumRetries: 12,
},
},
}
@ -158,6 +161,11 @@ func TestLoadConfigFile(t *testing.T) {
},
},
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
if diff := deep.Equal(config, expected); diff != nil {
@ -203,6 +211,11 @@ func TestLoadConfigFile_Method_Wrapping(t *testing.T) {
},
},
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
if diff := deep.Equal(config, expected); diff != nil {
@ -228,6 +241,11 @@ func TestLoadConfigFile_AgentCache_NoAutoAuth(t *testing.T) {
},
},
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
config.Listeners[0].RawConfig = nil
@ -316,6 +334,11 @@ func TestLoadConfigFile_AgentCache_AutoAuth_NoSink(t *testing.T) {
UseAutoAuthTokenRaw: true,
ForceAutoAuthToken: false,
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
config.Listeners[0].RawConfig = nil
@ -355,6 +378,11 @@ func TestLoadConfigFile_AgentCache_AutoAuth_Force(t *testing.T) {
UseAutoAuthTokenRaw: "force",
ForceAutoAuthToken: true,
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
config.Listeners[0].RawConfig = nil
@ -394,6 +422,11 @@ func TestLoadConfigFile_AgentCache_AutoAuth_True(t *testing.T) {
UseAutoAuthTokenRaw: "true",
ForceAutoAuthToken: false,
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
config.Listeners[0].RawConfig = nil
@ -444,6 +477,11 @@ func TestLoadConfigFile_AgentCache_AutoAuth_False(t *testing.T) {
UseAutoAuthTokenRaw: "false",
ForceAutoAuthToken: false,
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
config.Listeners[0].RawConfig = nil
@ -478,6 +516,11 @@ func TestLoadConfigFile_AgentCache_Persist(t *testing.T) {
},
},
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
config.Listeners[0].RawConfig = nil
@ -593,6 +636,11 @@ func TestLoadConfigFile_Template(t *testing.T) {
},
},
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
Templates: tc.expectedTemplates,
}
@ -689,6 +737,11 @@ func TestLoadConfigFile_Template_NoSinks(t *testing.T) {
Sinks: nil,
},
Templates: tc.expectedTemplates,
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
if diff := deep.Equal(config, expected); diff != nil {
@ -731,14 +784,9 @@ func TestLoadConfigFile_Vault_Retry(t *testing.T) {
},
Vault: &Vault{
Address: "http://127.0.0.1:1111",
},
TemplateRetry: &TemplateRetry{
Enabled: true,
Attempts: 5,
BackoffRaw: nil,
Backoff: 100 * time.Millisecond,
MaxBackoffRaw: nil,
MaxBackoff: 400 * time.Millisecond,
Retry: &Retry{
NumRetries: 5,
},
},
}
@ -780,14 +828,9 @@ func TestLoadConfigFile_Vault_Retry_Empty(t *testing.T) {
},
Vault: &Vault{
Address: "http://127.0.0.1:1111",
},
TemplateRetry: &TemplateRetry{
Enabled: false,
Attempts: ctconfig.DefaultRetryAttempts,
BackoffRaw: nil,
Backoff: ctconfig.DefaultRetryBackoff,
MaxBackoffRaw: nil,
MaxBackoff: ctconfig.DefaultRetryMaxBackoff,
Retry: &Retry{
ctconfig.DefaultRetryAttempts,
},
},
}
@ -817,6 +860,11 @@ func TestLoadConfigFile_EnforceConsistency(t *testing.T) {
EnforceConsistency: "always",
WhenInconsistent: "retry",
},
Vault: &Vault{
Retry: &Retry{
NumRetries: 12,
},
},
}
config.Listeners[0].RawConfig = nil

View File

@ -23,6 +23,6 @@ auto_auth {
vault {
address = "http://127.0.0.1:1111"
retry {}
}
template_retry {}

View File

@ -23,11 +23,7 @@ auto_auth {
vault {
address = "http://127.0.0.1:1111"
}
template_retry {
enabled = true
attempts = 5
backoff = "100ms"
max_backoff = "400ms"
retry {
num_retries = 5
}
}

View File

@ -65,10 +65,6 @@ type Server struct {
logger hclog.Logger
exitAfterAuth bool
// testingLimitRetry is used for tests to limit the number of retries
// performed by the template server
testingLimitRetry int
}
// NewServer returns a new configured server
@ -164,19 +160,6 @@ func (ts *Server) Run(ctx context.Context, incoming chan string, templates []*ct
},
}
if ts.config.AgentConfig.TemplateRetry != nil && ts.config.AgentConfig.TemplateRetry.Enabled {
ctv.Vault.Retry = &ctconfig.RetryConfig{
Attempts: &ts.config.AgentConfig.TemplateRetry.Attempts,
Backoff: &ts.config.AgentConfig.TemplateRetry.Backoff,
MaxBackoff: &ts.config.AgentConfig.TemplateRetry.MaxBackoff,
Enabled: &ts.config.AgentConfig.TemplateRetry.Enabled,
}
} else if ts.testingLimitRetry != 0 {
// If we're testing, limit retries to 3 attempts to avoid
// long test runs from exponential back-offs
ctv.Vault.Retry = &ctconfig.RetryConfig{Attempts: &ts.testingLimitRetry}
}
runnerConfig = runnerConfig.Merge(&ctv)
var runnerErr error
ts.runner, runnerErr = manager.NewRunner(runnerConfig, false)
@ -255,8 +238,21 @@ func newRunnerConfig(sc *ServerConfig, templates ctconfig.TemplateConfigs) (*ctc
ServerName: pointerutil.StringPtr(""),
}
// The cache does its own retry management based on sc.AgentConfig.Retry,
// so we only want to set this up for templating if we're not routing
// templating through the cache. We do need to assign something to Retry
// though or it will use its default of 12 retries.
var attempts int
if sc.AgentConfig.Vault != nil && sc.AgentConfig.Vault.Retry != nil {
attempts = sc.AgentConfig.Vault.Retry.NumRetries
}
// Use the cache if available or fallback to the Vault server values.
// For now we're only routing templating through the cache when persistence
// is enabled. The templating engine and the cache have some inconsistencies
// that need to be fixed for 1.7x/1.8
if sc.AgentConfig.Cache != nil && sc.AgentConfig.Cache.Persist != nil && len(sc.AgentConfig.Listeners) != 0 {
attempts = 0
scheme := "unix://"
if sc.AgentConfig.Listeners[0].Type == "tcp" {
scheme = "https://"
@ -286,6 +282,11 @@ func newRunnerConfig(sc *ServerConfig, templates ctconfig.TemplateConfigs) (*ctc
CaPath: &sc.AgentConfig.Vault.CAPath,
}
}
enabled := attempts > 0
conf.Vault.Retry = &ctconfig.RetryConfig{
Attempts: &attempts,
Enabled: &enabled,
}
conf.Finalize()

View File

@ -454,6 +454,9 @@ func TestServerRun(t *testing.T) {
AgentConfig: &config.Config{
Vault: &config.Vault{
Address: ts.URL,
Retry: &config.Retry{
NumRetries: 3,
},
},
},
LogLevel: hclog.Trace,
@ -466,7 +469,6 @@ func TestServerRun(t *testing.T) {
if ts == nil {
t.Fatal("nil server returned")
}
server.testingLimitRetry = 3
errCh := make(chan error)
go func() {

View File

@ -501,12 +501,6 @@ auto_auth {
secret_id_file_path = "%s"
}
}
sink "file" {
config = {
path = "%s"
}
}
}
cache {
@ -1234,3 +1228,465 @@ func makeTempFile(t *testing.T, name, contents string) string {
f.Close()
return path
}
// handler makes 500 errors happen for reads on /v1/secret.
// Definitely not thread-safe, do not use t.Parallel with this.
type handler struct {
props *vault.HandlerProperties
failCount int
t *testing.T
}
func (h *handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
if req.Method == "GET" && strings.HasPrefix(req.URL.Path, "/v1/secret") {
if h.failCount > 0 {
h.failCount--
h.t.Logf("%s failing GET request on %s, failures left: %d", time.Now(), req.URL.Path, h.failCount)
resp.WriteHeader(500)
return
}
h.t.Logf("passing GET request on %s", req.URL.Path)
}
vaulthttp.Handler(h.props).ServeHTTP(resp, req)
}
// TestAgent_Template_Retry verifies that the template server retries requests
// based on retry configuration.
func TestAgent_Template_Retry(t *testing.T) {
//----------------------------------------------------
// Start the server and agent
//----------------------------------------------------
logger := logging.NewVaultLogger(hclog.Trace)
var h handler
cluster := vault.NewTestCluster(t,
&vault.CoreConfig{
Logger: logger,
CredentialBackends: map[string]logical.Factory{
"approle": credAppRole.Factory,
},
LogicalBackends: map[string]logical.Factory{
"kv": logicalKv.Factory,
},
},
&vault.TestClusterOptions{
NumCores: 1,
HandlerFunc: func(properties *vault.HandlerProperties) http.Handler {
h.props = properties
h.t = t
return &h
},
})
cluster.Start()
defer cluster.Cleanup()
vault.TestWaitActive(t, cluster.Cores[0].Core)
serverClient := cluster.Cores[0].Client
// Unset the environment variable so that agent picks up the right test
// cluster address
defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress))
os.Unsetenv(api.EnvVaultAddress)
methodConf, cleanup := prepAgentApproleKV(t, serverClient)
defer cleanup()
err := serverClient.Sys().TuneMount("secret", api.MountConfigInput{
Options: map[string]string{
"version": "2",
},
})
if err != nil {
t.Fatal(err)
}
_, err = serverClient.Logical().Write("secret/data/otherapp", map[string]interface{}{
"data": map[string]interface{}{
"username": "barstuff",
"password": "zap",
"cert": "something",
},
})
if err != nil {
t.Fatal(err)
}
// make a temp directory to hold renders. Each test will create a temp dir
// inside this one
tmpDirRoot, err := ioutil.TempDir("", "agent-test-renders")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDirRoot)
intRef := func(i int) *int {
return &i
}
// start test cases here
testCases := map[string]struct {
retries *int
expectError bool
}{
"none": {
retries: intRef(-1),
expectError: true,
},
"one": {
retries: intRef(1),
expectError: true,
},
"two": {
retries: intRef(2),
expectError: false,
},
"missing": {
retries: nil,
expectError: false,
},
"default": {
retries: intRef(0),
expectError: false,
},
}
for tcname, tc := range testCases {
t.Run(tcname, func(t *testing.T) {
// We fail the first 6 times. The consul-template code creates
// a Vault client with MaxRetries=2, so for every consul-template
// retry configured, it will in practice make up to 3 requests.
// Thus if consul-template is configured with "one" retry, it will
// fail given our failCount, but if configured with "two" retries,
// they will consume our 6th failure, and on the "third (from its
// perspective) attempt, it will succeed.
h.failCount = 6
// create temp dir for this test run
tmpDir, err := ioutil.TempDir(tmpDirRoot, tcname)
if err != nil {
t.Fatal(err)
}
// make some template files
templatePath := filepath.Join(tmpDir, "render_0.tmpl")
if err := ioutil.WriteFile(templatePath, []byte(templateContents(0)), 0600); err != nil {
t.Fatal(err)
}
templateConfig := fmt.Sprintf(templateConfigString, templatePath, tmpDir, "render_0.json")
var retryConf string
if tc.retries != nil {
retryConf = fmt.Sprintf("retry { num_retries = %d }", *tc.retries)
}
config := fmt.Sprintf(`
%s
vault {
address = "%s"
%s
tls_skip_verify = true
}
%s
`, methodConf, serverClient.Address(), retryConf, templateConfig)
configPath := makeTempFile(t, "config.hcl", config)
defer os.Remove(configPath)
// Start the agent
_, cmd := testAgentCommand(t, logger)
cmd.startedCh = make(chan struct{})
wg := &sync.WaitGroup{}
wg.Add(1)
var code int
go func() {
code = cmd.Run([]string{"-config", configPath})
wg.Done()
}()
select {
case <-cmd.startedCh:
case <-time.After(5 * time.Second):
t.Errorf("timeout")
}
verify := func() error {
t.Helper()
// We need to poll for a bit to give Agent time to render the
// templates. Without this this, the test will attempt to read
// the temp dir before Agent has had time to render and will
// likely fail the test
tick := time.Tick(1 * time.Second)
timeout := time.After(10 * time.Second)
var err error
for {
select {
case <-timeout:
return fmt.Errorf("timed out waiting for templates to render, last error: %v", err)
case <-tick:
}
// Check for files rendered in the directory and break
// early for shutdown if we do have all the files
// rendered
//----------------------------------------------------
// Perform the tests
//----------------------------------------------------
if numFiles := testListFiles(t, tmpDir, ".json"); numFiles != 1 {
err = fmt.Errorf("expected 1 template, got (%d)", numFiles)
continue
}
fileName := filepath.Join(tmpDir, "render_0.json")
var c []byte
c, err = ioutil.ReadFile(fileName)
if err != nil {
continue
}
if string(c) != templateRendered(0) {
err = fmt.Errorf("expected='%s', got='%s'", templateRendered(0), string(c))
continue
}
return nil
}
}
err = verify()
close(cmd.ShutdownCh)
wg.Wait()
switch {
case (code != 0 || err != nil) && tc.expectError:
case code == 0 && err == nil && !tc.expectError:
default:
t.Fatalf("%s expectError=%v error=%v code=%d", tcname, tc.expectError, err, code)
}
})
}
}
// prepAgentApproleKV configures a Vault instance for approle authentication,
// such that the resulting token will have global permissions across /kv
// and /secret mounts. Returns the auto_auth config stanza to setup an Agent
// to connect using approle.
func prepAgentApproleKV(t *testing.T, client *api.Client) (string, func()) {
t.Helper()
policyAutoAuthAppRole := `
path "/kv/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "/secret/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
`
// Add an kv-admin policy
if err := client.Sys().PutPolicy("test-autoauth", policyAutoAuthAppRole); err != nil {
t.Fatal(err)
}
// Enable approle
err := client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
Type: "approle",
})
if err != nil {
t.Fatal(err)
}
_, err = client.Logical().Write("auth/approle/role/test1", map[string]interface{}{
"bind_secret_id": "true",
"token_ttl": "3s",
"token_max_ttl": "10s",
"policies": []string{"test-autoauth"},
})
if err != nil {
t.Fatal(err)
}
resp, err := client.Logical().Write("auth/approle/role/test1/secret-id", nil)
if err != nil {
t.Fatal(err)
}
secretID := resp.Data["secret_id"].(string)
secretIDFile := makeTempFile(t, "secret_id.txt", secretID+"\n")
resp, err = client.Logical().Read("auth/approle/role/test1/role-id")
if err != nil {
t.Fatal(err)
}
roleID := resp.Data["role_id"].(string)
roleIDFile := makeTempFile(t, "role_id.txt", roleID+"\n")
config := fmt.Sprintf(`
auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "%s"
secret_id_file_path = "%s"
remove_secret_id_file_after_reading = false
}
}
}
`, roleIDFile, secretIDFile)
cleanup := func() {
_ = os.Remove(roleIDFile)
_ = os.Remove(secretIDFile)
}
return config, cleanup
}
func TestAgent_Cache_Retry(t *testing.T) {
//----------------------------------------------------
// Start the server and agent
//----------------------------------------------------
logger := logging.NewVaultLogger(hclog.Trace)
var h handler
cluster := vault.NewTestCluster(t,
&vault.CoreConfig{
Logger: logger,
CredentialBackends: map[string]logical.Factory{
"approle": credAppRole.Factory,
},
LogicalBackends: map[string]logical.Factory{
"kv": logicalKv.Factory,
},
},
&vault.TestClusterOptions{
NumCores: 1,
HandlerFunc: func(properties *vault.HandlerProperties) http.Handler {
h.props = properties
h.t = t
return &h
},
})
cluster.Start()
defer cluster.Cleanup()
vault.TestWaitActive(t, cluster.Cores[0].Core)
serverClient := cluster.Cores[0].Client
// Unset the environment variable so that agent picks up the right test
// cluster address
defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress))
os.Unsetenv(api.EnvVaultAddress)
_, err := serverClient.Logical().Write("secret/foo", map[string]interface{}{
"bar": "baz",
})
if err != nil {
t.Fatal(err)
}
intRef := func(i int) *int {
return &i
}
// start test cases here
testCases := map[string]struct {
retries *int
expectError bool
}{
"none": {
retries: intRef(-1),
expectError: true,
},
"one": {
retries: intRef(1),
expectError: true,
},
"two": {
retries: intRef(2),
expectError: false,
},
"missing": {
retries: nil,
expectError: false,
},
"default": {
retries: intRef(0),
expectError: false,
},
}
for tcname, tc := range testCases {
t.Run(tcname, func(t *testing.T) {
h.failCount = 2
cacheConfig := fmt.Sprintf(`
cache {
}
`)
listenAddr := "127.0.0.1:18123"
listenConfig := fmt.Sprintf(`
listener "tcp" {
address = "%s"
tls_disable = true
}
`, listenAddr)
var retryConf string
if tc.retries != nil {
retryConf = fmt.Sprintf("retry { num_retries = %d }", *tc.retries)
}
config := fmt.Sprintf(`
vault {
address = "%s"
%s
tls_skip_verify = true
}
%s
%s
`, serverClient.Address(), retryConf, cacheConfig, listenConfig)
configPath := makeTempFile(t, "config.hcl", config)
defer os.Remove(configPath)
// Start the agent
_, cmd := testAgentCommand(t, logger)
cmd.startedCh = make(chan struct{})
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
cmd.Run([]string{"-config", configPath})
wg.Done()
}()
select {
case <-cmd.startedCh:
case <-time.After(5 * time.Second):
t.Errorf("timeout")
}
client, err := api.NewClient(api.DefaultConfig())
if err != nil {
t.Fatal(err)
}
client.SetToken(serverClient.Token())
client.SetMaxRetries(0)
err = client.SetAddress("http://" + listenAddr)
if err != nil {
t.Fatal(err)
}
secret, err := client.Logical().Read("secret/foo")
switch {
case (err != nil || secret == nil) && tc.expectError:
case (err == nil || secret != nil) && !tc.expectError:
default:
t.Fatalf("%s expectError=%v error=%v secret=%v", tcname, tc.expectError, err, secret)
}
if secret != nil && secret.Data["foo"] != nil {
val := secret.Data["foo"].(map[string]interface{})
if !reflect.DeepEqual(val, map[string]interface{}{"bar": "baz"}) {
t.Fatalf("expected key 'foo' to yield bar=baz, got: %v", val)
}
}
time.Sleep(time.Second)
close(cmd.ShutdownCh)
wg.Wait()
})
}
}