feat: return credential state for all status errors
This commit is contained in:
1 parent
a0cb3b8fee
commit
08d1e99e8b
16 files changed
+1450
-623
No files matched your search
@@ -34,8 +34,9 @@ type CredentialAttributes struct {
|
||||
}
|
||||
|
||||
// GetCredentialsConfig parses values out of a protobuf struct secrets and returns a
|
||||
// CredentialsConfig used for configuring an AWS session. An error is returned if
|
||||
// any unrecognized fields are found in the protobuf struct input.
|
||||
// CredentialsConfig used for configuring an AWS session. An status error is returned
|
||||
// with an InvalidArgument code if any unrecognized fields are found in the protobuf
|
||||
// struct input.
|
||||
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutil.CredentialsConfig, error) {
|
||||
// initialize secrets if it is nil
|
||||
// secrets can be nil because static credentials are optional
|
||||
@@ -116,13 +117,18 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
|
||||
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags))
|
||||
}
|
||||
|
||||
return awsutil.NewCredentialsConfig(opts...)
|
||||
cfg, err := awsutil.NewCredentialsConfig(opts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus(err.Error())
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// GetCredentialAttributes parses values out of a protobuf struct input and returns a
|
||||
// CredentialAttributes used for configuring an AWS session. An error is returned if
|
||||
// any of the following fields are missing from the protobuf struct input or have
|
||||
// invalid value types: region, disableCredentialRotation
|
||||
// CredentialAttributes used for configuring an AWS session. An status error is returned
|
||||
// with an InvalidArgument code if any of the following fields are missing from the protobuf
|
||||
// struct input or have invalid value types: region, disableCredentialRotation
|
||||
func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) {
|
||||
badFields := make(map[string]string)
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@ package credential
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
stderr "errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/values"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -71,7 +72,7 @@ func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption
|
||||
func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedStateOption {
|
||||
return func(s *AwsCredentialPersistedState) error {
|
||||
if s.CredentialsConfig != nil {
|
||||
return errors.New("credentials config already set")
|
||||
return stderr.New("credentials config already set")
|
||||
}
|
||||
|
||||
s.CredentialsConfig = x
|
||||
@@ -83,7 +84,7 @@ func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedS
|
||||
func WithCredsLastRotatedTime(t time.Time) AwsCredentialPersistedStateOption {
|
||||
return func(s *AwsCredentialPersistedState) error {
|
||||
if !s.CredsLastRotatedTime.IsZero() {
|
||||
return errors.New("last rotation time already set")
|
||||
return stderr.New("last rotation time already set")
|
||||
}
|
||||
|
||||
s.CredsLastRotatedTime = t
|
||||
@@ -106,13 +107,15 @@ func NewAwsCredentialPersistedState(opts ...AwsCredentialPersistedStateOption) (
|
||||
|
||||
// ValidateCreds takes the credentials configuration from the persisted state
|
||||
// and runs sts.GetCallerIdentity for the current credentials, which is done
|
||||
// to check that the credentials are valid.
|
||||
// to check that the credentials are valid. This method returns a status error
|
||||
// with PluginError details.
|
||||
func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
return errors.BadRequestStatus("missing credentials config")
|
||||
}
|
||||
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
|
||||
return fmt.Errorf("error validating credentials: %w", err)
|
||||
st, _ := errors.ParseAWSError(err, "validating credentials")
|
||||
return st.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -121,18 +124,19 @@ func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
||||
// then deletes the old access key. If deletion of the old access key is successful, the new access key/secret key are
|
||||
// written into the credentials config and the persisted state. On any error, the old credentials are not overwritten.
|
||||
// This ensures that any generated new secret key never leaves this function in case of an error, even though it will
|
||||
// still result in an extraneous access key existing.
|
||||
// still result in an extraneous access key existing. This method returns a status error with PluginError details.
|
||||
func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
return errors.BadRequestStatus("missing credentials config")
|
||||
}
|
||||
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
|
||||
return errors.New("invalid credential type")
|
||||
return errors.BadRequestStatus("invalid credential type")
|
||||
}
|
||||
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutil.Option{
|
||||
awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
|
||||
}, s.testOpts...)...); err != nil {
|
||||
return fmt.Errorf("error rotating credentials: %w", err)
|
||||
st, _ := errors.ParseAWSError(err, "error rotating credentials")
|
||||
return st.Err()
|
||||
}
|
||||
s.CredsLastRotatedTime = time.Now()
|
||||
return nil
|
||||
@@ -140,13 +144,14 @@ func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
|
||||
|
||||
// ReplaceCreds replaces the access key in the state with a new key.
|
||||
// If the existing key was rotated at any point in time, it is
|
||||
// deleted first, otherwise it's left alone.
|
||||
// deleted first, otherwise it's left alone. This method returns a
|
||||
// status error with PluginError details.
|
||||
func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credentialsConfig *awsutil.CredentialsConfig) error {
|
||||
if credentialsConfig == nil {
|
||||
return errors.New("missing new credentials config")
|
||||
return errors.BadRequestStatus("missing new credentials config")
|
||||
}
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
return errors.BadRequestStatus("missing credentials config")
|
||||
}
|
||||
|
||||
// Delete old/existing credentials. This action is only possible for static credential types.
|
||||
@@ -166,27 +171,29 @@ func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credenti
|
||||
|
||||
// DeleteCreds deletes the credentials in the state. The access key
|
||||
// ID, secret access key, and rotation time fields are zeroed out in
|
||||
// the state just to ensure that they cannot be re-used after.
|
||||
// the state just to ensure that they cannot be re-used after. This
|
||||
// method returns a status error with PluginError details.
|
||||
func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.New("missing credentials config")
|
||||
return errors.BadRequestStatus("missing credentials config")
|
||||
}
|
||||
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
|
||||
return errors.New("invalid credential type")
|
||||
return errors.BadRequestStatus("invalid credential type")
|
||||
}
|
||||
|
||||
if err := s.CredentialsConfig.DeleteAccessKey(ctx, s.CredentialsConfig.AccessKey, s.testOpts...); err != nil {
|
||||
// Determine if the deletion error was due to a missing
|
||||
// resource. If it was, just pass it.
|
||||
var awsErr *iamTypes.NoSuchEntityException
|
||||
if errors.As(err, &awsErr) && awsErr.ErrorCode() == "NoSuchEntity" {
|
||||
if stderr.As(err, &awsErr) && awsErr.ErrorCode() == "NoSuchEntity" {
|
||||
s.CredentialsConfig = nil
|
||||
s.CredsLastRotatedTime = time.Time{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise treat it as an actual error.
|
||||
return err
|
||||
st, _ := errors.ParseAWSError(err, "failed to delete access key")
|
||||
return st.Err()
|
||||
}
|
||||
|
||||
s.CredentialsConfig = nil
|
||||
|
||||
@@ -14,7 +14,10 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
@@ -340,14 +343,16 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
|
||||
func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
expectedErr string
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
expectedStatusCode codes.Code
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing credentials config",
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
expectedErr: "missing credentials config",
|
||||
},
|
||||
{
|
||||
name: "validation error",
|
||||
@@ -365,7 +370,8 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error validating credentials: %s", testGetCallerIdentityErr),
|
||||
expectedStatusCode: codes.Unknown,
|
||||
expectedErr: "aws service unknown: unknown error: validating credentials",
|
||||
},
|
||||
{
|
||||
name: "good",
|
||||
@@ -383,10 +389,12 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
err := tc.in.ValidateCreds(context.Background())
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
require.Error(err)
|
||||
assert.ErrorContains(err, tc.expectedErr)
|
||||
assert.Equal(tc.expectedStatusCode, status.Code(err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -402,11 +410,13 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
exepected *awsutil.CredentialsConfig
|
||||
expectedNonZeroRotatedTime bool
|
||||
expectedErr string
|
||||
expectedStatusCode codes.Code
|
||||
}{
|
||||
{
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing credentials config",
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing credentials config",
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "cannot delete dynamic credential type",
|
||||
@@ -417,7 +427,8 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
},
|
||||
expectedErr: "invalid credential type",
|
||||
expectedErr: "invalid credential type",
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "cannot delete unknown credential type",
|
||||
@@ -428,7 +439,8 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
},
|
||||
expectedErr: "invalid credential type",
|
||||
expectedErr: "invalid credential type",
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "rotation error",
|
||||
@@ -446,7 +458,8 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
|
||||
expectedErr: "aws service unknown: unknown error: error rotating credentials",
|
||||
expectedStatusCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
name: "good",
|
||||
@@ -496,10 +509,12 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
err := tc.in.RotateCreds(context.Background())
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
require.Error(err)
|
||||
assert.ErrorContains(err, tc.expectedErr)
|
||||
assert.Equal(tc.expectedStatusCode, status.Code(err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -521,17 +536,20 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
expected *awsutil.CredentialsConfig
|
||||
expectedDeleteCalled bool
|
||||
expectedErr string
|
||||
expectedStatusCode codes.Code
|
||||
}{
|
||||
{
|
||||
name: "missing new credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing new credentials config",
|
||||
name: "missing new credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing new credentials config",
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
expectedErr: "missing credentials config",
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
expectedErr: "missing credentials config",
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "deletion error",
|
||||
@@ -549,8 +567,9 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
expectedErr: "aws service unknown: unknown error: failed to delete access key",
|
||||
expectedStatusCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
name: "good with delete of old rotated",
|
||||
@@ -604,11 +623,12 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
state.Reset()
|
||||
err := tc.in.ReplaceCreds(context.Background(), tc.credentialConfig)
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
require.Error(err)
|
||||
assert.ErrorContains(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -623,14 +643,16 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
|
||||
func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
expectedErr string
|
||||
name string
|
||||
in *AwsCredentialPersistedState
|
||||
expectedErr string
|
||||
expectedStatusCode codes.Code
|
||||
}{
|
||||
{
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing credentials config",
|
||||
name: "missing credentials config",
|
||||
in: &AwsCredentialPersistedState{},
|
||||
expectedErr: "missing credentials config",
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "cannot delete dynamic credential type",
|
||||
@@ -641,7 +663,8 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
},
|
||||
expectedErr: "invalid credential type",
|
||||
expectedErr: "invalid credential type",
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "cannot delete unknown credential type",
|
||||
@@ -652,7 +675,8 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
},
|
||||
CredsLastRotatedTime: time.Now(),
|
||||
},
|
||||
expectedErr: "invalid credential type",
|
||||
expectedErr: "invalid credential type",
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "deletion error",
|
||||
@@ -669,7 +693,8 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
|
||||
expectedErr: "aws service unknown: unknown error: failed to delete access key",
|
||||
expectedStatusCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
name: "deletion error, but OK because key was just gone",
|
||||
@@ -708,10 +733,11 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
err := tc.in.DeleteCreds(context.Background())
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
require.Error(err)
|
||||
assert.ErrorContains(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+76
-40
@@ -18,6 +18,7 @@ import (
|
||||
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||
"github.com/aws/smithy-go"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -38,6 +39,22 @@ const (
|
||||
// expired. This error can only be returned by a dynamic credential
|
||||
// type. This error can resolve itself by retrying the request.
|
||||
awsErrorExpiredToken = "ExpiredToken"
|
||||
|
||||
// NoSuchBucket is returned when the specified S3 storage bucket does
|
||||
// not exist
|
||||
awsErrorNoSuchBucket = "NoSuchBucket"
|
||||
|
||||
// BadDigest is returned when an S3 PUT OBJECT request's calculated
|
||||
// checksum does not match the checksum which was sent by the client
|
||||
awsErrorBadDigest = "BadDigest"
|
||||
|
||||
// NoSuchKey is returned when attempting to interact with an S3 object
|
||||
// whose key does not exist
|
||||
awsErrorNoSuchKey = "NoSuchKey"
|
||||
|
||||
// The InvalidObjectState error is returned when trying to
|
||||
// access an object that was moved into cold storage.
|
||||
awsErrorInvalidObjectState = "InvalidObjectState"
|
||||
)
|
||||
|
||||
// InvalidArgumentError returns an grpc invalid argument status error.
|
||||
@@ -50,7 +67,7 @@ func InvalidArgumentError(msg string, f map[string]string) error {
|
||||
sort.Strings(fieldMsgs)
|
||||
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
|
||||
}
|
||||
return status.Error(codes.InvalidArgument, msg)
|
||||
return BadRequestStatus(msg)
|
||||
}
|
||||
|
||||
// ParseAWSError converts an aws service error into a RPC status. This method
|
||||
@@ -58,9 +75,9 @@ func InvalidArgumentError(msg string, f map[string]string) error {
|
||||
// and credentials. This method will fallback to parsing the http status code
|
||||
// when it cannot match an aws error code. This method does not handle specific
|
||||
// service type errors such as S3 or EC2.
|
||||
func ParseAWSError(err error, msg string) (st *status.Status) {
|
||||
func ParseAWSError(err error, msg string) (st *status.Status, permission *pb.Permission) {
|
||||
if err == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// find the service name of the aws api
|
||||
@@ -74,20 +91,14 @@ func ParseAWSError(err error, msg string) (st *status.Status) {
|
||||
// when the aws service returns an error that
|
||||
// has failed to be parsed. By default, this
|
||||
// error code is retryable.
|
||||
plgErr := &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_UNKNOWN,
|
||||
Message: err.Error(),
|
||||
Nonretryable: false,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if st == nil {
|
||||
statusMsg := fmt.Sprintf("aws service %s: unknown error: %s", serviceName, msg)
|
||||
st = status.New(codes.Unknown, statusMsg)
|
||||
}
|
||||
st, err = st.WithDetails(plgErr)
|
||||
if err != nil {
|
||||
st = status.New(codes.Internal, err.Error())
|
||||
if permission == nil {
|
||||
permission = &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -96,9 +107,8 @@ func ParseAWSError(err error, msg string) (st *status.Status) {
|
||||
Codes: retry.DefaultThrottleErrorCodes,
|
||||
}
|
||||
if throttleErr.IsErrorThrottle(err).Bool() {
|
||||
plgErr.Code = pb.ERROR_ERROR_THROTTLING
|
||||
statusMsg := fmt.Sprintf("aws service %s: throttling error: %s", serviceName, msg)
|
||||
return status.New(codes.Unavailable, statusMsg)
|
||||
return status.New(codes.Unavailable, statusMsg), nil
|
||||
}
|
||||
// evaluate against aws connection error codes
|
||||
connectionErr := retry.IsErrorRetryables([]retry.IsErrorRetryable{
|
||||
@@ -113,24 +123,40 @@ func ParseAWSError(err error, msg string) (st *status.Status) {
|
||||
},
|
||||
})
|
||||
if connectionErr.IsErrorRetryable(err).Bool() {
|
||||
plgErr.Code = pb.ERROR_ERROR_TIMEOUT
|
||||
statusMsg := fmt.Sprintf("aws service %s: connectivity error: %s", serviceName, msg)
|
||||
return status.New(codes.DeadlineExceeded, statusMsg)
|
||||
return status.New(codes.DeadlineExceeded, statusMsg), nil
|
||||
}
|
||||
// evaluate against aws credentials error codes
|
||||
|
||||
// parse some specific aws error codes that we have special cred states for
|
||||
var apiErr smithy.APIError
|
||||
if errors.As(err, &apiErr) {
|
||||
statusMsg := fmt.Sprintf("aws service %s: invalid credentials error: %s", serviceName, msg)
|
||||
switch apiErr.ErrorCode() {
|
||||
case awsErrorAccessDenied:
|
||||
fallthrough
|
||||
case awsErrorInvalidAccessKeyId:
|
||||
plgErr.Code = pb.ERROR_ERROR_INVALID_CREDENTIAL
|
||||
plgErr.Nonretryable = true
|
||||
return status.New(codes.PermissionDenied, statusMsg)
|
||||
fallthrough
|
||||
case awsErrorExpiredToken:
|
||||
plgErr.Code = pb.ERROR_ERROR_INVALID_CREDENTIAL
|
||||
return status.New(codes.PermissionDenied, statusMsg)
|
||||
statusMsg := fmt.Sprintf("aws service %s: invalid credentials error: %s", serviceName, msg)
|
||||
return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: apiErr.ErrorMessage(),
|
||||
CheckedAt: timestamppb.Now(),
|
||||
}
|
||||
case awsErrorNoSuchBucket:
|
||||
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||
return status.New(codes.NotFound, statusMsg), &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: apiErr.ErrorMessage(),
|
||||
CheckedAt: timestamppb.Now(),
|
||||
}
|
||||
case awsErrorBadDigest:
|
||||
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||
return status.New(codes.Aborted, statusMsg), nil
|
||||
case awsErrorNoSuchKey:
|
||||
fallthrough
|
||||
case awsErrorInvalidObjectState:
|
||||
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||
return status.New(codes.NotFound, statusMsg), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,34 +165,33 @@ func ParseAWSError(err error, msg string) (st *status.Status) {
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(httpErr.Response.Body)
|
||||
if err != nil {
|
||||
return status.New(codes.Internal, err.Error())
|
||||
return status.New(codes.Internal, err.Error()), nil
|
||||
}
|
||||
defer httpErr.Response.Body.Close()
|
||||
plgErr.Message = buf.String()
|
||||
switch httpErr.HTTPStatusCode() {
|
||||
case http.StatusBadRequest:
|
||||
plgErr.Code = pb.ERROR_ERROR_BAD_REQUEST
|
||||
plgErr.Nonretryable = true
|
||||
statusMsg := fmt.Sprintf("aws service %s: bad request error: %s", serviceName, msg)
|
||||
return status.New(codes.InvalidArgument, statusMsg)
|
||||
return status.New(codes.InvalidArgument, statusMsg), nil
|
||||
case http.StatusUnauthorized:
|
||||
plgErr.Code = pb.ERROR_ERROR_INVALID_CREDENTIAL
|
||||
statusMsg := fmt.Sprintf("aws service %s: invalid credentials error: %s", serviceName, msg)
|
||||
return status.New(codes.PermissionDenied, statusMsg)
|
||||
return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: buf.String(),
|
||||
CheckedAt: timestamppb.Now(),
|
||||
}
|
||||
case http.StatusForbidden:
|
||||
plgErr.Code = pb.ERROR_ERROR_INVALID_CREDENTIAL
|
||||
plgErr.Nonretryable = true
|
||||
statusMsg := fmt.Sprintf("aws service %s: invalid credentials error: %s", serviceName, msg)
|
||||
return status.New(codes.PermissionDenied, statusMsg)
|
||||
return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: buf.String(),
|
||||
CheckedAt: timestamppb.Now(),
|
||||
}
|
||||
case http.StatusNotFound:
|
||||
plgErr.Code = pb.ERROR_ERROR_BAD_REQUEST
|
||||
plgErr.Nonretryable = true
|
||||
statusMsg := fmt.Sprintf("aws service %s: resource not found error: %s", serviceName, msg)
|
||||
return status.New(codes.NotFound, statusMsg)
|
||||
return status.New(codes.NotFound, statusMsg), nil
|
||||
case http.StatusTooManyRequests:
|
||||
plgErr.Code = pb.ERROR_ERROR_THROTTLING
|
||||
statusMsg := fmt.Sprintf("aws service %s: throttling error: %s", serviceName, msg)
|
||||
return status.New(codes.Unavailable, statusMsg)
|
||||
return status.New(codes.Unavailable, statusMsg), nil
|
||||
case http.StatusRequestTimeout:
|
||||
fallthrough
|
||||
case http.StatusInternalServerError:
|
||||
@@ -176,11 +201,22 @@ func ParseAWSError(err error, msg string) (st *status.Status) {
|
||||
case http.StatusServiceUnavailable:
|
||||
fallthrough
|
||||
case http.StatusGatewayTimeout:
|
||||
plgErr.Code = pb.ERROR_ERROR_TIMEOUT
|
||||
statusMsg := fmt.Sprintf("aws service %s: connectivity error: %s", serviceName, msg)
|
||||
return status.New(codes.DeadlineExceeded, statusMsg)
|
||||
return status.New(codes.DeadlineExceeded, statusMsg), nil
|
||||
}
|
||||
}
|
||||
|
||||
return st
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// BadRequestStatus returns a status error with an invalid
|
||||
// argument code
|
||||
func BadRequestStatus(format string, args ...any) error {
|
||||
return status.New(codes.InvalidArgument, fmt.Sprintf(format, args...)).Err()
|
||||
}
|
||||
|
||||
// UnknownStatus returns a status error with an internal
|
||||
// error code and a retryable plugin error detail.
|
||||
func UnknownStatus(format string, args ...any) error {
|
||||
return status.New(codes.Internal, fmt.Sprintf(format, args...)).Err()
|
||||
}
|
||||
@@ -7,19 +7,21 @@ import (
|
||||
"net/http"
|
||||
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func Test_ParseAWSError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
err error
|
||||
expectedStatusCode codes.Code
|
||||
expectedStatusMsg string
|
||||
expectedPluginError *pb.PluginError
|
||||
name string
|
||||
err error
|
||||
expectedStatusCode codes.Code
|
||||
expectedStatusMsg string
|
||||
expectedPermission *pb.Permission
|
||||
}{
|
||||
{
|
||||
name: "nil-error",
|
||||
@@ -30,10 +32,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: fmt.Errorf("what can this error be?"),
|
||||
expectedStatusCode: codes.Unknown,
|
||||
expectedStatusMsg: "aws service unknown: unknown error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_UNKNOWN,
|
||||
Message: "what can this error be?",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -41,10 +41,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusBadRequest, "bad request"),
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
expectedStatusMsg: "aws service unknown: bad request error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_BAD_REQUEST,
|
||||
Message: "bad request",
|
||||
Nonretryable: true,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -52,10 +50,10 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusUnauthorized, "unauthorized request"),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_INVALID_CREDENTIAL,
|
||||
Message: "unauthorized request",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "unauthorized request",
|
||||
CheckedAt: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -63,10 +61,10 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusForbidden, "forbidden request"),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_INVALID_CREDENTIAL,
|
||||
Message: "forbidden request",
|
||||
Nonretryable: true,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "forbidden request",
|
||||
CheckedAt: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -74,10 +72,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusNotFound, "not found request"),
|
||||
expectedStatusCode: codes.NotFound,
|
||||
expectedStatusMsg: "aws service unknown: resource not found error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_BAD_REQUEST,
|
||||
Message: "not found request",
|
||||
Nonretryable: true,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -85,10 +81,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusTooManyRequests, "too many requests"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "too many requests",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -96,10 +90,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusRequestTimeout, "request timeout"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_TIMEOUT,
|
||||
Message: "request timeout",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -107,10 +99,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusInternalServerError, "internal server error"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_TIMEOUT,
|
||||
Message: "https response error StatusCode: 500",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -118,10 +108,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusBadGateway, "bad gateway"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_TIMEOUT,
|
||||
Message: "https response error StatusCode: 502",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -129,10 +117,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusServiceUnavailable, "service unavailable"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_TIMEOUT,
|
||||
Message: "https response error StatusCode: 503",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -140,10 +126,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsHttpResponseError(http.StatusGatewayTimeout, "gateway timeout"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_TIMEOUT,
|
||||
Message: "https response error StatusCode: 504",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -151,10 +135,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("Throttling", "throttling error"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "throttling error",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -162,10 +144,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("ThrottlingException", "throttling exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "throttling exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -173,10 +153,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("ThrottledException", "throttled exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "throttled exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -184,10 +162,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("RequestThrottledException", "request throttled exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "request throttled exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -195,10 +171,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("TooManyRequestsException", "too many requests exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "too many requests exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -206,10 +180,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("ProvisionedThroughputExceededException", "provisioned throughput exceeded exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "provisioned throughput exceeded exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -217,10 +189,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("TransactionInProgressException", "transaction in progress exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "transaction in progress exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -228,10 +198,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("RequestLimitExceeded", "request limit exceeded"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "request limit exceeded",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -239,10 +207,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("BandwidthLimitExceeded", "bandwidth limit exceeded"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "bandwidth limit exceeded",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -250,10 +216,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("LimitExceededException", "limit exceeded exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "limit exceeded exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -261,10 +225,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("RequestThrottled", "request throttled"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "request throttled",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -272,10 +234,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("SlowDown", "slow down"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "slow down",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -283,10 +243,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("PriorRequestNotComplete", "prior request not complete"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "prior request not complete",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -294,10 +252,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("EC2ThrottledException", "ec2 throttled exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_THROTTLING,
|
||||
Message: "ec2 throttled exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -305,10 +261,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("RequestTimeout", "request timeout"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_TIMEOUT,
|
||||
Message: "request timeout",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -316,10 +270,8 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError("RequestTimeoutException", "request timeout exception"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_TIMEOUT,
|
||||
Message: "request timeout exception",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -327,10 +279,10 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError(awsErrorAccessDenied, "not authorized to perform action"),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_INVALID_CREDENTIAL,
|
||||
Message: "not authorized to perform action",
|
||||
Nonretryable: true,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "not authorized to perform action",
|
||||
CheckedAt: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -338,10 +290,10 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_INVALID_CREDENTIAL,
|
||||
Message: "The AWS Access Key Id you provided does not exist in our records.",
|
||||
Nonretryable: true,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "The AWS Access Key Id you provided does not exist in our records.",
|
||||
CheckedAt: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -349,18 +301,18 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
err: TestAwsError(awsErrorExpiredToken, "The security token included in the request is expired."),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||
expectedPluginError: &pb.PluginError{
|
||||
Code: pb.ERROR_ERROR_INVALID_CREDENTIAL,
|
||||
Message: "The security token included in the request is expired.",
|
||||
Nonretryable: false,
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "The security token included in the request is expired.",
|
||||
CheckedAt: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
|
||||
actualStatus := ParseAWSError(tt.err, "test")
|
||||
actualStatus, permission := ParseAWSError(tt.err, "test")
|
||||
if tt.expectedStatusCode == codes.OK {
|
||||
require.Nil(actualStatus)
|
||||
return
|
||||
@@ -368,15 +320,48 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
|
||||
require.Equal(tt.expectedStatusCode, actualStatus.Code())
|
||||
require.Equal(tt.expectedStatusMsg, actualStatus.Message())
|
||||
require.Len(actualStatus.Details(), 1)
|
||||
for _, detail := range actualStatus.Details() {
|
||||
switch errDetail := detail.(type) {
|
||||
case *pb.PluginError:
|
||||
require.Equal(tt.expectedPluginError.Code, errDetail.Code)
|
||||
require.Contains(errDetail.Message, tt.expectedPluginError.Message)
|
||||
require.Equal(tt.expectedPluginError.Nonretryable, errDetail.Nonretryable)
|
||||
}
|
||||
}
|
||||
require.Len(actualStatus.Details(), 0)
|
||||
CheckSimilarPermission(assert, tt.expectedPermission, permission, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// func Test_BadRequestStatus(t *testing.T) {
|
||||
// require, assert := require.New(t), assert.New(t)
|
||||
// err := BadRequestStatus("test: %s", "hello world")
|
||||
// assert.ErrorContains(err, "test: hello world")
|
||||
// assert.Equal(status.Code(err), codes.InvalidArgument)
|
||||
// st, ok := status.FromError(err)
|
||||
// require.True(ok)
|
||||
// var plgErr *pb.PluginError
|
||||
// for _, detail := range st.Details() {
|
||||
// if errDetail, ok := detail.(*pb.PluginError); ok {
|
||||
// plgErr = errDetail
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// require.NotNil(plgErr)
|
||||
// assert.Equal(pb.ERROR_ERROR_BAD_REQUEST, plgErr.Code)
|
||||
// assert.Equal("bad request", plgErr.Message)
|
||||
// assert.True(plgErr.Nonretryable)
|
||||
// }
|
||||
|
||||
// func Test_UnknownStatus(t *testing.T) {
|
||||
// require, assert := require.New(t), assert.New(t)
|
||||
// err := UnknownStatus("test: %s", "hello world")
|
||||
// assert.ErrorContains(err, "test: hello world")
|
||||
// assert.Equal(status.Code(err), codes.Internal)
|
||||
// st, ok := status.FromError(err)
|
||||
// require.True(ok)
|
||||
// var plgErr *pb.PluginError
|
||||
// for _, detail := range st.Details() {
|
||||
// if errDetail, ok := detail.(*pb.PluginError); ok {
|
||||
// plgErr = errDetail
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// require.NotNil(plgErr)
|
||||
// assert.Equal(pb.ERROR_ERROR_UNKNOWN, plgErr.Code)
|
||||
// assert.Equal("unknown internal error", plgErr.Message)
|
||||
// assert.False(plgErr.Nonretryable)
|
||||
// }
|
||||
@@ -8,6 +8,9 @@ import (
|
||||
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||
"github.com/aws/smithy-go"
|
||||
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// TestAwsHttpResponseError returns an aws http response error
|
||||
@@ -32,3 +35,23 @@ func TestAwsError(code, msg string) error {
|
||||
Fault: smithy.FaultServer,
|
||||
}
|
||||
}
|
||||
|
||||
func CheckSimilarPermission(assert *assert.Assertions, expected, actual *pb.Permission, substituteNilWithDefaultOk bool) {
|
||||
if expected == nil && substituteNilWithDefaultOk {
|
||||
expected = &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_OK,
|
||||
CheckedAt: timestamppb.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
if expected != nil {
|
||||
assert.NotNil(actual)
|
||||
if actual != nil {
|
||||
assert.Equal(expected.State, actual.State, "StateType mismatch")
|
||||
assert.Equal(expected.ErrorDetails, actual.ErrorDetails)
|
||||
assert.NotNil(actual.CheckedAt)
|
||||
}
|
||||
} else {
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user