diff --git a/go.mod b/go.mod index 29b301d148..3064e9a5f4 100644 --- a/go.mod +++ b/go.mod @@ -13,17 +13,17 @@ require ( github.com/aws/aws-sdk-go-v2/credentials v1.17.65 github.com/aws/aws-sdk-go-v2/service/autoscaling v1.52.3 github.com/aws/aws-sdk-go-v2/service/cloudformation v1.59.2 - github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.48.3 + github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.48.4 github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.47.3 github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.51.3 github.com/aws/aws-sdk-go-v2/service/ec2 v1.210.1 github.com/aws/aws-sdk-go-v2/service/eks v1.64.0 - github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.2 - github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.1 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.3 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2 github.com/aws/aws-sdk-go-v2/service/iam v1.41.1 github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 github.com/aws/aws-sdk-go-v2/service/outposts v1.50.1 - github.com/aws/aws-sdk-go-v2/service/ssm v1.58.1 + github.com/aws/aws-sdk-go-v2/service/ssm v1.58.2 github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 github.com/aws/smithy-go v1.22.3 github.com/awslabs/amazon-eks-ami/nodeadm v0.0.0-20250219002025-c3b5cd3d2fd9 diff --git a/go.sum b/go.sum index 2618e02fb2..7a889d5343 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ github.com/aws/aws-sdk-go-v2/service/autoscaling v1.52.3 h1:QsKdBxtC8csnKt5BbV7D github.com/aws/aws-sdk-go-v2/service/autoscaling v1.52.3/go.mod h1:CDqMoc3KRdZJ8qziW96J35lKH01Wq3B2aihtHj2JbRs= github.com/aws/aws-sdk-go-v2/service/cloudformation v1.59.2 h1:o9cuZdZlI9VWMqsNa2mnf2IRsFAROHnaYA1BW3lHGuY= github.com/aws/aws-sdk-go-v2/service/cloudformation v1.59.2/go.mod h1:penaZKzGmqHGZId4EUCBIW/f9l4Y7hQ5NKd45yoCYuI= -github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.48.3 h1:nTKHvvDTsS6SqAqu/fDhpmbNmDz+0ONh8niPoCkhPtM= -github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.48.3/go.mod h1:/BibEr5ksr34abqBTQN213GrNG6GCKCB6WG7CH4zH2w= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.48.4 h1:pQpinmWv9jEisDR6/DccOf2cXdAf/CAwQ39nfJfJDlE= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.48.4/go.mod h1:/BibEr5ksr34abqBTQN213GrNG6GCKCB6WG7CH4zH2w= github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.47.3 h1:3y0jkGtsaZLCg+n73BoSXOAkLFtgmD/+4prXW1pzovc= github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.47.3/go.mod h1:uo14VBn5cNk/BPGTPz3kyLBxgpgOObgO8lmz+H7Z4Ck= github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.51.3 h1:4U9dpQZTvJ0Mi1qn8L1hRJ4igFCQYEjwUuOmYkWM5tE= @@ -138,10 +138,10 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.210.1 h1:+4A9SDduLZFlDeXWRmfQ6r8kyEJ github.com/aws/aws-sdk-go-v2/service/ec2 v1.210.1/go.mod h1:ouvGEfHbLaIlWwpDpOVWPWR+YwO0HDv3vm5tYLq8ImY= github.com/aws/aws-sdk-go-v2/service/eks v1.64.0 h1:EYeOThTRysemFtC6J6h6b7dNg3jN03QuO5cg92ojIQE= github.com/aws/aws-sdk-go-v2/service/eks v1.64.0/go.mod h1:v1xXy6ea0PHtWkjFUvAUh6B/5wv7UF909Nru0dOIJDk= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.2 h1:Zlfmpg4QsduBeiK0vTc8WjnHZoYVGe64FcwuCsipjWE= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.2/go.mod h1:H232HdqVlSUoqy0cMJYW1TKjcxvGFGFZ20xQG8fOAPw= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.1 h1:USXR7nfl+bu7HnR/M3KtnPD3wjlCXM72kYX+2PaIgEI= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.1/go.mod h1:xnCC3vFBfOKpU6PcsCKL2ktgBTZfOwTGxj6V8/X3IS4= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.3 h1:DpyV8LeDf0y7iDaGZ3h1Y+Nh5IaBOR+xj44vVgEEegY= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.3/go.mod h1:H232HdqVlSUoqy0cMJYW1TKjcxvGFGFZ20xQG8fOAPw= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2 h1:vX70Z4lNSr7XsioU0uJq5yvxgI50sB66MvD+V/3buS4= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2/go.mod h1:xnCC3vFBfOKpU6PcsCKL2ktgBTZfOwTGxj6V8/X3IS4= github.com/aws/aws-sdk-go-v2/service/eventbridge v1.36.12 h1:uH6GOnGSvVN9MCk6o3+HvZFpdqL7AzJKNOTM/6l+3/s= github.com/aws/aws-sdk-go-v2/service/eventbridge v1.36.12/go.mod h1:6qtp53AQg7KEeYrsp430PNlmVVO9qK0Xw8nddE1y+ow= github.com/aws/aws-sdk-go-v2/service/iam v1.41.1 h1:Kq3R+K49y23CGC5UQF3Vpw5oZEQk5gF/nn+MekPD0ZY= @@ -166,8 +166,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.77.1 h1:5bI9tJL2Z0FGFtp/LPDv0eyliFBHC github.com/aws/aws-sdk-go-v2/service/s3 v1.77.1/go.mod h1:njj3tSJONkfdLt4y6X8pyqeM6sJLNZxmzctKKV+n1GM= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.15 h1:KRXf9/NWjoRgj2WJbX13GNjBPQ1SxUYLnIfXTz08mWs= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.15/go.mod h1:1CY54O4jz8BzgH2d6KyrzKWr2bAoqKsqUv2YZUGwMLE= -github.com/aws/aws-sdk-go-v2/service/ssm v1.58.1 h1:GLyAQEth2SljkC2DP5iK2GMkzgrGvURD+NEBVgQer3I= -github.com/aws/aws-sdk-go-v2/service/ssm v1.58.1/go.mod h1:PUWUl5MDiYNQkUHN9Pyd9kgtA/YhbxnSnHP+yQqzrM8= +github.com/aws/aws-sdk-go-v2/service/ssm v1.58.2 h1:uXy3QGAw3xv0RS+OlbeMEAnOA3vFFsf7yvjUswV6N/k= +github.com/aws/aws-sdk-go-v2/service/ssm v1.58.2/go.mod h1:PUWUl5MDiYNQkUHN9Pyd9kgtA/YhbxnSnHP+yQqzrM8= github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 h1:pdgODsAhGo4dvzC3JAG5Ce0PX8kWXrTZGx+jxADD+5E= github.com/aws/aws-sdk-go-v2/service/sso v1.25.2/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 h1:90uX0veLKcdHVfvxhkWUQSCi5VabtwMLFutYiRke4oo= diff --git a/pkg/actions/irsa/get.go b/pkg/actions/irsa/get.go index 7109e19146..ac217c354a 100644 --- a/pkg/actions/irsa/get.go +++ b/pkg/actions/irsa/get.go @@ -14,38 +14,9 @@ type GetOptions struct { } func (m *Manager) Get(ctx context.Context, options GetOptions) ([]*api.ClusterIAMServiceAccount, error) { - remoteServiceAccounts, err := m.stackManager.GetIAMServiceAccounts(ctx) + remoteServiceAccounts, err := m.stackManager.GetIAMServiceAccounts(ctx, options.Name, options.Namespace) if err != nil { return nil, fmt.Errorf("getting iamserviceaccounts: %w", err) } - - if options.Namespace != "" { - remoteServiceAccounts = filterByNamespace(remoteServiceAccounts, options.Namespace) - } - - if options.Name != "" { - remoteServiceAccounts = filterByName(remoteServiceAccounts, options.Name) - } - return remoteServiceAccounts, nil } - -func filterByNamespace(serviceAccounts []*api.ClusterIAMServiceAccount, namespace string) []*api.ClusterIAMServiceAccount { - var serviceAccountsMatching []*api.ClusterIAMServiceAccount - for _, sa := range serviceAccounts { - if sa.Namespace == namespace { - serviceAccountsMatching = append(serviceAccountsMatching, sa) - } - } - return serviceAccountsMatching -} - -func filterByName(serviceAccounts []*api.ClusterIAMServiceAccount, name string) []*api.ClusterIAMServiceAccount { - var serviceAccountsMatching []*api.ClusterIAMServiceAccount - for _, sa := range serviceAccounts { - if sa.Name == name { - serviceAccountsMatching = append(serviceAccountsMatching, sa) - } - } - return serviceAccountsMatching -} diff --git a/pkg/actions/irsa/get_test.go b/pkg/actions/irsa/get_test.go index 816bf8549b..1108041963 100644 --- a/pkg/actions/irsa/get_test.go +++ b/pkg/actions/irsa/get_test.go @@ -24,8 +24,8 @@ var _ = Describe("Get", func() { irsaManager = irsa.New("my-cluster", fakeStackManager, nil, nil) }) - When("no options are specified", func() { - It("returns all service accounts", func() { + When("no error occurs", func() { + It("returns service accounts from GetIAMServiceAccounts", func() { fakeStackManager.GetIAMServiceAccountsReturns([]*api.ClusterIAMServiceAccount{ { ClusterIAMMeta: api.ClusterIAMMeta{ @@ -65,109 +65,4 @@ var _ = Describe("Get", func() { })) }) }) - - When("name option is specified", func() { - It("returns only the service account matching the name", func() { - fakeStackManager.GetIAMServiceAccountsReturns([]*api.ClusterIAMServiceAccount{ - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "test-sa", - Namespace: "default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "test-sa-2", - Namespace: "not-default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - }, nil) - - serviceAccounts, err := irsaManager.Get(context.Background(), irsa.GetOptions{Name: "test-sa"}) - Expect(err).NotTo(HaveOccurred()) - - Expect(fakeStackManager.GetIAMServiceAccountsCallCount()).To(Equal(1)) - Expect(serviceAccounts).To(Equal([]*api.ClusterIAMServiceAccount{ - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "test-sa", - Namespace: "default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - })) - }) - }) - - When("namespace option is specified", func() { - It("returns only the service account matching the name", func() { - fakeStackManager.GetIAMServiceAccountsReturns([]*api.ClusterIAMServiceAccount{ - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "test-sa", - Namespace: "default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "test-sa-2", - Namespace: "not-default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - }, nil) - - serviceAccounts, err := irsaManager.Get(context.Background(), irsa.GetOptions{Namespace: "not-default"}) - Expect(err).NotTo(HaveOccurred()) - - Expect(fakeStackManager.GetIAMServiceAccountsCallCount()).To(Equal(1)) - Expect(serviceAccounts).To(Equal([]*api.ClusterIAMServiceAccount{ - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "test-sa-2", - Namespace: "not-default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - })) - }) - }) - - When("name and namespace option is specified", func() { - It("returns only the service account matching the name", func() { - fakeStackManager.GetIAMServiceAccountsReturns([]*api.ClusterIAMServiceAccount{ - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "test-sa", - Namespace: "default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "some-other-sa", - Namespace: "default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - }, nil) - - serviceAccounts, err := irsaManager.Get(context.Background(), irsa.GetOptions{Namespace: "default", Name: "test-sa"}) - Expect(err).NotTo(HaveOccurred()) - - Expect(fakeStackManager.GetIAMServiceAccountsCallCount()).To(Equal(1)) - Expect(serviceAccounts).To(Equal([]*api.ClusterIAMServiceAccount{ - { - ClusterIAMMeta: api.ClusterIAMMeta{ - Name: "test-sa", - Namespace: "default", - }, - AttachPolicyARNs: []string{"arn-123"}, - }, - })) - }) - }) }) diff --git a/pkg/actions/podidentityassociation/deleter.go b/pkg/actions/podidentityassociation/deleter.go index 6573b2efba..782b2dedf2 100644 --- a/pkg/actions/podidentityassociation/deleter.go +++ b/pkg/actions/podidentityassociation/deleter.go @@ -25,7 +25,7 @@ type StackLister interface { ListPodIdentityStackNames(ctx context.Context) ([]string, error) DescribeStack(ctx context.Context, stack *manager.Stack) (*manager.Stack, error) GetStackTemplate(ctx context.Context, stackName string) (string, error) - GetIAMServiceAccounts(ctx context.Context) ([]*api.ClusterIAMServiceAccount, error) + GetIAMServiceAccounts(ctx context.Context, name string, namespace string) ([]*api.ClusterIAMServiceAccount, error) } // A StackDeleter lists and deletes CloudFormation stacks. diff --git a/pkg/actions/podidentityassociation/fakes/fake_stack_updater.go b/pkg/actions/podidentityassociation/fakes/fake_stack_updater.go index b0c9e38f75..02c9b1cdc3 100644 --- a/pkg/actions/podidentityassociation/fakes/fake_stack_updater.go +++ b/pkg/actions/podidentityassociation/fakes/fake_stack_updater.go @@ -25,10 +25,12 @@ type FakeStackUpdater struct { result1 *manager.Stack result2 error } - GetIAMServiceAccountsStub func(context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error) + GetIAMServiceAccountsStub func(context.Context, string, string) ([]*v1alpha5.ClusterIAMServiceAccount, error) getIAMServiceAccountsMutex sync.RWMutex getIAMServiceAccountsArgsForCall []struct { arg1 context.Context + arg2 string + arg3 string } getIAMServiceAccountsReturns struct { result1 []*v1alpha5.ClusterIAMServiceAccount @@ -146,18 +148,20 @@ func (fake *FakeStackUpdater) DescribeStackReturnsOnCall(i int, result1 *manager }{result1, result2} } -func (fake *FakeStackUpdater) GetIAMServiceAccounts(arg1 context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error) { +func (fake *FakeStackUpdater) GetIAMServiceAccounts(arg1 context.Context, arg2 string, arg3 string) ([]*v1alpha5.ClusterIAMServiceAccount, error) { fake.getIAMServiceAccountsMutex.Lock() ret, specificReturn := fake.getIAMServiceAccountsReturnsOnCall[len(fake.getIAMServiceAccountsArgsForCall)] fake.getIAMServiceAccountsArgsForCall = append(fake.getIAMServiceAccountsArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 string + arg3 string + }{arg1, arg2, arg3}) stub := fake.GetIAMServiceAccountsStub fakeReturns := fake.getIAMServiceAccountsReturns - fake.recordInvocation("GetIAMServiceAccounts", []interface{}{arg1}) + fake.recordInvocation("GetIAMServiceAccounts", []interface{}{arg1, arg2, arg3}) fake.getIAMServiceAccountsMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -171,17 +175,17 @@ func (fake *FakeStackUpdater) GetIAMServiceAccountsCallCount() int { return len(fake.getIAMServiceAccountsArgsForCall) } -func (fake *FakeStackUpdater) GetIAMServiceAccountsCalls(stub func(context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error)) { +func (fake *FakeStackUpdater) GetIAMServiceAccountsCalls(stub func(context.Context, string, string) ([]*v1alpha5.ClusterIAMServiceAccount, error)) { fake.getIAMServiceAccountsMutex.Lock() defer fake.getIAMServiceAccountsMutex.Unlock() fake.GetIAMServiceAccountsStub = stub } -func (fake *FakeStackUpdater) GetIAMServiceAccountsArgsForCall(i int) context.Context { +func (fake *FakeStackUpdater) GetIAMServiceAccountsArgsForCall(i int) (context.Context, string, string) { fake.getIAMServiceAccountsMutex.RLock() defer fake.getIAMServiceAccountsMutex.RUnlock() argsForCall := fake.getIAMServiceAccountsArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeStackUpdater) GetIAMServiceAccountsReturns(result1 []*v1alpha5.ClusterIAMServiceAccount, result2 error) { diff --git a/pkg/actions/podidentityassociation/migrator.go b/pkg/actions/podidentityassociation/migrator.go index b5608afe6d..02148d2fc8 100644 --- a/pkg/actions/podidentityassociation/migrator.go +++ b/pkg/actions/podidentityassociation/migrator.go @@ -86,7 +86,7 @@ func (m *Migrator) MigrateToPodIdentity(ctx context.Context, options PodIdentity */ resolver := IRSAv1StackNameResolver{} if err := resolver.Populate(func() ([]*api.ClusterIAMServiceAccount, error) { - return m.stackUpdater.GetIAMServiceAccounts(ctx) + return m.stackUpdater.GetIAMServiceAccounts(ctx, "", "") }); err != nil { return err } diff --git a/pkg/actions/podidentityassociation/mocks/StackDeleter.go b/pkg/actions/podidentityassociation/mocks/StackDeleter.go index e1db024da3..ad94a864f8 100644 --- a/pkg/actions/podidentityassociation/mocks/StackDeleter.go +++ b/pkg/actions/podidentityassociation/mocks/StackDeleter.go @@ -132,9 +132,9 @@ func (_c *StackDeleter_DescribeStack_Call) RunAndReturn(run func(context.Context return _c } -// GetIAMServiceAccounts provides a mock function with given fields: ctx -func (_m *StackDeleter) GetIAMServiceAccounts(ctx context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error) { - ret := _m.Called(ctx) +// GetIAMServiceAccounts provides a mock function with given fields: ctx, name, namespace +func (_m *StackDeleter) GetIAMServiceAccounts(ctx context.Context, name string, namespace string) ([]*v1alpha5.ClusterIAMServiceAccount, error) { + ret := _m.Called(ctx, name, namespace) if len(ret) == 0 { panic("no return value specified for GetIAMServiceAccounts") @@ -142,19 +142,19 @@ func (_m *StackDeleter) GetIAMServiceAccounts(ctx context.Context) ([]*v1alpha5. var r0 []*v1alpha5.ClusterIAMServiceAccount var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error)); ok { - return rf(ctx) + if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]*v1alpha5.ClusterIAMServiceAccount, error)); ok { + return rf(ctx, name, namespace) } - if rf, ok := ret.Get(0).(func(context.Context) []*v1alpha5.ClusterIAMServiceAccount); ok { - r0 = rf(ctx) + if rf, ok := ret.Get(0).(func(context.Context, string, string) []*v1alpha5.ClusterIAMServiceAccount); ok { + r0 = rf(ctx, name, namespace) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*v1alpha5.ClusterIAMServiceAccount) } } - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, name, namespace) } else { r1 = ret.Error(1) } @@ -169,13 +169,15 @@ type StackDeleter_GetIAMServiceAccounts_Call struct { // GetIAMServiceAccounts is a helper method to define mock.On call // - ctx context.Context -func (_e *StackDeleter_Expecter) GetIAMServiceAccounts(ctx interface{}) *StackDeleter_GetIAMServiceAccounts_Call { - return &StackDeleter_GetIAMServiceAccounts_Call{Call: _e.mock.On("GetIAMServiceAccounts", ctx)} +// - name string +// - namespace string +func (_e *StackDeleter_Expecter) GetIAMServiceAccounts(ctx interface{}, name interface{}, namespace interface{}) *StackDeleter_GetIAMServiceAccounts_Call { + return &StackDeleter_GetIAMServiceAccounts_Call{Call: _e.mock.On("GetIAMServiceAccounts", ctx, name, namespace)} } -func (_c *StackDeleter_GetIAMServiceAccounts_Call) Run(run func(ctx context.Context)) *StackDeleter_GetIAMServiceAccounts_Call { +func (_c *StackDeleter_GetIAMServiceAccounts_Call) Run(run func(ctx context.Context, name string, namespace string)) *StackDeleter_GetIAMServiceAccounts_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -185,7 +187,7 @@ func (_c *StackDeleter_GetIAMServiceAccounts_Call) Return(_a0 []*v1alpha5.Cluste return _c } -func (_c *StackDeleter_GetIAMServiceAccounts_Call) RunAndReturn(run func(context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error)) *StackDeleter_GetIAMServiceAccounts_Call { +func (_c *StackDeleter_GetIAMServiceAccounts_Call) RunAndReturn(run func(context.Context, string, string) ([]*v1alpha5.ClusterIAMServiceAccount, error)) *StackDeleter_GetIAMServiceAccounts_Call { _c.Call.Return(run) return _c } diff --git a/pkg/cfn/manager/fakes/fake_stack_manager.go b/pkg/cfn/manager/fakes/fake_stack_manager.go index b4047b44bb..8c5dc7b43a 100644 --- a/pkg/cfn/manager/fakes/fake_stack_manager.go +++ b/pkg/cfn/manager/fakes/fake_stack_manager.go @@ -343,10 +343,12 @@ type FakeStackManager struct { result1 []*manager.Stack result2 error } - GetIAMServiceAccountsStub func(context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error) + GetIAMServiceAccountsStub func(context.Context, string, string) ([]*v1alpha5.ClusterIAMServiceAccount, error) getIAMServiceAccountsMutex sync.RWMutex getIAMServiceAccountsArgsForCall []struct { arg1 context.Context + arg2 string + arg3 string } getIAMServiceAccountsReturns struct { result1 []*v1alpha5.ClusterIAMServiceAccount @@ -2396,18 +2398,20 @@ func (fake *FakeStackManager) GetIAMAddonsStacksReturnsOnCall(i int, result1 []* }{result1, result2} } -func (fake *FakeStackManager) GetIAMServiceAccounts(arg1 context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error) { +func (fake *FakeStackManager) GetIAMServiceAccounts(arg1 context.Context, arg2 string, arg3 string) ([]*v1alpha5.ClusterIAMServiceAccount, error) { fake.getIAMServiceAccountsMutex.Lock() ret, specificReturn := fake.getIAMServiceAccountsReturnsOnCall[len(fake.getIAMServiceAccountsArgsForCall)] fake.getIAMServiceAccountsArgsForCall = append(fake.getIAMServiceAccountsArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 string + arg3 string + }{arg1, arg2, arg3}) stub := fake.GetIAMServiceAccountsStub fakeReturns := fake.getIAMServiceAccountsReturns - fake.recordInvocation("GetIAMServiceAccounts", []interface{}{arg1}) + fake.recordInvocation("GetIAMServiceAccounts", []interface{}{arg1, arg2, arg3}) fake.getIAMServiceAccountsMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -2421,17 +2425,17 @@ func (fake *FakeStackManager) GetIAMServiceAccountsCallCount() int { return len(fake.getIAMServiceAccountsArgsForCall) } -func (fake *FakeStackManager) GetIAMServiceAccountsCalls(stub func(context.Context) ([]*v1alpha5.ClusterIAMServiceAccount, error)) { +func (fake *FakeStackManager) GetIAMServiceAccountsCalls(stub func(context.Context, string, string) ([]*v1alpha5.ClusterIAMServiceAccount, error)) { fake.getIAMServiceAccountsMutex.Lock() defer fake.getIAMServiceAccountsMutex.Unlock() fake.GetIAMServiceAccountsStub = stub } -func (fake *FakeStackManager) GetIAMServiceAccountsArgsForCall(i int) context.Context { +func (fake *FakeStackManager) GetIAMServiceAccountsArgsForCall(i int) (context.Context, string, string) { fake.getIAMServiceAccountsMutex.RLock() defer fake.getIAMServiceAccountsMutex.RUnlock() argsForCall := fake.getIAMServiceAccountsArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeStackManager) GetIAMServiceAccountsReturns(result1 []*v1alpha5.ClusterIAMServiceAccount, result2 error) { diff --git a/pkg/cfn/manager/iam.go b/pkg/cfn/manager/iam.go index b26579430e..0224259b40 100644 --- a/pkg/cfn/manager/iam.go +++ b/pkg/cfn/manager/iam.go @@ -73,8 +73,9 @@ func (c *StackCollection) ListIAMServiceAccountStacks(ctx context.Context) ([]st return names, nil } -// GetIAMServiceAccounts calls DescribeIAMServiceAccountStacks and return native iamserviceaccounts -func (c *StackCollection) GetIAMServiceAccounts(ctx context.Context) ([]*api.ClusterIAMServiceAccount, error) { +// GetIAMServiceAccounts calls DescribeIAMServiceAccountStacks and return native iamserviceaccounts. +// If name or namespace are provided, only service accounts matching those fields will be returned. +func (c *StackCollection) GetIAMServiceAccounts(ctx context.Context, name string, namespace string) ([]*api.ClusterIAMServiceAccount, error) { stacks, err := c.DescribeIAMServiceAccountStacks(ctx) if err != nil { return nil, err @@ -86,6 +87,14 @@ func (c *StackCollection) GetIAMServiceAccounts(ctx context.Context) ([]*api.Clu if err != nil { return nil, err } + + if name != "" && name != meta.Name { + continue + } + if namespace != "" && namespace != meta.Namespace { + continue + } + serviceAccount := &api.ClusterIAMServiceAccount{ ClusterIAMMeta: *meta, Status: &api.ClusterIAMServiceAccountStatus{ diff --git a/pkg/cfn/manager/iam_test.go b/pkg/cfn/manager/iam_test.go new file mode 100644 index 0000000000..2ddce6afe7 --- /dev/null +++ b/pkg/cfn/manager/iam_test.go @@ -0,0 +1,409 @@ +package manager + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + cfn "github.com/aws/aws-sdk-go-v2/service/cloudformation" + "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" + + api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/cfn/outputs" + "github.com/weaveworks/eksctl/pkg/testutils/mockprovider" +) + +var _ = Describe("IAM Service Accounts", func() { + var ( + p *mockprovider.MockProvider + cfg *api.ClusterConfig + stackManager *StackCollection + ctx context.Context + ) + + BeforeEach(func() { + ctx = context.Background() + p = mockprovider.NewMockProvider() + cfg = &api.ClusterConfig{ + Metadata: &api.ClusterMeta{ + Name: "test-cluster", + }, + } + stackManager = NewStackCollection(p, cfg).(*StackCollection) + }) + + Describe("GetIAMServiceAccounts", func() { + It("returns service accounts when found", func() { + // Setup mock response for DescribeIAMServiceAccountStacks + testCases := []iamServiceAccountTestCase{ + { + Name: "app-service-account", + Namespace: "default", + }, + { + Name: "monitoring-service-account", + Namespace: "monitoring", + }, + } + + // Mock the ListStacks call that DescribeIAMServiceAccountStacks uses + stacks := getStacks(testCases) + p.MockCloudFormation().On("ListStacks", mock.Anything, mock.Anything, mock.Anything).Return( + &cfn.ListStacksOutput{ + StackSummaries: getStackSummaries(stacks), + }, nil) + + // Mock the DescribeStacks call that outputs.Collect uses + for _, stack := range stacks { + p.MockCloudFormation().On("DescribeStacks", mock.Anything, mock.MatchedBy(func(input interface{}) bool { + if describeInput, ok := input.(*cfn.DescribeStacksInput); ok { + return describeInput.StackName != nil && *describeInput.StackName == *stack.StackName + } + return false + })).Return(&cfn.DescribeStacksOutput{ + Stacks: []types.Stack{ + { + StackName: stack.StackName, + CreationTime: stack.CreationTime, + StackStatus: stack.StackStatus, + Tags: stack.Tags, + Outputs: []types.Output{ + { + OutputKey: aws.String(outputs.IAMServiceAccountRoleName), + OutputValue: aws.String(fmt.Sprintf("arn:aws:iam::123456789012:role/eksctl-%s-%s-%s", + cfg.Metadata.Name, *stack.Tags[0].Value, *stack.Tags[1].Value)), + }, + }, + }, + }, + }, nil) + } + + // Call the function with no filters - this should use the real implementation + serviceAccounts, err := stackManager.GetIAMServiceAccounts(ctx, "", "") + + // Verify results + Expect(err).NotTo(HaveOccurred()) + Expect(serviceAccounts).To(HaveLen(2)) + + // Verify the service accounts have the expected values + for _, sa := range serviceAccounts { + Expect(sa.Status.RoleARN).NotTo(BeNil()) + if sa.Name == "app-service-account" { + Expect(sa.Namespace).To(Equal("default")) + } else if sa.Name == "monitoring-service-account" { + Expect(sa.Namespace).To(Equal("monitoring")) + } else { + Fail(fmt.Sprintf("Unexpected service account name: %s", sa.Name)) + } + } + }) + + It("filters service accounts by name", func() { + // Setup mock response + testCases := []iamServiceAccountTestCase{ + { + Name: "monitoring-service-account", + Namespace: "monitoring", + }, + { + Name: "app-service-account", + Namespace: "default", + }, + { + Name: "another-app-service-account", + Namespace: "default", + }, + } + + // Mock the ListStacks call + stacks := getStacks(testCases) + p.MockCloudFormation().On("ListStacks", mock.Anything, mock.Anything, mock.Anything).Return( + &cfn.ListStacksOutput{ + StackSummaries: getStackSummaries(stacks), + }, nil) + + // Mock the DescribeStacks call for each stack + for _, stack := range stacks { + p.MockCloudFormation().On("DescribeStacks", mock.Anything, mock.MatchedBy(func(input interface{}) bool { + if describeInput, ok := input.(*cfn.DescribeStacksInput); ok { + return describeInput.StackName != nil && *describeInput.StackName == *stack.StackName + } + return false + })).Return(&cfn.DescribeStacksOutput{ + Stacks: []types.Stack{ + { + StackName: stack.StackName, + CreationTime: stack.CreationTime, + StackStatus: stack.StackStatus, + Tags: stack.Tags, + Outputs: []types.Output{ + { + OutputKey: aws.String(outputs.IAMServiceAccountRoleName), + OutputValue: aws.String(fmt.Sprintf("arn:aws:iam::123456789012:role/eksctl-%s-%s-%s", + cfg.Metadata.Name, *stack.Tags[0].Value, *stack.Tags[1].Value)), + }, + }, + }, + }, + }, nil) + } + + // Call the function with name filter + serviceAccounts, err := stackManager.GetIAMServiceAccounts(ctx, "app-service-account", "") + + // Verify results + Expect(err).NotTo(HaveOccurred()) + Expect(serviceAccounts).To(HaveLen(1)) + Expect(serviceAccounts[0].Name).To(Equal("app-service-account")) + Expect(serviceAccounts[0].Namespace).To(Equal("default")) + }) + + It("filters service accounts by namespace", func() { + // Setup mock response + testCases := []iamServiceAccountTestCase{ + { + Name: "app-service-account", + Namespace: "default", + }, + { + Name: "monitoring-service-account", + Namespace: "monitoring", + }, + { + Name: "observability-account", + Namespace: "monitoring", + }, + { + Name: "another-app-service-account", + Namespace: "default", + }, + } + + // Mock the ListStacks call + stacks := getStacks(testCases) + p.MockCloudFormation().On("ListStacks", mock.Anything, mock.Anything, mock.Anything).Return( + &cfn.ListStacksOutput{ + StackSummaries: getStackSummaries(stacks), + }, nil) + + // Mock the DescribeStacks call for each stack + for _, stack := range stacks { + p.MockCloudFormation().On("DescribeStacks", mock.Anything, mock.MatchedBy(func(input interface{}) bool { + if describeInput, ok := input.(*cfn.DescribeStacksInput); ok { + return describeInput.StackName != nil && *describeInput.StackName == *stack.StackName + } + return false + })).Return(&cfn.DescribeStacksOutput{ + Stacks: []types.Stack{ + { + StackName: stack.StackName, + CreationTime: stack.CreationTime, + StackStatus: stack.StackStatus, + Tags: stack.Tags, + Outputs: []types.Output{ + { + OutputKey: aws.String(outputs.IAMServiceAccountRoleName), + OutputValue: aws.String(fmt.Sprintf("arn:aws:iam::123456789012:role/eksctl-%s-%s-%s", + cfg.Metadata.Name, *stack.Tags[0].Value, *stack.Tags[1].Value)), + }, + }, + }, + }, + }, nil) + } + + // Call the function with namespace filter + serviceAccounts, err := stackManager.GetIAMServiceAccounts(ctx, "", "monitoring") + + // Verify results + Expect(err).NotTo(HaveOccurred()) + Expect(serviceAccounts).To(HaveLen(2)) + for _, sa := range serviceAccounts { + Expect(sa.Namespace).To(Equal("monitoring")) + } + }) + + It("filters service accounts by both name and namespace", func() { + // Setup mock response + testCases := []iamServiceAccountTestCase{ + { + Name: "app-service-account", + Namespace: "default", + }, + { + Name: "app-service-account", + Namespace: "monitoring", + }, + { + Name: "another-app-service-account", + Namespace: "monitoring", + }, + { + Name: "another-app-service-account", + Namespace: "default", + }, + } + + // Mock the ListStacks call + stacks := getStacks(testCases) + p.MockCloudFormation().On("ListStacks", mock.Anything, mock.Anything, mock.Anything).Return( + &cfn.ListStacksOutput{ + StackSummaries: getStackSummaries(stacks), + }, nil) + + // Mock the DescribeStacks call for each stack + for _, stack := range stacks { + p.MockCloudFormation().On("DescribeStacks", mock.Anything, mock.MatchedBy(func(input interface{}) bool { + if describeInput, ok := input.(*cfn.DescribeStacksInput); ok { + return describeInput.StackName != nil && *describeInput.StackName == *stack.StackName + } + return false + })).Return(&cfn.DescribeStacksOutput{ + Stacks: []types.Stack{ + { + StackName: stack.StackName, + CreationTime: stack.CreationTime, + StackStatus: stack.StackStatus, + Tags: stack.Tags, + Outputs: []types.Output{ + { + OutputKey: aws.String(outputs.IAMServiceAccountRoleName), + OutputValue: aws.String(fmt.Sprintf("arn:aws:iam::123456789012:role/eksctl-%s-%s-%s", + cfg.Metadata.Name, *stack.Tags[0].Value, *stack.Tags[1].Value)), + }, + }, + }, + }, + }, nil) + } + + serviceAccounts, err := stackManager.GetIAMServiceAccounts(ctx, "app-service-account", "default") + + Expect(err).NotTo(HaveOccurred()) + Expect(serviceAccounts).To(HaveLen(1)) + Expect(serviceAccounts[0].Name).To(Equal("app-service-account")) + Expect(serviceAccounts[0].Namespace).To(Equal("default")) + }) + + It("handles errors from the CloudFormation API", func() { + // Setup mock error response + expectedError := errors.New("failed to describe IAM service account stacks") + p.MockCloudFormation().On("ListStacks", mock.Anything, mock.Anything, mock.Anything).Return(nil, expectedError) + + // Call the function + serviceAccounts, err := stackManager.GetIAMServiceAccounts(ctx, "", "") + + // Verify results + Expect(err).To(MatchError(expectedError)) + Expect(serviceAccounts).To(BeNil()) + }) + + It("returns empty slice when no service accounts match filters", func() { + // Setup mock response with stacks that won't match our filter + testCases := []iamServiceAccountTestCase{ + { + Name: "app-service-account", + Namespace: "default", + }, + { + Name: "monitoring-service-account", + Namespace: "monitoring", + }, + } + + // Mock the ListStacks call + stacks := getStacks(testCases) + p.MockCloudFormation().On("ListStacks", mock.Anything, mock.Anything, mock.Anything).Return( + &cfn.ListStacksOutput{ + StackSummaries: getStackSummaries(stacks), + }, nil) + + // Mock the DescribeStacks call for each stack + for _, stack := range stacks { + p.MockCloudFormation().On("DescribeStacks", mock.Anything, mock.MatchedBy(func(input interface{}) bool { + if describeInput, ok := input.(*cfn.DescribeStacksInput); ok { + return describeInput.StackName != nil && *describeInput.StackName == *stack.StackName + } + return false + })).Return(&cfn.DescribeStacksOutput{ + Stacks: []types.Stack{ + { + StackName: stack.StackName, + CreationTime: stack.CreationTime, + StackStatus: stack.StackStatus, + Tags: stack.Tags, + Outputs: []types.Output{ + { + OutputKey: aws.String(outputs.IAMServiceAccountRoleName), + OutputValue: aws.String(fmt.Sprintf("arn:aws:iam::123456789012:role/eksctl-%s-%s-%s", + cfg.Metadata.Name, *stack.Tags[0].Value, *stack.Tags[1].Value)), + }, + }, + }, + }, + }, nil) + } + + serviceAccounts, err := stackManager.GetIAMServiceAccounts(ctx, "non-existent", "non-existent") + + Expect(err).NotTo(HaveOccurred()) + Expect(serviceAccounts).To(BeEmpty()) + }) + }) +}) + +type iamServiceAccountTestCase struct { + Name string + Namespace string +} + +func getStacks(testCases []iamServiceAccountTestCase) []types.Stack { + stacks := make([]types.Stack, 0) + + for _, testCase := range testCases { + stackName := fmt.Sprintf("eksctl-test-cluster-addon-iamserviceaccount-%s-%s", testCase.Namespace, testCase.Name) + stack := types.Stack{ + StackName: aws.String(stackName), + CreationTime: aws.Time(time.Now()), + StackStatus: types.StackStatusCreateComplete, + Tags: []types.Tag{ + { + Key: aws.String(api.IAMServiceAccountNameTag), + Value: aws.String(fmt.Sprintf("%s/%s", testCase.Namespace, testCase.Name)), + }, + { + Key: aws.String("Namespace"), + Value: aws.String(testCase.Namespace), + }, + { + Key: aws.String("ServiceAccount"), + Value: aws.String(testCase.Name), + }, + }, + } + stacks = append(stacks, stack) + } + + return stacks +} + +func getStackSummaries(stacks []types.Stack) []types.StackSummary { + summaries := make([]types.StackSummary, 0, len(stacks)) + + for _, stack := range stacks { + summary := types.StackSummary{ + StackName: stack.StackName, + CreationTime: stack.CreationTime, + StackStatus: stack.StackStatus, + } + summaries = append(summaries, summary) + } + + return summaries +} diff --git a/pkg/cfn/manager/interface.go b/pkg/cfn/manager/interface.go index ef887db7bc..ef166a345a 100644 --- a/pkg/cfn/manager/interface.go +++ b/pkg/cfn/manager/interface.go @@ -63,7 +63,7 @@ type StackManager interface { GetClusterStackIfExists(ctx context.Context) (*Stack, error) GetFargateStack(ctx context.Context) (*Stack, error) GetIAMAddonsStacks(ctx context.Context) ([]*Stack, error) - GetIAMServiceAccounts(ctx context.Context) ([]*api.ClusterIAMServiceAccount, error) + GetIAMServiceAccounts(ctx context.Context, name string, namespace string) ([]*api.ClusterIAMServiceAccount, error) GetKarpenterStack(ctx context.Context) (*Stack, error) GetManagedNodeGroupTemplate(ctx context.Context, options GetNodegroupOption) (string, error) GetNodeGroupName(s *Stack) string