Skip to content

Commit

Permalink
feat: add http status 429 to default retry policy (#99)
Browse files Browse the repository at this point in the history
* feat: add http status 429 to default retry policy

* feat: use type alias for Secret and Setting, remove stub package
  • Loading branch information
KarlGW authored May 7, 2024
1 parent 8b3c744 commit df014dc
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 356 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,20 @@ For more information about option **2** and **3**, see [Credentials](#credential

#### Built-in credentials

The built-in credential handling for managed identities have been tested on:

- Azure Functions
- Azure Container Apps
- Azure Container Instances

In addition to this it should work on:

- Azure Virtual Machines (since it makes use of the IMDS endpoint like Azure Container Instances)
- Azure App Services (since it makes us of the same endpoint as Azure Functions)

For more advanced scenarios like Azure Stack or Service Fabric see the section about using [`authopts`](./authopts/)
together with [`azidentity`](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity).

##### Authentication with environment variables

For all authentication scenarios the following environment variables are used:
Expand Down
50 changes: 36 additions & 14 deletions azcfg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ func TestParse(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
secretClient := mockSecretClient{err: test.wantErr}
settingClient := mockSettingClient{err: test.wantErr}
secretClient := newMockSecretClient(responseSecrets, test.wantErr)
settingClient := newMockSettingClient(responseSettings, test.wantErr)

gotErr := parse(context.Background(), &test.input, parseOptions{secretClient: secretClient, settingClient: settingClient})
if diff := cmp.Diff(test.want, test.input, cmp.AllowUnexported(Struct{})); diff != "" {
Expand All @@ -217,17 +217,17 @@ func TestParseRequired(t *testing.T) {
input: StructWithRequired{},
wantErr: &RequiredFieldsError{
errors: []error{
requiredSecretsError{message: requiredErrorMessage(map[string]secret.Secret{"empty": {}, "empty-float64": {}}, []string{"empty", "empty-float64"}, "secret")},
requiredSettingsError{message: requiredErrorMessage(map[string]setting.Setting{"empty-setting": {}}, []string{"empty-setting"}, "setting")},
requiredSecretsError{message: requiredErrorMessage(map[string]Secret{"empty": {}, "empty-float64": {}}, []string{"empty", "empty-float64"}, "secret")},
requiredSettingsError{message: requiredErrorMessage(map[string]Setting{"empty-setting": {}}, []string{"empty-setting"}, "setting")},
},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
secretClient := mockSecretClient{}
settingClient := mockSettingClient{}
secretClient := mockSecretClient{secrets: responseSecrets}
settingClient := mockSettingClient{settings: responseSettings}

gotErr := parse(context.Background(), &test.input, parseOptions{secretClient: secretClient, settingClient: settingClient})
if test.wantErr != nil && gotErr == nil {
Expand Down Expand Up @@ -375,18 +375,29 @@ type NestedStructWithRequired struct {
}

type mockSecretClient struct {
err error
secrets map[string]Secret
err error
}

func (c mockSecretClient) GetSecrets(ctx context.Context, names []string, options ...secret.Option) (map[string]secret.Secret, error) {
func newMockSecretClient(secrets map[string]Secret, err error) mockSecretClient {
if secrets == nil {
secrets = make(map[string]Secret)
}
return mockSecretClient{
secrets: secrets,
err: err,
}
}

func (c mockSecretClient) GetSecrets(ctx context.Context, names []string, options ...secret.Option) (map[string]Secret, error) {
if c.err != nil {
return nil, errors.New("could not get secrets")
}
return responseSecrets, nil
return c.secrets, nil
}

var (
responseSecrets = map[string]secret.Secret{
responseSecrets = map[string]Secret{
"string": {Value: "new string"},
"string-ptr": {Value: "new string ptr"},
"empty": {Value: ""},
Expand All @@ -413,23 +424,34 @@ var (
)

type mockSettingClient struct {
err error
settings map[string]Setting
err error
}

func newMockSettingClient(settings map[string]Setting, err error) mockSettingClient {
if settings == nil {
settings = make(map[string]Setting)
}
return mockSettingClient{
settings: settings,
err: err,
}
}

func (c mockSettingClient) GetSettings(ctx context.Context, keys []string, options ...setting.Option) (map[string]setting.Setting, error) {
func (c mockSettingClient) GetSettings(ctx context.Context, keys []string, options ...setting.Option) (map[string]Setting, error) {
time.Sleep(time.Millisecond * 10)
if c.err != nil {
return nil, errors.New("could not get settings")
}
return responseSettings, nil
return c.settings, nil
}

func toPtr[V any](v V) *V {
return &v
}

var (
responseSettings = map[string]setting.Setting{
responseSettings = map[string]Setting{
"string-setting": {Value: "new string setting"},
"string-setting-ptr": {Value: "new string setting ptr"},
"bool-setting": {Value: "true"},
Expand Down
38 changes: 18 additions & 20 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,36 @@ package azcfg
import (
"testing"

"github.com/KarlGW/azcfg/internal/secret"
"github.com/KarlGW/azcfg/internal/setting"
"github.com/google/go-cmp/cmp"
)

func TestRequiredErrorMessage_Secret(t *testing.T) {
var tests = []struct {
name string
input struct {
secrets map[string]secret.Secret
secrets map[string]Secret
required []string
}
want string
}{
{
name: "no required",
input: struct {
secrets map[string]secret.Secret
secrets map[string]Secret
required []string
}{
secrets: map[string]secret.Secret{},
secrets: map[string]Secret{},
required: []string{},
},
want: "",
},
{
name: "1 required",
input: struct {
secrets map[string]secret.Secret
secrets map[string]Secret
required []string
}{
secrets: map[string]secret.Secret{
secrets: map[string]Secret{
"secret1": {Value: ""},
},
required: []string{
Expand All @@ -46,10 +44,10 @@ func TestRequiredErrorMessage_Secret(t *testing.T) {
{
name: "2 required",
input: struct {
secrets map[string]secret.Secret
secrets map[string]Secret
required []string
}{
secrets: map[string]secret.Secret{
secrets: map[string]Secret{
"secret1": {Value: ""},
"secret2": {Value: ""},
},
Expand All @@ -63,10 +61,10 @@ func TestRequiredErrorMessage_Secret(t *testing.T) {
{
name: "3 required",
input: struct {
secrets map[string]secret.Secret
secrets map[string]Secret
required []string
}{
secrets: map[string]secret.Secret{
secrets: map[string]Secret{
"secret1": {Value: ""},
"secret2": {Value: ""},
"secret3": {Value: ""},
Expand Down Expand Up @@ -96,29 +94,29 @@ func TestRequiredErrorMessage_Setting(t *testing.T) {
var tests = []struct {
name string
input struct {
settings map[string]setting.Setting
settings map[string]Setting
required []string
}
want string
}{
{
name: "no required",
input: struct {
settings map[string]setting.Setting
settings map[string]Setting
required []string
}{
settings: map[string]setting.Setting{},
settings: map[string]Setting{},
required: []string{},
},
want: "",
},
{
name: "1 required",
input: struct {
settings map[string]setting.Setting
settings map[string]Setting
required []string
}{
settings: map[string]setting.Setting{
settings: map[string]Setting{
"setting1": {Value: ""},
},
required: []string{
Expand All @@ -130,10 +128,10 @@ func TestRequiredErrorMessage_Setting(t *testing.T) {
{
name: "2 required",
input: struct {
settings map[string]setting.Setting
settings map[string]Setting
required []string
}{
settings: map[string]setting.Setting{
settings: map[string]Setting{
"setting1": {Value: ""},
"setting2": {Value: ""},
},
Expand All @@ -147,10 +145,10 @@ func TestRequiredErrorMessage_Setting(t *testing.T) {
{
name: "3 required",
input: struct {
settings map[string]setting.Setting
settings map[string]Setting
required []string
}{
settings: map[string]setting.Setting{
settings: map[string]Setting{
"setting1": {Value: ""},
"setting2": {Value: ""},
"setting3": {Value: ""},
Expand Down
6 changes: 5 additions & 1 deletion internal/httpr/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ func defaultRetry(r *http.Response, err error) bool {
return true
}
switch r.StatusCode {
case 0, http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
case 0:
return true
case http.StatusTooManyRequests:
return true
case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
return true
}
return false
Expand Down
13 changes: 8 additions & 5 deletions internal/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@ type AccessKey struct {
Secret string
}

// Secret represents a secret as returned from the Key Vault REST API.
type Secret = secret.Secret

// secretClient is the interface that wraps around method Get, Vault and
// SetVault.
type secretClient interface {
Get(ctx context.Context, name string, options ...secret.Option) (secret.Secret, error)
Get(ctx context.Context, name string, options ...secret.Option) (Secret, error)
Vault() string
SetVault(vault string)
}
Expand Down Expand Up @@ -266,13 +269,13 @@ func (c *Client) Get(ctx context.Context, key string, options ...Option) (Settin
}

// getSecret gets a secret from the provided URI.
func (c *Client) getSecret(ctx context.Context, uri string) (secret.Secret, error) {
func (c *Client) getSecret(ctx context.Context, uri string) (Secret, error) {
c.mu.Lock()
defer c.mu.Unlock()

v, s, err := vaultAndSecret(uri)
if err != nil {
return secret.Secret{}, err
return Secret{}, err
}
if c.sc == nil {
c.sc, err = newSecretClient(v, c.cred,
Expand All @@ -281,7 +284,7 @@ func (c *Client) getSecret(ctx context.Context, uri string) (secret.Secret, erro
secret.WithRetryPolicy(c.retryPolicy),
)
if err != nil {
return secret.Secret{}, err
return Secret{}, err
}
} else {
if c.sc.Vault() != v {
Expand All @@ -290,7 +293,7 @@ func (c *Client) getSecret(ctx context.Context, uri string) (secret.Secret, erro
}
sec, err := c.sc.Get(ctx, s)
if err != nil {
return secret.Secret{}, err
return Secret{}, err
}
return sec, nil
}
Expand Down
Loading

0 comments on commit df014dc

Please sign in to comment.