feat(cli) Update error handling for checkHosts, Target EC2 client logic (#91)
* feat(cli) Update error handling for checkHosts, Target EC2 client logic * feat(cli) Add UnauthorizedOperation handling to errors.go
This commit is contained in:
1 parent
24119fc1a1
commit
ca7b5ff389
4 files changed
+211
-20
No files matched your search
@@ -29,6 +29,11 @@ const (
|
|||||||
// the owner. This error cannot be resolved by retrying the request.
|
// the owner. This error cannot be resolved by retrying the request.
|
||||||
awsErrorAccessDenied = "AccessDenied"
|
awsErrorAccessDenied = "AccessDenied"
|
||||||
|
|
||||||
|
// awsErrorUnauthorizedOperation is returned by EC2 when the caller is not
|
||||||
|
// authorized to perform the operation. This includes DryRun permission
|
||||||
|
// checks that fail (the counterpart to DryRunOperation success).
|
||||||
|
awsErrorUnauthorizedOperation = "UnauthorizedOperation"
|
||||||
|
|
||||||
// awsErrorInvalidAccessKeyId is returned when the credential does
|
// awsErrorInvalidAccessKeyId is returned when the credential does
|
||||||
// not exist. This error will persist until the credentials attached
|
// not exist. This error will persist until the credentials attached
|
||||||
// to the storage bucket is updated. This error can occur when the
|
// to the storage bucket is updated. This error can occur when the
|
||||||
@@ -66,8 +71,26 @@ const (
|
|||||||
|
|
||||||
// RequestTimeoutException is returned when an http request takes longer than allowed
|
// RequestTimeoutException is returned when an http request takes longer than allowed
|
||||||
awsErrorRequestTimeoutException = "RequestTimeoutException"
|
awsErrorRequestTimeoutException = "RequestTimeoutException"
|
||||||
|
|
||||||
|
// dryRunOperationErrorCode is returned by AWS when a DryRun request would have
|
||||||
|
// succeeded. It is a success signal disguised as an API error (typically HTTP 412).
|
||||||
|
dryRunOperationErrorCode = "DryRunOperation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsDryRunSuccess reports whether err is the AWS DryRunOperation success signal.
|
||||||
|
// A true result means the dry-run validated credentials, permissions, and parameters;
|
||||||
|
// it must not be treated as a failure.
|
||||||
|
func IsDryRunSuccess(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var apiErr smithy.APIError
|
||||||
|
if !errors.As(err, &apiErr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return apiErr.ErrorCode() == dryRunOperationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
// InvalidArgumentError returns an grpc invalid argument status error.
|
// InvalidArgumentError returns an grpc invalid argument status error.
|
||||||
func InvalidArgumentError(msg string, f map[string]string) error {
|
func InvalidArgumentError(msg string, f map[string]string) error {
|
||||||
var fieldMsgs []string
|
var fieldMsgs []string
|
||||||
@@ -172,6 +195,8 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
|
|||||||
switch apiErr.ErrorCode() {
|
switch apiErr.ErrorCode() {
|
||||||
case awsErrorAccessDenied:
|
case awsErrorAccessDenied:
|
||||||
fallthrough
|
fallthrough
|
||||||
|
case awsErrorUnauthorizedOperation:
|
||||||
|
fallthrough
|
||||||
case awsErrorInvalidAccessKeyId:
|
case awsErrorInvalidAccessKeyId:
|
||||||
fallthrough
|
fallthrough
|
||||||
case awsErrorExpiredToken:
|
case awsErrorExpiredToken:
|
||||||
|
|||||||
@@ -300,6 +300,17 @@ func Test_ParseAWSError(t *testing.T) {
|
|||||||
CheckedAt: timestamppb.Now(),
|
CheckedAt: timestamppb.Now(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-unauthorized-operation",
|
||||||
|
err: TestAwsError(awsErrorUnauthorizedOperation, "You are not authorized to perform this operation."),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "You are not authorized to perform this operation.",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "aws-request-invalid-access-key-id",
|
name: "aws-request-invalid-access-key-id",
|
||||||
err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."),
|
err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."),
|
||||||
@@ -390,6 +401,46 @@ func Test_ParseAWSError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_IsDryRunSuccess(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "plain error",
|
||||||
|
err: fmt.Errorf("not an aws error"),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "other api error",
|
||||||
|
err: TestAwsError(awsErrorAccessDenied, "not authorized"),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dry run operation",
|
||||||
|
err: TestAwsError(dryRunOperationErrorCode, "Request would have succeeded, but DryRun flag is set"),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped dry run operation",
|
||||||
|
err: fmt.Errorf("describe instances: %w", TestAwsError(dryRunOperationErrorCode, "Request would have succeeded, but DryRun flag is set")),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, IsDryRunSuccess(tt.err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_BadRequestStatusf(t *testing.T) {
|
func Test_BadRequestStatusf(t *testing.T) {
|
||||||
require, assert := require.New(t), assert.New(t)
|
require, assert := require.New(t), assert.New(t)
|
||||||
err := BadRequestStatusf("test: %s", "hello world")
|
err := BadRequestStatusf("test: %s", "hello world")
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package host
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
goerrors "errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||||
stsTypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
|
stsTypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
|
||||||
"github.com/aws/smithy-go"
|
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
@@ -44,8 +42,6 @@ type hostSetQuery struct {
|
|||||||
OutputHosts []*pb.ListHostsResponseHost
|
OutputHosts []*pb.ListHostsResponseHost
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSessionName = "boundary-default"
|
|
||||||
const dryRunOperationErrorCode = "DryRunOperation"
|
|
||||||
const errBuildDescribeInstancesInput = "error building DescribeInstances input: %s"
|
const errBuildDescribeInstancesInput = "error building DescribeInstances input: %s"
|
||||||
const errDryRunFailed = "error performing DescribeInstances dry run: %s"
|
const errDryRunFailed = "error performing DescribeInstances dry run: %s"
|
||||||
|
|
||||||
@@ -567,10 +563,14 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
for i, query := range queries {
|
for i, query := range queries {
|
||||||
output, err := checkHosts(ctx, catalogState, query.Input, opts, catalogAttributes.TargetAccount)
|
output, err := checkHosts(ctx, catalogState, query.Input, opts, catalogAttributes.TargetAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if catalogAttributes.TargetAccount != nil {
|
// Mapped AWS failures are already gRPC statuses from checkHosts.
|
||||||
return nil, errors.BadRequestStatusf("unable to list hosts for host set %q: %s", query.Id, err)
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, errors.BadRequestStatusf("error retrieving host results for host set id %q: %s", query.Id, err)
|
if catalogAttributes.TargetAccount != nil {
|
||||||
|
return nil, status.Errorf(codes.Unknown, "unable to list hosts for host set %q: %v", query.Id, err)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unknown, "error retrieving host results for host set id %q: %v", query.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
queries[i].Output = output
|
queries[i].Output = output
|
||||||
@@ -584,7 +584,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
for _, instance := range reservation.Instances {
|
for _, instance := range reservation.Instances {
|
||||||
host, err := awsInstanceToHost(instance, catalogAttributes)
|
host, err := awsInstanceToHost(instance, catalogAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatusf("error processing host results for host set id %q: %s", query.Id, err)
|
return nil, status.Errorf(codes.Unknown, "error processing host results for host set id %q: %v", query.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
||||||
@@ -759,25 +759,31 @@ func checkHosts(ctx context.Context,
|
|||||||
// use the principal account to execute the DescribeInstances command
|
// use the principal account to execute the DescribeInstances command
|
||||||
ec2Client, err = principalCatalogState.EC2Client(ctx, opts...)
|
ec2Client, err = principalCatalogState.EC2Client(ctx, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if st, _ := errors.ParseAWSError("EC2 client setup", err); st.Code() != codes.Unknown {
|
||||||
|
return nil, st.Err()
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// use the target account to execute the DescribeInstances command
|
// use the target account to execute the DescribeInstances command
|
||||||
ec2Client, err = ec2ClientForTarget(ctx, principalCatalogState, target, opts)
|
ec2Client, err = ec2ClientForTarget(ctx, principalCatalogState, target, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("EC2 client setup failed for target account: %w", err)
|
return nil, fmt.Errorf("EC2 client setup failed for target account: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := ec2Client.DescribeInstances(ctx, input)
|
output, err := ec2Client.DescribeInstances(ctx, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var awsErr smithy.APIError
|
if errors.IsDryRunSuccess(err) {
|
||||||
ok := goerrors.As(err, &awsErr)
|
// DryRunOperation: request would have succeeded; not a failure.
|
||||||
if ok && awsErr.ErrorCode() == dryRunOperationErrorCode {
|
|
||||||
// successful DryRun validation, no response necessary, no error thrown
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
if st, _ := errors.ParseAWSError("DescribeInstances", err); st.Code() != codes.Unknown {
|
||||||
|
return nil, st.Err()
|
||||||
|
}
|
||||||
if target != nil {
|
if target != nil {
|
||||||
return nil, fmt.Errorf("EC2 DescribeInstances failed for target account: %w", err)
|
return nil, fmt.Errorf("EC2 DescribeInstances failed for target account: %w", err)
|
||||||
}
|
}
|
||||||
@@ -800,6 +806,9 @@ func ec2ClientForTarget(
|
|||||||
// principal identity).
|
// principal identity).
|
||||||
principalCfg, err := principalCatalogState.GenerateCredentialChain(ctx)
|
principalCfg, err := principalCatalogState.GenerateCredentialChain(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if st, _ := errors.ParseAWSError("EC2 client setup for target account", err); st.Code() != codes.Unknown {
|
||||||
|
return nil, st.Err()
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("principal AWS configuration could not be loaded: %w", err)
|
return nil, fmt.Errorf("principal AWS configuration could not be loaded: %w", err)
|
||||||
}
|
}
|
||||||
if principalCfg == nil {
|
if principalCfg == nil {
|
||||||
@@ -813,9 +822,6 @@ func ec2ClientForTarget(
|
|||||||
provider := stscreds.NewAssumeRoleProvider(stsClient, target.RoleArn, func(o *stscreds.AssumeRoleOptions) {
|
provider := stscreds.NewAssumeRoleProvider(stsClient, target.RoleArn, func(o *stscreds.AssumeRoleOptions) {
|
||||||
if target.RoleSessionName != "" {
|
if target.RoleSessionName != "" {
|
||||||
o.RoleSessionName = target.RoleSessionName
|
o.RoleSessionName = target.RoleSessionName
|
||||||
} else {
|
|
||||||
// STS may reject the AssumeRole request if session name is not provided.
|
|
||||||
o.RoleSessionName = defaultSessionName
|
|
||||||
}
|
}
|
||||||
if target.RoleExternalId != "" {
|
if target.RoleExternalId != "" {
|
||||||
o.ExternalID = &target.RoleExternalId
|
o.ExternalID = &target.RoleExternalId
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||||
|
awserrors "github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
@@ -2299,7 +2300,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("error retrieving host results for host set id \"foobar\": %s", testDescribeInstancesError),
|
expectedErrContains: fmt.Sprintf("error retrieving host results for host set id \"foobar\": %s", testDescribeInstancesError),
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "target attributes missing target_role_arn",
|
name: "target attributes missing target_role_arn",
|
||||||
@@ -2365,7 +2366,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 client setup failed for target account: test EC2 client error",
|
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 client setup failed for target account: test EC2 client error",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "target DescribeInstances error",
|
name: "target DescribeInstances error",
|
||||||
@@ -2393,7 +2394,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 DescribeInstances failed for target account: DescribeInstances error",
|
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 DescribeInstances failed for target account: DescribeInstances error",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "awsInstanceToHost error",
|
name: "awsInstanceToHost error",
|
||||||
@@ -2454,7 +2455,115 @@ func TestPluginListHostsErr(t *testing.T) {
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: "error processing host results for host set id \"foobar\": response integrity error: missing instance id",
|
expectedErrContains: "error processing host results for host set id \"foobar\": response integrity error: missing instance id",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DescribeInstances AccessDenied",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: testListHostsPersisted(),
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
testListHostsSet("foobar", "tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DescribeInstances UnauthorizedOperation",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: testListHostsPersisted(),
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
testListHostsSet("foobar", "tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("UnauthorizedOperation", "You are not authorized to perform this operation.")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target DescribeInstances AccessDenied",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
credential.ConstTargetRoleArn: structpb.NewStringValue("arn:aws:iam::222222222222:role/Target"),
|
||||||
|
credential.ConstTargetRegion: structpb.NewStringValue("eu-west-1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: testListHostsPersisted(),
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
testListHostsSet("foobar", "tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target EC2 client AccessDenied",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
credential.ConstTargetRoleArn: structpb.NewStringValue("arn:aws:iam::222222222222:role/Target"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: testListHostsPersisted(),
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
testListHostsSet("foobar", "tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(func(...aws.Config) (EC2API, error) {
|
||||||
|
return nil, awserrors.TestAwsError("AccessDenied", "not authorized to assume role")
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
// ec2ClientForTarget returns testEC2APIFunc errors unparsed; checkHosts
|
||||||
|
// only preserves existing gRPC statuses, so this surfaces as Unknown.
|
||||||
|
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 client setup failed for target account: api error AccessDenied: not authorized to assume role",
|
||||||
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user