Skip to content

Commit

Permalink
Support auth method with snapshot agent [ENT] (#15020)
Browse files Browse the repository at this point in the history
Port of hashicorp/consul-enterprise#3303
  • Loading branch information
ishustava committed Oct 17, 2022
1 parent fe2d41d commit 5cd0ccf
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 4 deletions.
12 changes: 12 additions & 0 deletions command/flags/flag_map_value.go
Expand Up @@ -35,3 +35,15 @@ func (h *FlagMapValue) Set(value string) error {

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
}
}
}
74 changes: 74 additions & 0 deletions command/flags/flag_map_value_test.go
Expand Up @@ -3,6 +3,8 @@ package flags
import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
)

func TestFlagMapValueSet(t *testing.T) {
Expand Down Expand Up @@ -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)
})
}
}
23 changes: 22 additions & 1 deletion lib/retry/retry.go
Expand Up @@ -2,6 +2,7 @@ package retry

import (
"context"
"fmt"
"math/rand"
"time"
)
Expand Down Expand Up @@ -30,7 +31,7 @@ func NewJitter(percent int64) Jitter {
}

// Waiter records the number of failures and performs exponential backoff when
// when there are consecutive failures.
// there are consecutive failures.
type Waiter struct {
// MinFailures before exponential backoff starts. Any failures before
// MinFailures is reached will wait MinWait time.
Expand Down Expand Up @@ -117,3 +118,23 @@ func (w *Waiter) Wait(ctx context.Context) error {
func (w *Waiter) NextWait() time.Duration {
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
}
}
}
33 changes: 33 additions & 0 deletions lib/retry/retry_test.go
Expand Up @@ -2,6 +2,7 @@ package retry

import (
"context"
"fmt"
"math"
"testing"
"time"
Expand Down Expand Up @@ -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) {
before := time.Now()
err := w.Wait(ctx)
Expand Down
22 changes: 19 additions & 3 deletions website/content/commands/snapshot/agent.mdx
Expand Up @@ -143,6 +143,12 @@ Usage: `consul snapshot agent [options]`
"key_file": "",
"license_path": "",
"tls_server_name": "",
"login": {
"auth_method": "",
"bearer_token": "",
"bearer_token_file": "",
"meta": {},
},
"log": {
"level": "INFO",
"enable_syslog": false,
Expand Down Expand Up @@ -238,6 +244,16 @@ if desired.
- `-syslog-facility` - Sets the facility to use for forwarding logs to syslog.
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-path` - Location to store snapshots locally. The default behavior
Expand Down Expand Up @@ -277,7 +293,7 @@ Note that despite the AWS references, any S3-compatible endpoint can be specifie
Use this if you want to rely on [S3's versioning capabilities](http://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html) instead of the agent handling it for you.

- `-aws-s3-force-path-style` - Enables the use of legacy path-based addressing instead of virtual addressing. This flag is required by minio
and other 3rd party S3 compatible object storage platforms where DNS or TLS requirements for virtual addressing are prohibitive.
and other 3rd party S3 compatible object storage platforms where DNS or TLS requirements for virtual addressing are prohibitive.
For more information, refer to the AWS documentation on [Methods for accessing a bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html)

- `-aws-s3-enable-kms` - Enables using [Amazon KMS](https://aws.amazon.com/kms/) for encrypting snapshots.
Expand Down Expand Up @@ -394,6 +410,6 @@ then the order of precedence is as follows:
2. `CONSUL_LICENSE_PATH` variable
3. `license_path` configuration.

The ability to load licenses from the configuration or environment was added in v1.10.0,
v1.9.7 and v1.8.13. See the [licensing documentation](/docs/enterprise/license/overview) for
The ability to load licenses from the configuration or environment was added in v1.10.0,
v1.9.7 and v1.8.13. See the [licensing documentation](/docs/enterprise/license/overview) for
more information about Consul Enterprise license management.

0 comments on commit 5cd0ccf

Please sign in to comment.