Skip to content

Commit 9289bf8

Browse files
TiberiuGCcPu1
andauthored
Handle K8s service account lifecycle on eksctl create/delete podidentityassociation commands (#7706)
* Handle K8s service account lifecycle on eksctl create/delete podidentityassociations commands * correct typo Co-authored-by: Chetan Patwal <[email protected]> --------- Co-authored-by: Chetan Patwal <[email protected]>
1 parent 00934fd commit 9289bf8

File tree

11 files changed

+148
-27
lines changed

11 files changed

+148
-27
lines changed

pkg/actions/cluster/owned.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,15 @@ func (c *OwnedCluster) Delete(ctx context.Context, _, podEvictionWaitPeriod time
120120
}
121121
newTasksToDeleteAddonIAM := addon.NewRemover(c.stackManager).DeleteAddonIAMTasks
122122
newTasksToDeletePodIdentityRoles := func() (*tasks.TaskTree, error) {
123-
return podidentityassociation.NewDeleter(c.cfg.Metadata.Name, c.stackManager, c.ctl.AWSProvider.EKS()).
123+
clientSet, err = c.newClientSet()
124+
if err != nil {
125+
if force {
126+
logger.Warning("error occurred while deleting IAM Role stacks for pod identity associations: %v; force=true so proceeding with cluster deletion", err)
127+
return &tasks.TaskTree{}, nil
128+
}
129+
return nil, err
130+
}
131+
return podidentityassociation.NewDeleter(c.cfg.Metadata.Name, c.stackManager, c.ctl.AWSProvider.EKS(), clientSet).
124132
DeleteTasks(ctx, []podidentityassociation.Identifier{})
125133
}
126134

pkg/actions/podidentityassociation/creator.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import (
44
"context"
55
"fmt"
66

7+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
kubeclient "k8s.io/client-go/kubernetes"
9+
710
api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
811
"github.com/weaveworks/eksctl/pkg/awsapi"
912
"github.com/weaveworks/eksctl/pkg/cfn/builder"
13+
"github.com/weaveworks/eksctl/pkg/kubernetes"
1014
"github.com/weaveworks/eksctl/pkg/utils/tasks"
1115
)
1216

@@ -21,13 +25,15 @@ type Creator struct {
2125

2226
stackCreator StackCreator
2327
eksAPI awsapi.EKS
28+
clientSet kubeclient.Interface
2429
}
2530

26-
func NewCreator(clusterName string, stackCreator StackCreator, eksAPI awsapi.EKS) *Creator {
31+
func NewCreator(clusterName string, stackCreator StackCreator, eksAPI awsapi.EKS, clientSet kubeclient.Interface) *Creator {
2732
return &Creator{
2833
clusterName: clusterName,
2934
stackCreator: stackCreator,
3035
eksAPI: eksAPI,
36+
clientSet: clientSet,
3137
}
3238
}
3339

@@ -53,6 +59,18 @@ func (c *Creator) CreateTasks(ctx context.Context, podIdentityAssociations []api
5359
stackCreator: c.stackCreator,
5460
})
5561
}
62+
piaCreationTasks.Append(&tasks.GenericTask{
63+
Description: fmt.Sprintf("create service account %q, if it does not already exist", pia.NameString()),
64+
Doer: func() error {
65+
if err := kubernetes.MaybeCreateServiceAccountOrUpdateMetadata(c.clientSet, v1.ObjectMeta{
66+
Name: pia.ServiceAccountName,
67+
Namespace: pia.Namespace,
68+
}); err != nil {
69+
return fmt.Errorf("failed to create service account %q: %w", pia.NameString(), err)
70+
}
71+
return nil
72+
},
73+
})
5674
piaCreationTasks.Append(&createPodIdentityAssociationTask{
5775
ctx: ctx,
5876
info: fmt.Sprintf("create pod identity association for service account %q", pia.NameString()),

pkg/actions/podidentityassociation/creator_test.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import (
55
"fmt"
66

77
awseks "github.com/aws/aws-sdk-go-v2/service/eks"
8+
9+
"k8s.io/apimachinery/pkg/runtime"
10+
kubeclientfakes "k8s.io/client-go/kubernetes/fake"
11+
kubeclienttesting "k8s.io/client-go/testing"
12+
813
. "github.com/onsi/ginkgo/v2"
914
. "github.com/onsi/gomega"
1015
"github.com/stretchr/testify/mock"
@@ -20,6 +25,7 @@ type createPodIdentityAssociationEntry struct {
2025
toBeCreated []api.PodIdentityAssociation
2126
mockEKS func(provider *mockprovider.MockProvider)
2227
mockCFN func(stackCreator *fakes.FakeStackCreator)
28+
mockK8s func(clientSet *kubeclientfakes.Clientset)
2329
expectedCreateStackCalls int
2430
expectedErr string
2531
}
@@ -28,6 +34,7 @@ var _ = Describe("Create", func() {
2834
var (
2935
creator *podidentityassociation.Creator
3036
fakeStackCreator *fakes.FakeStackCreator
37+
fakeClientSet *kubeclientfakes.Clientset
3138
mockProvider *mockprovider.MockProvider
3239

3340
clusterName = "test-cluster"
@@ -44,12 +51,17 @@ var _ = Describe("Create", func() {
4451
e.mockCFN(fakeStackCreator)
4552
}
4653

54+
fakeClientSet = kubeclientfakes.NewSimpleClientset()
55+
if e.mockK8s != nil {
56+
e.mockK8s(fakeClientSet)
57+
}
58+
4759
mockProvider = mockprovider.NewMockProvider()
4860
if e.mockEKS != nil {
4961
e.mockEKS(mockProvider)
5062
}
5163

52-
creator = podidentityassociation.NewCreator(clusterName, fakeStackCreator, mockProvider.MockEKS())
64+
creator = podidentityassociation.NewCreator(clusterName, fakeStackCreator, mockProvider.MockEKS(), fakeClientSet)
5365

5466
err := creator.CreatePodIdentityAssociations(context.Background(), e.toBeCreated)
5567
if e.expectedErr != "" {
@@ -80,6 +92,32 @@ var _ = Describe("Create", func() {
8092
expectedErr: "creating IAM role for pod identity association",
8193
}),
8294

95+
Entry("returns an error if creating the service account fails", createPodIdentityAssociationEntry{
96+
toBeCreated: []api.PodIdentityAssociation{
97+
{
98+
Namespace: namespace,
99+
ServiceAccountName: serviceAccountName1,
100+
RoleARN: roleARN,
101+
},
102+
},
103+
mockK8s: func(clientSet *kubeclientfakes.Clientset) {
104+
clientSet.PrependReactor("get", "namespaces", func(action kubeclienttesting.Action) (bool, runtime.Object, error) {
105+
return true, nil, genericErr
106+
})
107+
},
108+
mockEKS: func(provider *mockprovider.MockProvider) {
109+
mockProvider.MockEKS().
110+
On("CreatePodIdentityAssociation", mock.Anything, mock.Anything).
111+
Run(func(args mock.Arguments) {
112+
Expect(args).To(HaveLen(2))
113+
Expect(args[1]).To(BeAssignableToTypeOf(&awseks.CreatePodIdentityAssociationInput{}))
114+
}).
115+
Return(&awseks.CreatePodIdentityAssociationOutput{}, nil).
116+
Once()
117+
},
118+
expectedErr: "failed to create service account",
119+
}),
120+
83121
Entry("returns an error if creating the association fails", createPodIdentityAssociationEntry{
84122
toBeCreated: []api.PodIdentityAssociation{
85123
{

pkg/actions/podidentityassociation/deleter.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import (
55
"fmt"
66
"strings"
77

8-
cfntypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
8+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
kubeclient "k8s.io/client-go/kubernetes"
910

1011
"github.com/aws/aws-sdk-go-v2/aws"
12+
cfntypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
1113
"github.com/aws/aws-sdk-go-v2/service/eks"
1214

1315
"github.com/kris-nova/logger"
1416

1517
api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
1618
"github.com/weaveworks/eksctl/pkg/cfn/manager"
19+
"github.com/weaveworks/eksctl/pkg/kubernetes"
1720
"github.com/weaveworks/eksctl/pkg/utils/tasks"
1821
)
1922

@@ -49,6 +52,8 @@ type Deleter struct {
4952
StackDeleter StackDeleter
5053
// APIDeleter deletes pod identity associations using the EKS API.
5154
APIDeleter APIDeleter
55+
// ClientSet is used to delete K8s service accounts.
56+
ClientSet kubeclient.Interface
5257
}
5358

5459
// Identifier represents a pod identity association.
@@ -71,11 +76,12 @@ func (i Identifier) toString(delimiter string) string {
7176
return i.Namespace + delimiter + i.ServiceAccountName
7277
}
7378

74-
func NewDeleter(clusterName string, stackDeleter StackDeleter, apiDeleter APIDeleter) *Deleter {
79+
func NewDeleter(clusterName string, stackDeleter StackDeleter, apiDeleter APIDeleter, clientSet kubeclient.Interface) *Deleter {
7580
return &Deleter{
7681
ClusterName: clusterName,
7782
StackDeleter: stackDeleter,
7883
APIDeleter: apiDeleter,
84+
ClientSet: clientSet,
7985
}
8086
}
8187

@@ -111,7 +117,24 @@ func (d *Deleter) DeleteTasks(ctx context.Context, podIDs []Identifier) (*tasks.
111117
}
112118

113119
for _, p := range podIDs {
114-
taskTree.Append(d.makeDeleteTask(ctx, p, roleStackNames))
120+
piaDeletionTasks := &tasks.TaskTree{
121+
Parallel: false,
122+
IsSubTask: true,
123+
}
124+
piaDeletionTasks.Append(d.makeDeleteTask(ctx, p, roleStackNames))
125+
piaDeletionTasks.Append(&tasks.GenericTask{
126+
Description: fmt.Sprintf("delete service account %q", p.IDString()),
127+
Doer: func() error {
128+
if err := kubernetes.MaybeDeleteServiceAccount(d.ClientSet, v1.ObjectMeta{
129+
Name: p.ServiceAccountName,
130+
Namespace: p.Namespace,
131+
}); err != nil {
132+
return fmt.Errorf("failed to delete service account %q: %w", p.IDString(), err)
133+
}
134+
return nil
135+
},
136+
})
137+
taskTree.Append(piaDeletionTasks)
115138
}
116139
return taskTree, nil
117140
}

pkg/actions/podidentityassociation/deleter_test.go

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ import (
55
"crypto/sha1"
66
"fmt"
77

8-
cfntypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
9-
ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
8+
. "github.com/onsi/ginkgo/v2"
9+
. "github.com/onsi/gomega"
1010

11-
"github.com/aws/aws-sdk-go-v2/aws"
12-
"github.com/aws/aws-sdk-go-v2/service/eks"
1311
"github.com/stretchr/testify/mock"
1412

15-
. "github.com/onsi/ginkgo/v2"
16-
. "github.com/onsi/gomega"
13+
corev1 "k8s.io/api/core/v1"
14+
"k8s.io/apimachinery/pkg/runtime"
15+
kubeclientfakes "k8s.io/client-go/kubernetes/fake"
16+
kubeclienttesting "k8s.io/client-go/testing"
17+
18+
"github.com/aws/aws-sdk-go-v2/aws"
19+
cfntypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
20+
"github.com/aws/aws-sdk-go-v2/service/eks"
21+
ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
1722

1823
"github.com/weaveworks/eksctl/pkg/actions/podidentityassociation"
1924
api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
@@ -25,7 +30,7 @@ import (
2530
var _ = Describe("Pod Identity Deleter", func() {
2631
type deleteEntry struct {
2732
podIdentityAssociations []api.PodIdentityAssociation
28-
mockCalls func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS)
33+
mockCalls func(stackManager *managerfakes.FakeStackManager, clientSet *kubeclientfakes.Clientset, eksAPI *mocksv2.EKS)
2934

3035
expectedCalls func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS)
3136
expectedErr string
@@ -40,14 +45,23 @@ var _ = Describe("Pod Identity Deleter", func() {
4045
return nil
4146
}
4247
}
43-
mockCalls := func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS, podID podidentityassociation.Identifier) {
48+
mockClientSet := func(clientSet *kubeclientfakes.Clientset) {
49+
clientSet.PrependReactor("delete", "serviceaccounts", func(action kubeclienttesting.Action) (bool, runtime.Object, error) {
50+
return true, nil, nil
51+
})
52+
clientSet.PrependReactor("get", "serviceaccounts", func(action kubeclienttesting.Action) (bool, runtime.Object, error) {
53+
return true, &corev1.ServiceAccount{}, nil
54+
})
55+
}
56+
mockCalls := func(stackManager *managerfakes.FakeStackManager, clientSet *kubeclientfakes.Clientset, eksAPI *mocksv2.EKS, podID podidentityassociation.Identifier) {
4457
stackName := makeIRSAv2StackName(podID)
4558
associationID := fmt.Sprintf("%x", sha1.Sum([]byte(stackName)))
4659
mockListPodIdentityAssociations(eksAPI, podID, []ekstypes.PodIdentityAssociationSummary{
4760
{
4861
AssociationId: aws.String(associationID),
4962
},
5063
}, nil)
64+
mockClientSet(clientSet)
5165
eksAPI.On("DeletePodIdentityAssociation", mock.Anything, &eks.DeletePodIdentityAssociationInput{
5266
ClusterName: aws.String(clusterName),
5367
AssociationId: aws.String(associationID),
@@ -57,12 +71,14 @@ var _ = Describe("Pod Identity Deleter", func() {
5771

5872
DescribeTable("delete pod identity association", func(e deleteEntry) {
5973
provider := mockprovider.NewMockProvider()
74+
clientSet := kubeclientfakes.NewSimpleClientset()
6075
var stackManager managerfakes.FakeStackManager
61-
e.mockCalls(&stackManager, provider.MockEKS())
76+
e.mockCalls(&stackManager, clientSet, provider.MockEKS())
6277
deleter := podidentityassociation.Deleter{
6378
ClusterName: clusterName,
6479
StackDeleter: &stackManager,
6580
APIDeleter: provider.EKS(),
81+
ClientSet: clientSet,
6682
}
6783
err := deleter.Delete(context.Background(), podidentityassociation.ToIdentifiers(e.podIdentityAssociations))
6884

@@ -80,13 +96,13 @@ var _ = Describe("Pod Identity Deleter", func() {
8096
ServiceAccountName: "default",
8197
},
8298
},
83-
mockCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) {
99+
mockCalls: func(stackManager *managerfakes.FakeStackManager, fakeClientSet *kubeclientfakes.Clientset, eksAPI *mocksv2.EKS) {
84100
podID := podidentityassociation.Identifier{
85101
Namespace: "default",
86102
ServiceAccountName: "default",
87103
}
88104
mockListStackNames(stackManager, []podidentityassociation.Identifier{podID})
89-
mockCalls(stackManager, eksAPI, podID)
105+
mockCalls(stackManager, fakeClientSet, eksAPI, podID)
90106
},
91107

92108
expectedCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) {
@@ -107,7 +123,7 @@ var _ = Describe("Pod Identity Deleter", func() {
107123
ServiceAccountName: "aws-node",
108124
},
109125
},
110-
mockCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) {
126+
mockCalls: func(stackManager *managerfakes.FakeStackManager, clientSet *kubeclientfakes.Clientset, eksAPI *mocksv2.EKS) {
111127
podIDs := []podidentityassociation.Identifier{
112128
{
113129
Namespace: "default",
@@ -120,7 +136,7 @@ var _ = Describe("Pod Identity Deleter", func() {
120136
}
121137
mockListStackNamesWithIRSAv1(stackManager, podIDs[:1], podIDs[1:])
122138
for _, podID := range podIDs {
123-
mockCalls(stackManager, eksAPI, podID)
139+
mockCalls(stackManager, clientSet, eksAPI, podID)
124140
}
125141
},
126142

@@ -167,7 +183,7 @@ var _ = Describe("Pod Identity Deleter", func() {
167183
ServiceAccountName: "coredns",
168184
},
169185
},
170-
mockCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) {
186+
mockCalls: func(stackManager *managerfakes.FakeStackManager, clientSet *kubeclientfakes.Clientset, eksAPI *mocksv2.EKS) {
171187
podIDs := []podidentityassociation.Identifier{
172188
{
173189
Namespace: "default",
@@ -184,7 +200,7 @@ var _ = Describe("Pod Identity Deleter", func() {
184200
}
185201
mockListStackNames(stackManager, podIDs)
186202
for _, podID := range podIDs {
187-
mockCalls(stackManager, eksAPI, podID)
203+
mockCalls(stackManager, clientSet, eksAPI, podID)
188204
}
189205
mockListPodIdentityAssociations(eksAPI, podidentityassociation.Identifier{
190206
Namespace: "kube-system",
@@ -207,7 +223,7 @@ var _ = Describe("Pod Identity Deleter", func() {
207223
ServiceAccountName: "aws-node",
208224
},
209225
},
210-
mockCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) {
226+
mockCalls: func(stackManager *managerfakes.FakeStackManager, clientSet *kubeclientfakes.Clientset, eksAPI *mocksv2.EKS) {
211227
podID := podidentityassociation.Identifier{
212228
Namespace: "kube-system",
213229
ServiceAccountName: "aws-node",
@@ -236,7 +252,7 @@ var _ = Describe("Pod Identity Deleter", func() {
236252
ServiceAccountName: "aws-node",
237253
},
238254
},
239-
mockCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) {
255+
mockCalls: func(stackManager *managerfakes.FakeStackManager, clientSet *kubeclientfakes.Clientset, eksAPI *mocksv2.EKS) {
240256
podIDs := []podidentityassociation.Identifier{
241257
{
242258
Namespace: "default",
@@ -263,7 +279,7 @@ var _ = Describe("Pod Identity Deleter", func() {
263279

264280
Entry("delete IAM resources on cluster deletion", deleteEntry{
265281
podIdentityAssociations: []api.PodIdentityAssociation{},
266-
mockCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) {
282+
mockCalls: func(stackManager *managerfakes.FakeStackManager, clientSet *kubeclientfakes.Clientset, eksAPI *mocksv2.EKS) {
267283
podIDs := []podidentityassociation.Identifier{
268284
{
269285
Namespace: "default",

pkg/actions/podidentityassociation/migrator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func (m *Migrator) MigrateToPodIdentity(ctx context.Context, options PodIdentity
177177
}
178178

179179
// add tasks to create pod identity associations
180-
createAssociationsTasks := NewCreator(m.clusterName, nil, m.eksAPI).CreateTasks(ctx, toBeCreated)
180+
createAssociationsTasks := NewCreator(m.clusterName, nil, m.eksAPI, m.clientSet).CreateTasks(ctx, toBeCreated)
181181
if createAssociationsTasks.Len() > 0 {
182182
createAssociationsTasks.IsSubTask = true
183183
taskTree.Append(createAssociationsTasks)

pkg/actions/podidentityassociation/migrator_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ var _ = Describe("Create", func() {
220220
validateCustomLoggerOutput: func(output string) {
221221
Expect(output).To(ContainSubstring("update trust policy for owned role \"test-role-1\""))
222222
Expect(output).To(ContainSubstring("update trust policy for unowned role \"test-role-2\""))
223+
Expect(output).To(ContainSubstring("create service account \"default/service-account-1\", if it does not already exist"))
224+
Expect(output).To(ContainSubstring("create service account \"default/service-account-2\", if it does not already exist"))
223225
Expect(output).To(ContainSubstring("create pod identity association for service account \"default/service-account-1\""))
224226
Expect(output).To(ContainSubstring("create pod identity association for service account \"default/service-account-2\""))
225227
},

pkg/actions/podidentityassociation/podidentityassociation_suite_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ import (
99

1010
func TestPodIdentityAssociation(t *testing.T) {
1111
RegisterFailHandler(Fail)
12-
RunSpecs(t, "Nodegroup Suite")
12+
RunSpecs(t, "Pod Identity Association Suite")
1313
}

0 commit comments

Comments
 (0)