More unit testing and addins of mocks from awsutil

This commit is contained in:
Chris Marchesi committed 2021-10-05 16:36:53 -07:00
1 parent f7c5709046
commit a7401ef2d5
5 files changed
+495 -33

No files matched your search

+5 -5
View File
@@ -3,17 +3,17 @@ module github.com/hashicorp/boundary-plugin-host-aws
go 1.16
require (
github.com/aws/aws-sdk-go v1.40.44
github.com/fatih/color v1.12.0 // indirect
github.com/aws/aws-sdk-go v1.40.55
github.com/fatih/color v1.13.0 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/hashicorp/boundary/sdk v0.0.11-0.20210926154843-a834a147850b
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.4
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6-0.20211004211126-d07ba63494a2
github.com/hashicorp/terraform-json v0.12.0
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/rogpeppe/go-internal v1.6.2 // indirect
github.com/stretchr/testify v1.7.0
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect
golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 // indirect
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v2 v2.4.0 // indirect
+15 -12
View File
@@ -65,8 +65,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.40.44 h1:kECaYybTWYZY5IKHvQMxbE6Wi5Qrb+7hbkV7zQV3Sg8=
github.com/aws/aws-sdk-go v1.40.44/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go v1.40.55 h1:l5xNBmafEAfdJIIe32QbTLG3j/JscH8fzZqYyMlntuI=
github.com/aws/aws-sdk-go v1.40.55/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -115,8 +115,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -224,8 +224,8 @@ github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.4 h1:aCPwC2WPyomaw5gk5ZZ7DPevN8d9DS3igkmq6DxGMjU=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.4/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6-0.20211004211126-d07ba63494a2 h1:P5iG1hox8kzIetqnXn/VgrARsAtooV7jK0McTkIOqQI=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6-0.20211004211126-d07ba63494a2/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2/go.mod h1:EN1DJMjv9y5e/uRAuP2WPibAox5KHEiq2BjZl00aNPQ=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.1/go.mod h1:irE9ILwhE8VooWdhHhBy0qXzOMRlHKoWoMlr31mAFCE=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
@@ -285,14 +285,15 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LE
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
@@ -519,8 +520,10 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef h1:fPxZ3Umkct3LZ8gK9nbk+DWDJ9fstZa2grBn+lWVKPs=
golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+40
View File
@@ -0,0 +1,40 @@
package plugin
import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/iam/iamiface"
"github.com/hashicorp/go-secure-stdlib/awsutil"
)
type testMockIAMState struct {
DeleteAccessKeyCalled bool
}
func (s *testMockIAMState) Reset() {
s.DeleteAccessKeyCalled = false
}
type testMockIAM struct {
iamiface.IAMAPI
State *testMockIAMState
}
func newTestMockIAM(state *testMockIAMState, opts ...awsutil.MockIAMOption) awsutil.IAMAPIFunc {
return func(sess *session.Session) (iamiface.IAMAPI, error) {
m := &testMockIAM{
State: state,
}
f := awsutil.NewMockIAM(opts...)
var err error
m.IAMAPI, err = f(sess)
return m, err
}
}
func (m *testMockIAM) DeleteAccessKey(input *iam.DeleteAccessKeyInput) (*iam.DeleteAccessKeyOutput, error) {
m.State.DeleteAccessKeyCalled = true
return m.IAMAPI.DeleteAccessKey(input)
}
+24 -16
View File
@@ -19,6 +19,9 @@ type awsCatalogPersistedState struct {
AccessKeyId string
SecretAccessKey string
CredsLastRotatedTime time.Time
// testOpts are options that should be used for testing only
testOpts []awsutil.Option
}
func awsCatalogPersistedStateFromProto(in *pb.HostCatalogPersisted) (*awsCatalogPersistedState, error) {
@@ -63,15 +66,15 @@ func (s *awsCatalogPersistedState) ToProto() (*pb.HostCatalogPersisted, error) {
}
func (s *awsCatalogPersistedState) ValidateCreds() error {
c, err := awsutil.NewCredentialsConfig(
c, err := awsutil.NewCredentialsConfig(append([]awsutil.Option{
awsutil.WithAccessKey(s.AccessKeyId),
awsutil.WithSecretKey(s.SecretAccessKey),
)
}, s.testOpts...)...)
if err != nil {
return fmt.Errorf("error loading credentials: %w", err)
}
if _, err := c.GetCallerIdentity(); err != nil {
if _, err := c.GetCallerIdentity(s.testOpts...); err != nil {
return fmt.Errorf("error validating credentials: %w", err)
}
@@ -79,15 +82,17 @@ func (s *awsCatalogPersistedState) ValidateCreds() error {
}
func (s *awsCatalogPersistedState) RotateCreds() error {
c, err := awsutil.NewCredentialsConfig(
c, err := awsutil.NewCredentialsConfig(append([]awsutil.Option{
awsutil.WithAccessKey(s.AccessKeyId),
awsutil.WithSecretKey(s.SecretAccessKey),
)
}, s.testOpts...)...)
if err != nil {
return fmt.Errorf("error loading credentials: %w", err)
}
if err := c.RotateKeys(awsutil.WithValidityCheckTimeout(rotationWaitTimeout)); err != nil {
if err := c.RotateKeys(append([]awsutil.Option{
awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
}, s.testOpts...)...); err != nil {
return fmt.Errorf("error rotating credentials: %w", err)
}
@@ -134,22 +139,25 @@ func (s *awsCatalogPersistedState) ReplaceCreds(accessKeyId, secretAccessKey str
// ID, secret access key, and rotation time fields are zeroed out in
// the state just to ensure that they cannot be re-used after.
func (s *awsCatalogPersistedState) DeleteCreds() error {
c, err := awsutil.NewCredentialsConfig(
c, err := awsutil.NewCredentialsConfig(append([]awsutil.Option{
awsutil.WithAccessKey(s.AccessKeyId),
awsutil.WithSecretKey(s.SecretAccessKey),
)
}, s.testOpts...)...)
if err != nil {
return fmt.Errorf("error loading credentials: %w", err)
}
if err := c.DeleteAccessKey(s.AccessKeyId); err != nil {
if err := c.DeleteAccessKey(s.AccessKeyId, s.testOpts...); err != nil {
// Determine if the deletion error was due to a missing
// resource. If it was, just pass it.
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == iam.ErrCodeNoSuchEntityException {
s.AccessKeyId = ""
s.SecretAccessKey = ""
s.CredsLastRotatedTime = time.Time{}
return nil
var awsErr awserr.Error
if errors.As(err, &awsErr) {
if awsErr.Code() == iam.ErrCodeNoSuchEntityException {
s.AccessKeyId = ""
s.SecretAccessKey = ""
s.CredsLastRotatedTime = time.Time{}
return nil
}
}
// Otherwise treat it as an actual error.
@@ -165,10 +173,10 @@ func (s *awsCatalogPersistedState) DeleteCreds() error {
// GetSession returns a configured AWS session for the credentials in
// the state.
func (s *awsCatalogPersistedState) GetSession() (*session.Session, error) {
c, err := awsutil.NewCredentialsConfig(
c, err := awsutil.NewCredentialsConfig(append([]awsutil.Option{
awsutil.WithAccessKey(s.AccessKeyId),
awsutil.WithSecretKey(s.SecretAccessKey),
)
}, s.testOpts...)...)
if err != nil {
return nil, err
}
+411
View File
@@ -1,13 +1,24 @@
package plugin
import (
"errors"
"fmt"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/iam"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/stretchr/testify/require"
)
const testOptionErr = "test option error"
const testGetCallerIdentityErr = "test error for GetCallerIdentity"
const testGetUserErr = "test error for GetUser"
const testDeleteAccessKeyErr = "test error for DeleteAccessKey"
func TestAwsCatalogPersistedStateFromProto(t *testing.T) {
cases := []struct {
name string
@@ -108,3 +119,403 @@ func TestAwsCatalogPersistedStateFromProto(t *testing.T) {
})
}
}
func TestAwsCatalogPersistedStateToProto(t *testing.T) {
cases := []struct {
name string
in *awsCatalogPersistedState
expected *pb.HostCatalogPersisted
expectedErr string // NOTE: Here for scaffolding but hard to test deep structpb err right now
}{
{
name: "good with non-zero timestamp",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
CredsLastRotatedTime: func() time.Time {
t, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
if err != nil {
panic(err)
}
return t
}(),
},
expected: &pb.HostCatalogPersisted{
Secrets: mustStruct(map[string]interface{}{
constAccessKeyId: "foobar",
constSecretAccessKey: "bazqux",
constCredsLastRotatedTime: "2006-01-02T15:04:05+07:00",
}),
},
},
{
name: "good with zero timestamp",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
CredsLastRotatedTime: time.Time{},
},
expected: &pb.HostCatalogPersisted{
Secrets: mustStruct(map[string]interface{}{
constAccessKeyId: "foobar",
constSecretAccessKey: "bazqux",
constCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
}),
},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
actual, err := tc.in.ToProto()
if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr)
return
}
require.NoError(err)
require.Equal(tc.expected, actual)
})
}
}
func TestAwsCatalogPersistedStateValidateCreds(t *testing.T) {
cases := []struct {
name string
in *awsCatalogPersistedState
expectedErr string
}{
{
name: "could not load credentials",
in: &awsCatalogPersistedState{
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
},
expectedErr: fmt.Sprintf("error loading credentials: error reading options in NewCredentialsConfig: %s", testOptionErr),
},
{
name: "validation error",
in: &awsCatalogPersistedState{
testOpts: []awsutil.Option{awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr))))},
},
expectedErr: fmt.Sprintf("error validating credentials: %s", testGetCallerIdentityErr),
},
{
name: "good",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
testOpts: []awsutil.Option{awsutil.WithSTSAPIFunc(awsutil.NewMockSTS())},
},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
err := tc.in.ValidateCreds()
if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr)
return
}
require.NoError(err)
})
}
}
func TestAwsCatalogPersistedStateRotateCreds(t *testing.T) {
cases := []struct {
name string
in *awsCatalogPersistedState
expectedAccessKeyId string
expectedSecretAccessKey string
expectedNonZeroRotatedTime bool
expectedErr string
}{
{
name: "could not load credentials",
in: &awsCatalogPersistedState{
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
},
expectedErr: fmt.Sprintf("error loading credentials: error reading options in NewCredentialsConfig: %s", testOptionErr),
},
{
name: "rotation error",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)),
),
),
},
},
expectedErr: fmt.Sprintf("error rotating credentials: error calling CreateAccessKey: error calling aws.GetUser: %s", testGetUserErr),
},
{
name: "good",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
testOpts: []awsutil.Option{
awsutil.WithSTSAPIFunc(
awsutil.NewMockSTS(),
),
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithGetUserOutput(
&iam.GetUserOutput{
User: &iam.User{
Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"),
UserId: aws.String("AIDAJQABLZS4A3QDU576Q"),
UserName: aws.String("JohnDoe"),
},
},
),
awsutil.WithCreateAccessKeyOutput(
&iam.CreateAccessKeyOutput{
AccessKey: &iam.AccessKey{
AccessKeyId: aws.String("one"),
SecretAccessKey: aws.String("two"),
UserName: aws.String("JohnDoe"),
},
},
),
),
),
},
},
expectedAccessKeyId: "one",
expectedSecretAccessKey: "two",
expectedNonZeroRotatedTime: true,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
err := tc.in.RotateCreds()
if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr)
return
}
require.NoError(err)
require.Equal(tc.expectedAccessKeyId, tc.in.AccessKeyId)
require.Equal(tc.expectedSecretAccessKey, tc.in.SecretAccessKey)
require.Equal(tc.expectedNonZeroRotatedTime, !tc.in.CredsLastRotatedTime.IsZero())
})
}
}
func TestAwsCatalogPersistedStateReplaceCreds(t *testing.T) {
// NOTE: Not safe to run this test in parallel
state := new(testMockIAMState)
cases := []struct {
name string
in *awsCatalogPersistedState
accessKeyId string
secretAccessKey string
expectedAccessKeyId string
expectedSecretAccessKey string
expectedDeleteCalled bool
expectedErr string
}{
{
name: "missing access key id",
in: &awsCatalogPersistedState{},
expectedErr: "access key id cannot be empty",
},
{
name: "missing access key id",
in: &awsCatalogPersistedState{},
accessKeyId: "one",
expectedErr: "secret access key cannot be empty",
},
{
name: "identical access key id",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
},
accessKeyId: "foobar",
secretAccessKey: "bazqux",
expectedErr: "attempting to replace access key with the same one",
},
{
name: "deletion error",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
CredsLastRotatedTime: time.Now(),
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
),
),
},
},
accessKeyId: "one",
secretAccessKey: "two",
expectedErr: fmt.Sprintf("error deleting old access key: error deleting old access key: %s", testDeleteAccessKeyErr),
},
{
name: "good with delete of old rotated",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
CredsLastRotatedTime: time.Now(),
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
newTestMockIAM(state),
),
},
},
accessKeyId: "one",
secretAccessKey: "two",
expectedAccessKeyId: "one",
expectedSecretAccessKey: "two",
expectedDeleteCalled: true,
},
{
name: "good without delete of old rotated",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
newTestMockIAM(state),
),
},
},
accessKeyId: "one",
secretAccessKey: "two",
expectedAccessKeyId: "one",
expectedSecretAccessKey: "two",
expectedDeleteCalled: false,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
state.Reset()
err := tc.in.ReplaceCreds(tc.accessKeyId, tc.secretAccessKey)
if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr)
return
}
require.NoError(err)
require.Equal(tc.expectedAccessKeyId, tc.in.AccessKeyId)
require.Equal(tc.expectedSecretAccessKey, tc.in.SecretAccessKey)
require.Zero(tc.in.CredsLastRotatedTime)
require.Equal(tc.expectedDeleteCalled, state.DeleteAccessKeyCalled)
})
}
}
func TestAwsCatalogPersistedStateDeleteCreds(t *testing.T) {
cases := []struct {
name string
in *awsCatalogPersistedState
expectedErr string
}{
{
name: "could not load credentials",
in: &awsCatalogPersistedState{
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
},
expectedErr: fmt.Sprintf("error loading credentials: error reading options in NewCredentialsConfig: %s", testOptionErr),
},
{
name: "deletion error",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
),
),
},
},
expectedErr: fmt.Sprintf("error deleting old access key: error deleting old access key: %s", testDeleteAccessKeyErr),
},
{
name: "deletion error, but OK because key was just gone",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(
awsutil.WithDeleteAccessKeyError(
awserr.New(iam.ErrCodeNoSuchEntityException, "", nil),
),
),
),
},
},
},
{
name: "good",
in: &awsCatalogPersistedState{
AccessKeyId: "foobar",
SecretAccessKey: "bazqux",
testOpts: []awsutil.Option{
awsutil.WithIAMAPIFunc(
awsutil.NewMockIAM(),
),
},
},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
err := tc.in.DeleteCreds()
if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr)
return
}
require.NoError(err)
require.Empty(tc.in.AccessKeyId)
require.Empty(tc.in.SecretAccessKey)
require.Zero(tc.in.CredsLastRotatedTime)
})
}
}
func TestAwsCatalogPersistedStateGetSessionErr(t *testing.T) {
require := require.New(t)
state := &awsCatalogPersistedState{
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
}
_, err := state.GetSession()
require.EqualError(err, fmt.Sprintf("error reading options in NewCredentialsConfig: %s", testOptionErr))
}
func TestAwsCatalogPersistedStateEC2ClientErr(t *testing.T) {
require := require.New(t)
state := &awsCatalogPersistedState{
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))},
}
_, err := state.EC2Client("someregion")
require.EqualError(err, fmt.Sprintf("error getting AWS session: error reading options in NewCredentialsConfig: %s", testOptionErr))
}