Support auth method with snapshot agent [ENT] (#15020)
Port of hashicorp/consul-enterprise#3303
This commit is contained in:
parent
9f41cc4a25
commit
22b6c39092
|
@ -35,3 +35,15 @@ func (h *FlagMapValue) Set(value string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge will overlay this value if it has been set.
|
||||||
|
func (h *FlagMapValue) Merge(onto map[string]string) {
|
||||||
|
if h == nil || onto == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range *h {
|
||||||
|
if _, ok := onto[k]; !ok {
|
||||||
|
onto[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package flags
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFlagMapValueSet(t *testing.T) {
|
func TestFlagMapValueSet(t *testing.T) {
|
||||||
|
@ -78,3 +80,75 @@ func TestFlagMapValueSet(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFlagMapValueMerge(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
src FlagMapValue
|
||||||
|
dst map[string]string
|
||||||
|
exp map[string]string
|
||||||
|
}{
|
||||||
|
"empty source and destination": {},
|
||||||
|
"empty source": {
|
||||||
|
dst: map[string]string{
|
||||||
|
"key": "val",
|
||||||
|
},
|
||||||
|
exp: map[string]string{
|
||||||
|
"key": "val",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty destination": {
|
||||||
|
src: map[string]string{
|
||||||
|
"key": "val",
|
||||||
|
},
|
||||||
|
dst: make(map[string]string),
|
||||||
|
exp: map[string]string{
|
||||||
|
"key": "val",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"non-overlapping keys": {
|
||||||
|
src: map[string]string{
|
||||||
|
"key1": "val1",
|
||||||
|
},
|
||||||
|
dst: map[string]string{
|
||||||
|
"key2": "val2",
|
||||||
|
},
|
||||||
|
exp: map[string]string{
|
||||||
|
"key1": "val1",
|
||||||
|
"key2": "val2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"overlapping keys": {
|
||||||
|
src: map[string]string{
|
||||||
|
"key1": "val1",
|
||||||
|
},
|
||||||
|
dst: map[string]string{
|
||||||
|
"key1": "val2",
|
||||||
|
},
|
||||||
|
exp: map[string]string{
|
||||||
|
"key1": "val2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple keys": {
|
||||||
|
src: map[string]string{
|
||||||
|
"key1": "val1",
|
||||||
|
"key2": "val2",
|
||||||
|
},
|
||||||
|
dst: map[string]string{
|
||||||
|
"key1": "val2",
|
||||||
|
"key3": "val3",
|
||||||
|
},
|
||||||
|
exp: map[string]string{
|
||||||
|
"key1": "val2",
|
||||||
|
"key2": "val2",
|
||||||
|
"key3": "val3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, c := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
c.src.Merge(c.dst)
|
||||||
|
require.Equal(t, c.exp, c.dst)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package retry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -30,7 +31,7 @@ func NewJitter(percent int64) Jitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Waiter records the number of failures and performs exponential backoff when
|
// Waiter records the number of failures and performs exponential backoff when
|
||||||
// when there are consecutive failures.
|
// there are consecutive failures.
|
||||||
type Waiter struct {
|
type Waiter struct {
|
||||||
// MinFailures before exponential backoff starts. Any failures before
|
// MinFailures before exponential backoff starts. Any failures before
|
||||||
// MinFailures is reached will wait MinWait time.
|
// MinFailures is reached will wait MinWait time.
|
||||||
|
@ -117,3 +118,23 @@ func (w *Waiter) Wait(ctx context.Context) error {
|
||||||
func (w *Waiter) NextWait() time.Duration {
|
func (w *Waiter) NextWait() time.Duration {
|
||||||
return w.delay()
|
return w.delay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetryLoop retries an operation until either operation completes without error
|
||||||
|
// or Waiter's context is canceled.
|
||||||
|
func (w *Waiter) RetryLoop(ctx context.Context, operation func() error) error {
|
||||||
|
var lastError error
|
||||||
|
for {
|
||||||
|
if err := w.Wait(ctx); err != nil {
|
||||||
|
// The error will only be non-nil if the context is canceled.
|
||||||
|
return fmt.Errorf("could not retry operation: %w", lastError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := operation(); err == nil {
|
||||||
|
// Reset the failure count seen by the waiter if there was no error.
|
||||||
|
w.Reset()
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
lastError = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package retry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -157,6 +158,38 @@ func TestWaiter_Wait(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWaiter_RetryLoop(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
// Change the default factor so that we retry faster.
|
||||||
|
w := &Waiter{Factor: 1 * time.Millisecond}
|
||||||
|
|
||||||
|
t.Run("exits if operation is successful after a few reties", func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
t.Cleanup(cancel)
|
||||||
|
numRetries := 0
|
||||||
|
err := w.RetryLoop(ctx, func() error {
|
||||||
|
if numRetries < 2 {
|
||||||
|
numRetries++
|
||||||
|
return fmt.Errorf("operation not successful")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("errors if operation is never successful", func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
t.Cleanup(cancel)
|
||||||
|
err := w.RetryLoop(ctx, func() error {
|
||||||
|
return fmt.Errorf("operation not successful")
|
||||||
|
})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.EqualError(t, err, "could not retry operation: operation not successful")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func runWait(ctx context.Context, w *Waiter) (time.Duration, error) {
|
func runWait(ctx context.Context, w *Waiter) (time.Duration, error) {
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
err := w.Wait(ctx)
|
err := w.Wait(ctx)
|
||||||
|
|
|
@ -143,6 +143,12 @@ Usage: `consul snapshot agent [options]`
|
||||||
"key_file": "",
|
"key_file": "",
|
||||||
"license_path": "",
|
"license_path": "",
|
||||||
"tls_server_name": "",
|
"tls_server_name": "",
|
||||||
|
"login": {
|
||||||
|
"auth_method": "",
|
||||||
|
"bearer_token": "",
|
||||||
|
"bearer_token_file": "",
|
||||||
|
"meta": {},
|
||||||
|
},
|
||||||
"log": {
|
"log": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"enable_syslog": false,
|
"enable_syslog": false,
|
||||||
|
@ -238,6 +244,16 @@ if desired.
|
||||||
- `-syslog-facility` - Sets the facility to use for forwarding logs to syslog.
|
- `-syslog-facility` - Sets the facility to use for forwarding logs to syslog.
|
||||||
Defaults to "LOCAL0".
|
Defaults to "LOCAL0".
|
||||||
|
|
||||||
|
- `login-auth-method` - Auth method name to use to log into Consul. If provided, the token obtained with this auth method
|
||||||
|
will be used instead of a static token if it is provided. Currently, only `kubernetes` auth method type is supported.
|
||||||
|
|
||||||
|
- `login-bearer-token` - Bearer token to use to log into Consul. Used only if `-login-auth-method` is set.
|
||||||
|
|
||||||
|
- `login-bearer-token-file` - A file container bearer token to use for logging into Consul.
|
||||||
|
`-login-bearer-token` is ignored if this flag is provided.
|
||||||
|
|
||||||
|
- `login-meta` - Metadata to set on the token, formatted as key=value. This flag may be provided multiple times.
|
||||||
|
|
||||||
#### Local Storage Options
|
#### Local Storage Options
|
||||||
|
|
||||||
- `-local-path` - Location to store snapshots locally. The default behavior
|
- `-local-path` - Location to store snapshots locally. The default behavior
|
||||||
|
|
Loading…
Reference in New Issue