diff --git a/plugin/helper_test.go b/plugin/helper_test.go index bb1e9c1..598afb4 100644 --- a/plugin/helper_test.go +++ b/plugin/helper_test.go @@ -11,6 +11,8 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) +const testDescribeInstancesError = "DescribeInstances error" + type testMockIAMState struct { DeleteAccessKeyCalled bool } @@ -100,8 +102,10 @@ func newTestMockEC2(state *testMockEC2State, opts ...testMockEC2Option) ec2APIFu } func (m *testMockEC2) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) { - m.State.DescribeInstancesCalled = true - m.State.DescribeInstancesInputParams = input + if m.State != nil { + m.State.DescribeInstancesCalled = true + m.State.DescribeInstancesInputParams = input + } if m.DescribeInstancesError != nil { return nil, m.DescribeInstancesError diff --git a/plugin/plugin.go b/plugin/plugin.go index bc1c8f3..52d0d71 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -198,9 +198,9 @@ func (p *AwsPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalog // integrity. May need to be changed at later time if there are // scenarios where we might be deleting things and any secret state // may be corrupt/and or legitimately missing. - state, err := awsCatalogPersistedStateFromProto(req.GetPersisted()) + state, err := awsCatalogPersistedStateFromProto(req.GetPersisted(), p.testStateOpts...) if err != nil { - return nil, err + return nil, fmt.Errorf("error loading persisted state: %w", err) } if !state.CredsLastRotatedTime.IsZero() { @@ -208,7 +208,7 @@ func (p *AwsPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalog // credentials to ensure that it has the proper permissions to do // it. if err := state.DeleteCreds(); err != nil { - return nil, err + return nil, fmt.Errorf("error removing rotated credentials during catalog deletion: %w", err) } } @@ -226,15 +226,15 @@ func (p *AwsPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest) return nil, errors.New("catalog missing attributes") } - state, err := awsCatalogPersistedStateFromProto(req.GetPersisted()) + state, err := awsCatalogPersistedStateFromProto(req.GetPersisted(), p.testStateOpts...) if err != nil { - return nil, err + return nil, fmt.Errorf("error loading persisted state: %w", err) } // Get/validate the region. region, err := getValidateRegionValue(catalogAttrs) if err != nil { - return nil, err + return nil, fmt.Errorf("catalog validation error: %s", err) } set := req.GetSet() @@ -281,15 +281,15 @@ func (p *AwsPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest) return nil, errors.New("catalog missing attributes") } - state, err := awsCatalogPersistedStateFromProto(req.GetPersisted()) + state, err := awsCatalogPersistedStateFromProto(req.GetPersisted(), p.testStateOpts...) if err != nil { - return nil, err + return nil, fmt.Errorf("error loading persisted state: %w", err) } // Get/validate the region. region, err := getValidateRegionValue(catalogAttrs) if err != nil { - return nil, err + return nil, fmt.Errorf("catalog validation error: %s", err) } // As with catalog, we don't need to really look at the old host @@ -344,15 +344,15 @@ func (p *AwsPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*p return nil, errors.New("catalog missing attributes") } - state, err := awsCatalogPersistedStateFromProto(req.GetPersisted()) + state, err := awsCatalogPersistedStateFromProto(req.GetPersisted(), p.testStateOpts...) if err != nil { - return nil, err + return nil, fmt.Errorf("error loading persisted state: %w", err) } // Get/validate the region. region, err := getValidateRegionValue(catalogAttrs) if err != nil { - return nil, err + return nil, fmt.Errorf("catalog validation error: %w", err) } sets := req.GetSets() diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index b3e4bfe..6c30575 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -6,7 +6,9 @@ import ( "fmt" "testing" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" pb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/hashicorp/go-secure-stdlib/awsutil" "github.com/stretchr/testify/require" @@ -384,3 +386,813 @@ func TestOnUpdateCatalogErr(t *testing.T) { }) } } + +func TestOnDeleteCatalogErr(t *testing.T) { + cases := []struct { + name string + req *pb.OnDeleteCatalogRequest + opts []awsCatalogPersistedStateOption + expectedErr string + }{ + { + name: "persisted state setup error", + req: &pb.OnDeleteCatalogRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + func(s *awsCatalogPersistedState) error { + return errors.New(testOptionErr) + }, + }, + expectedErr: fmt.Sprintf("error loading persisted state: %s", testOptionErr), + }, + { + name: "delete error", + req: &pb.OnDeleteCatalogRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + withStateTestOpts([]awsutil.Option{ + awsutil.WithIAMAPIFunc( + awsutil.NewMockIAM( + awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), + ), + ), + }), + }, + expectedErr: fmt.Sprintf("error removing rotated credentials during catalog deletion: error deleting old access key: %s", testDeleteAccessKeyErr), + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + p := &AwsPlugin{ + testStateOpts: tc.opts, + } + _, err := p.OnDeleteCatalog(context.Background(), tc.req) + require.EqualError(err, tc.expectedErr) + }) + } +} + +func TestOnCreateSetErr(t *testing.T) { + cases := []struct { + name string + req *pb.OnCreateSetRequest + opts []awsCatalogPersistedStateOption + expectedErr string + }{ + { + name: "nil catalog", + req: &pb.OnCreateSetRequest{}, + expectedErr: "catalog is nil", + }, + { + name: "nil catalog attributes", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Secrets: new(structpb.Struct), + }, + }, + expectedErr: "catalog missing attributes", + }, + { + name: "persisted state setup error", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + func(s *awsCatalogPersistedState) error { + return errors.New(testOptionErr) + }, + }, + expectedErr: fmt.Sprintf("error loading persisted state: %s", testOptionErr), + }, + { + name: "invalid region", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "foobar", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + expectedErr: "catalog validation error: not a valid region: foobar", + }, + { + name: "nil set", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + expectedErr: "set is nil", + }, + { + name: "nil attributes in set", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Set: &hostsets.HostSet{}, + }, + expectedErr: "set missing attributes", + }, + { + name: "client load error", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Set: &hostsets.HostSet{ + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + withStateTestOpts([]awsutil.Option{ + awsutil.MockOptionErr(errors.New(testOptionErr)), + }), + }, + expectedErr: fmt.Sprintf("error getting EC2 client: error getting AWS session: error reading options in NewCredentialsConfig: %s", testOptionErr), + }, + { + name: "buildDescribeInstancesInput error", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Set: &hostsets.HostSet{ + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": "foo", + }, + }), + }, + }, + expectedErr: "error building DescribeInstances parameters: error building filters: unexpected type for filter values in \"tag-key\": want array, got string", + }, + { + name: "DescribeInstances error", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Set: &hostsets.HostSet{ + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + withTestEC2APIFunc(newTestMockEC2( + nil, + testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)), + )), + }, + expectedErr: fmt.Sprintf("error performing dry run of DescribeInstances: %s", testDescribeInstancesError), + }, + { + name: "DescribeInstances non-error", + req: &pb.OnCreateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Set: &hostsets.HostSet{ + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + withTestEC2APIFunc(newTestMockEC2( + nil, + )), + }, + expectedErr: "query error: DescribeInstances DryRun should have returned error, but none was found", + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + p := &AwsPlugin{ + testStateOpts: tc.opts, + } + _, err := p.OnCreateSet(context.Background(), tc.req) + require.EqualError(err, tc.expectedErr) + }) + } +} + +func TestOnUpdateSetErr(t *testing.T) { + cases := []struct { + name string + req *pb.OnUpdateSetRequest + opts []awsCatalogPersistedStateOption + expectedErr string + }{ + { + name: "nil catalog", + req: &pb.OnUpdateSetRequest{}, + expectedErr: "catalog is nil", + }, + { + name: "nil catalog attributes", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Secrets: new(structpb.Struct), + }, + }, + expectedErr: "catalog missing attributes", + }, + { + name: "persisted state setup error", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + func(s *awsCatalogPersistedState) error { + return errors.New(testOptionErr) + }, + }, + expectedErr: fmt.Sprintf("error loading persisted state: %s", testOptionErr), + }, + { + name: "invalid region", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "foobar", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + expectedErr: "catalog validation error: not a valid region: foobar", + }, + { + name: "nil set", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + expectedErr: "new set is nil", + }, + { + name: "nil attributes in set", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + NewSet: &hostsets.HostSet{}, + }, + expectedErr: "new set missing attributes", + }, + { + name: "client load error", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + NewSet: &hostsets.HostSet{ + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + withStateTestOpts([]awsutil.Option{ + awsutil.MockOptionErr(errors.New(testOptionErr)), + }), + }, + expectedErr: fmt.Sprintf("error getting EC2 client: error getting AWS session: error reading options in NewCredentialsConfig: %s", testOptionErr), + }, + { + name: "buildDescribeInstancesInput error", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + NewSet: &hostsets.HostSet{ + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": "foo", + }, + }), + }, + }, + expectedErr: "error building DescribeInstances parameters: error building filters: unexpected type for filter values in \"tag-key\": want array, got string", + }, + { + name: "DescribeInstances error", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + NewSet: &hostsets.HostSet{ + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + withTestEC2APIFunc(newTestMockEC2( + nil, + testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)), + )), + }, + expectedErr: fmt.Sprintf("error performing dry run of DescribeInstances: %s", testDescribeInstancesError), + }, + { + name: "DescribeInstances non-error", + req: &pb.OnUpdateSetRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + NewSet: &hostsets.HostSet{ + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + withTestEC2APIFunc(newTestMockEC2( + nil, + )), + }, + expectedErr: "query error: DescribeInstances DryRun should have returned error, but none was found", + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + p := &AwsPlugin{ + testStateOpts: tc.opts, + } + _, err := p.OnUpdateSet(context.Background(), tc.req) + require.EqualError(err, tc.expectedErr) + }) + } +} + +func TestListHostsErr(t *testing.T) { + cases := []struct { + name string + req *pb.ListHostsRequest + opts []awsCatalogPersistedStateOption + expectedErr string + }{ + { + name: "nil catalog", + req: &pb.ListHostsRequest{}, + expectedErr: "catalog is nil", + }, + { + name: "nil catalog attributes", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Secrets: new(structpb.Struct), + }, + }, + expectedErr: "catalog missing attributes", + }, + { + name: "persisted state setup error", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + opts: []awsCatalogPersistedStateOption{ + func(s *awsCatalogPersistedState) error { + return errors.New(testOptionErr) + }, + }, + expectedErr: fmt.Sprintf("error loading persisted state: %s", testOptionErr), + }, + { + name: "invalid region", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "foobar", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + expectedErr: "catalog validation error: not a valid region: foobar", + }, + { + name: "nil sets", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + }, + expectedErr: "sets is nil", + }, + { + name: "set missing id", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Sets: []*hostsets.HostSet{&hostsets.HostSet{}}, + }, + expectedErr: "set missing id", + }, + { + name: "set missing attributes", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Sets: []*hostsets.HostSet{ + &hostsets.HostSet{ + Id: "foobar", + }, + }, + }, + expectedErr: "set missing attributes", + }, + { + name: "buildDescribeInstancesInput error", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Sets: []*hostsets.HostSet{ + &hostsets.HostSet{ + Id: "foobar", + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": "foo", + }, + }), + }, + }, + }, + expectedErr: "error building DescribeInstances parameters for host set id \"foobar\": error building filters: unexpected type for filter values in \"tag-key\": want array, got string", + }, + { + name: "client load error", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Sets: []*hostsets.HostSet{ + &hostsets.HostSet{ + Id: "foobar", + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + }, + opts: []awsCatalogPersistedStateOption{ + withStateTestOpts([]awsutil.Option{ + awsutil.MockOptionErr(errors.New(testOptionErr)), + }), + }, + expectedErr: fmt.Sprintf("error getting EC2 client: error getting AWS session: error reading options in NewCredentialsConfig: %s", testOptionErr), + }, + { + name: "DescribeInstances error", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Sets: []*hostsets.HostSet{ + &hostsets.HostSet{ + Id: "foobar", + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + }, + opts: []awsCatalogPersistedStateOption{ + withTestEC2APIFunc(newTestMockEC2( + nil, + testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)), + )), + }, + expectedErr: fmt.Sprintf("error running DescribeInstances for host set id \"foobar\": %s", testDescribeInstancesError), + }, + { + name: "awsInstanceToHost error", + req: &pb.ListHostsRequest{ + Catalog: &hostcatalogs.HostCatalog{ + Attributes: mustStruct(map[string]interface{}{ + constRegion: "us-west-2", + }), + }, + Persisted: &pb.HostCatalogPersisted{ + Secrets: mustStruct(map[string]interface{}{ + constAccessKeyId: "foobar", + constSecretAccessKey: "bazqux", + constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00", + }), + }, + Sets: []*hostsets.HostSet{ + &hostsets.HostSet{ + Id: "foobar", + Attributes: mustStruct(map[string]interface{}{ + constDescribeInstancesFilters: map[string]interface{}{ + "tag-key": []interface{}{"foo"}, + }, + }), + }, + }, + }, + opts: []awsCatalogPersistedStateOption{ + withTestEC2APIFunc(newTestMockEC2( + nil, + testMockEC2WithDescribeInstancesOutput( + &ec2.DescribeInstancesOutput{ + Reservations: []*ec2.Reservation{ + &ec2.Reservation{ + Instances: []*ec2.Instance{ + &ec2.Instance{ + // Blank so we error out + }, + }, + }, + }, + }, + ), + )), + }, + expectedErr: "error processing host results for host set id \"foobar\": response integrity error: missing instance state", + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + p := &AwsPlugin{ + testStateOpts: tc.opts, + } + _, err := p.ListHosts(context.Background(), tc.req) + require.EqualError(err, tc.expectedErr) + }) + } +}