From 7b3f3e5b8fdb412856f0b1cee18cd37a983ffaae Mon Sep 17 00:00:00 2001 From: cs-mehta Date: Fri, 24 Apr 2026 01:57:32 +0530 Subject: [PATCH] fix: populate role ARN from stack outputs on NoChangeError IAMRoleUpdater.Update returns an empty roleArn to the caller when CFN has nothing to update and the config uses roleName (no roleARN). Pull the ARN from the existing stack outputs instead. --- .../iam_role_updater.go | 3 + .../podidentityassociation/updater_test.go | 78 ++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/pkg/actions/podidentityassociation/iam_role_updater.go b/pkg/actions/podidentityassociation/iam_role_updater.go index 36cc448718..93f9796140 100644 --- a/pkg/actions/podidentityassociation/iam_role_updater.go +++ b/pkg/actions/podidentityassociation/iam_role_updater.go @@ -52,6 +52,9 @@ func (u *IAMRoleUpdater) Update(ctx context.Context, podIdentityAssociation api. var noChangeErr *manager.NoChangeError if errors.As(err, &noChangeErr) { logger.Info("IAM resources for %s (pod identity association ID: %s) are already up-to-date", podIdentityAssociation.NameString(), podIdentityAssociationID) + if err := populateRoleARN(rs, stack); err != nil { + return "", false, err + } return podIdentityAssociation.RoleARN, false, nil } return "", false, fmt.Errorf("updating IAM resources for pod identity association: %w", err) diff --git a/pkg/actions/podidentityassociation/updater_test.go b/pkg/actions/podidentityassociation/updater_test.go index 6b7865f5bc..df62097f66 100644 --- a/pkg/actions/podidentityassociation/updater_test.go +++ b/pkg/actions/podidentityassociation/updater_test.go @@ -433,13 +433,21 @@ var _ = Describe("Pod Identity Update", func() { ServiceAccountName: "aws-node", }, } + describeStackOutputs := []cfntypes.Output{ + { + OutputKey: aws.String(outputs.IAMServiceAccountRoleName), + OutputValue: aws.String("arn:aws:iam::1234567:role/Role"), + }, + } mockListStackNames(stackManager, podIdentifiers) for _, options := range []mockOptions{ { - podIdentifier: podIdentifiers[0], + podIdentifier: podIdentifiers[0], + describeStackOutputs: describeStackOutputs, }, { - podIdentifier: podIdentifiers[1], + podIdentifier: podIdentifiers[1], + describeStackOutputs: describeStackOutputs, }, } { mockCalls(stackManager, eksAPI, options) @@ -458,6 +466,72 @@ var _ = Describe("Pod Identity Update", func() { }, }), + Entry("IAM no changes but disableSessionTags triggers EKS update with roleName resolving ARN from stack", updateEntry{ + podIdentityAssociations: []api.PodIdentityAssociation{ + { + Namespace: "default", + ServiceAccountName: "default", + RoleName: "my-custom-role", + DisableSessionTags: aws.Bool(true), + }, + }, + mockCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) { + podIdentifier := podidentityassociation.Identifier{ + Namespace: "default", + ServiceAccountName: "default", + } + mockListStackNames(stackManager, []podidentityassociation.Identifier{podIdentifier}) + + stackName := makeIRSAv2StackName(podIdentifier) + associationID := fmt.Sprintf("%x", sha1.Sum([]byte(stackName))) + resolvedRoleARN := "arn:aws:iam::1234567:role/my-custom-role" + + mockListPodIdentityAssociations(eksAPI, podIdentifier, []ekstypes.PodIdentityAssociationSummary{ + { + AssociationId: aws.String(associationID), + }, + }, nil) + eksAPI.On("DescribePodIdentityAssociation", mock.Anything, &eks.DescribePodIdentityAssociationInput{ + AssociationId: aws.String(associationID), + ClusterName: aws.String(clusterName), + }).Return(&eks.DescribePodIdentityAssociationOutput{ + Association: &ekstypes.PodIdentityAssociation{ + AssociationId: aws.String(associationID), + RoleArn: aws.String(resolvedRoleARN), + }, + }, nil) + + stackManager.DescribeStackReturns(&cfntypes.Stack{ + StackName: aws.String(stackName), + Outputs: []cfntypes.Output{ + { + OutputKey: aws.String(outputs.IAMServiceAccountRoleName), + OutputValue: aws.String(resolvedRoleARN), + }, + }, + Capabilities: []cfntypes.Capability{cfntypes.CapabilityCapabilityIam, cfntypes.CapabilityCapabilityNamedIam}, + }, nil) + + stackManager.MustUpdateStackReturns(&manager.NoChangeError{ + Msg: "no changes found", + }) + + eksAPI.On("UpdatePodIdentityAssociation", mock.Anything, &eks.UpdatePodIdentityAssociationInput{ + AssociationId: aws.String(associationID), + ClusterName: aws.String(clusterName), + RoleArn: aws.String(resolvedRoleARN), + DisableSessionTags: aws.Bool(true), + }).Return(&eks.UpdatePodIdentityAssociationOutput{}, nil) + }, + + expectedCalls: func(stackManager *managerfakes.FakeStackManager, eksAPI *mocksv2.EKS) { + Expect(stackManager.ListPodIdentityStackNamesCallCount()).To(Equal(1)) + Expect(stackManager.DescribeStackCallCount()).To(Equal(1)) + Expect(stackManager.MustUpdateStackCallCount()).To(Equal(1)) + eksAPI.AssertExpectations(GinkgoT()) + }, + }), + Entry("update pod identity association with cross-account access", updateEntry{ podIdentityAssociations: []api.PodIdentityAssociation{ {