Repository: eksctl-io/eksctl
Pull request #8358: Fix for `create podidentityassociation` bug in auto-mode clusters
Author: login: dheeraj-coding
Labels: name: kind/bug
name: area/podidentity
Created: 2025-04-18T20:37:53Z | Merged: 2025-04-28T21:06:33Z

### Description

Added auto-mode checks for `IsPodIdentityAgentInstalled` to allow for creation of podidentityassociations in auto-mode clusters as pod-identity-agent comes in-built in auto-mode clusters and doesn't need explicit installs. 

Before:
`eksctl create podidentityassociation ...` in auto-mode cluster failed with the below errors
```
Error: the "eks-pod-identity-agent" addon must be installed to create pod identity associations; please enable it using `eksctl ....
```
but this is not expected behavior as auto-mode clusters come installed with pod-identity-agent and we should allow creation of pod-identity-associations. 

This PR:
Command successfully runs for auto-mode clusters and allows creation of pod-identity associations
```bash
2025-04-18 20:34:51 [ℹ]  1 task: {
    2 sequential sub-tasks: {
        create service account "default/eksctl-pr", if it does not already exist,
        create pod identity association for service account "default/eksctl-pr",
    } }2025-04-18 20:34:51 [ℹ]  created serviceaccount "default/eksctl-pr"
2025-04-18 20:34:51 [ℹ]  created pod identity association for service account "eksctl-pr" in namespace "default"
2025-04-18 20:34:51 [ℹ]  all tasks were completed successfully
```

Fixes #8346

Updated documentation to notify people that auto-mode clusters come pre-installed with pod-identity-agent

<img width="788" alt="Screenshot 2025-04-18 at 2 17 50 PM" src="https://github.com/user-attachments/assets/7ba02f18-f45d-4409-8121-ca586f9fd53a" />


### Checklist
- [ ] Added tests that cover your change (if possible)
- [&check;] Added/modified documentation as required (such as the `README.md`, or the `userdocs` directory)
- [&check;] Manually tested
- [&check;] Made sure the title of the PR is a good description that can go into the release notes
- [ ] (Core team) Added labels for change area (e.g. `area/nodegroup`) and kind (e.g. `kind/improvement`)

### BONUS POINTS checklist: complete for good vibes and maybe prizes?! :exploding_head:
- [ ] Backfilled missing tests for code in same general area :tada:
- [ ] Refactored something and made the world a better place :star2:



Linked issue #8346: [Bug] 'eksctl create podidentityassociation' does not work in the case of EKS auto-mode
eksctl does not check whether EKS is using auto-mode or not. In EKS auto-mode, there is no need to check whether the eks-pod-identity-agent is installed or not, because eks-pod-identity-agent is part of the EKS auto-mode.

<!--
Thank you for opening this bug report! You are helping make eksctl a better project :)
In order to help us process this issue faster, please provide as much detail as possible.

When providing command examples, error output or code samples, please use code blocks.
For verbose output, for example logs, please either create a gist or use html details tags.
-->

<!--
Before you start writing:
1. Please search through existing issues to see whether your problem has already been reported.
2. Download the latest version of eksctl to see whether the problem has already been fixed.

For more information on how to open a bug report in eksctl, please refer to our issue submission guide.
https://github.com/eksctl-io/eksctl/blob/main/CONTRIBUTING.md#opening-issues
-->

### What were you trying to accomplish?
I tried to create a new podidentityassociation.
```
eksctl create podidentityassociation  ..... 
```
<!-- Information about the command you ran and what you expected to happen. -->

### What happened?
```
Error: the "eks-pod-identity-agent" addon must be installed to create pod identity associations; please enable it using `eksctl ....
```

### How to reproduce it?
<!--
Include ALL the steps to reproduce the bug.

If using a config file, include it here, removing any sensitive information!
-->
Run again
```
eksctl create podidentityassociation  ..... 
```

**Logs**
<!--
Include the output of the command line when running eksctl. If possible, eksctl should be run with debug logs. For example:
`eksctl get clusters -v 4`
Make sure you redact any sensitive information before posting.
If the output is long, please consider a Gist, or an html details tag.
-->

**Anything else we need to know?**
<!--
What OS are you using?
Are you using a downloaded binary or did you compile eksctl?
What type of AWS credentials are you using (i.e. default/named profile, MFA)? - please don't include actual credentials though!
-->
The IsPodIdentityAgentInstalled function in the file pkg/actions/podidentityassociation/migrator.go does not check whether EKS is using auto-mode or not.

Created pull request https://github.com/eksctl-io/eksctl/pull/8345


**Versions**
<!--
Please paste in the output of this command:
-->
```
$ eksctl info

eksctl version: 0.207.0
kubectl version: v1.29.2
OS: linux
```


Linked issue comments: user: login: github-actions[bot]
created_at: 2025-04-04T08:19:54Z
body: Hello TarmoKople :wave: Thank you for opening an issue in `eksctl` project. The team will review the issue and aim to respond within 1-5 business days. Meanwhile, please read about the Contribution and Code of Conduct guidelines [here](https://github.com/eksctl-io/eksctl#contributions). You can find out more information about `eksctl` on our [website](https://eksctl.io)
author_association: CONTRIBUTOR
user: login: dheeraj-coding
created_at: 2025-04-16T18:46:23Z
body: So for eks auto-mode a new cluster can be created with auto-mode or it can be enabled for an existing cluster. as far as I understand the computeConfig flag [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-eks-cluster-computeconfig.html) tells whether a cluster has auto-mode enabled or not. if we do not have to rely on the eksctl [ClusterConfig](https://eksctl.io/usage/auto-mode/) then we can do a describe-cluster to get the flag to decide if it is in auto-mode or not. Otherwise if we can be sure that ClusterConfig will always have updated information then we do not need to make that describe-cluster call to determine if auto-mode is enabled or not. 
author_association: CONTRIBUTOR

Diff:
diff --git a/pkg/actions/podidentityassociation/migrator.go b/pkg/actions/podidentityassociation/migrator.go
index 02148d2fc8..9d4c1c7833 100644
--- a/pkg/actions/podidentityassociation/migrator.go
+++ b/pkg/actions/podidentityassociation/migrator.go
@@ -215,6 +215,11 @@ func (m *Migrator) MigrateToPodIdentity(ctx context.Context, options PodIdentity
 }
 
 func IsPodIdentityAgentInstalled(ctx context.Context, eksAPI awsapi.EKS, clusterName string) (bool, error) {
+
+	if autoMode, _ := IsAutoModeEnabled(ctx, eksAPI, clusterName); autoMode {
+		return true, nil
+	}
+
 	if _, err := eksAPI.DescribeAddon(ctx, &awseks.DescribeAddonInput{
 		AddonName:   aws.String(api.PodIdentityAgentAddon),
 		ClusterName: &clusterName,
@@ -228,6 +233,22 @@ func IsPodIdentityAgentInstalled(ctx context.Context, eksAPI awsapi.EKS, cluster
 	return true, nil
 }
 
+func IsAutoModeEnabled(ctx context.Context, eksAPI awsapi.EKS, clusterName string) (bool, error) {
+	clstrDescribeResponse, err := eksAPI.DescribeCluster(ctx, &awseks.DescribeClusterInput{
+		Name: aws.String(clusterName),
+	})
+
+	if err != nil {
+		return false, fmt.Errorf("calling EKS::DescribeCluster: %w", err)
+	}
+
+	if *clstrDescribeResponse.Cluster.ComputeConfig.Enabled {
+		return true, nil
+	}
+
+	return false, nil
+}
+
 type IRSAv1StackNameResolver map[string]IRSAv1StackSummary
 
 type IRSAv1StackSummary struct {
diff --git a/pkg/actions/podidentityassociation/migrator_test.go b/pkg/actions/podidentityassociation/migrator_test.go
index b1adf80e7a..a35391c5f5 100644
--- a/pkg/actions/podidentityassociation/migrator_test.go
+++ b/pkg/actions/podidentityassociation/migrator_test.go
@@ -66,11 +66,24 @@ var _ = Describe("Create", func() {
 		genericErr = fmt.Errorf("ERR")
 	)
 
-	mockDescribeAddon := func(provider *mockprovider.MockProvider, err error) {
+	mockDescribeAddon := func(provider *mockprovider.MockProvider, err error, autoMode bool) {
 		mockProvider.MockEKS().
-			On("DescribeAddon", mock.Anything, mock.Anything).
-			Return(nil, err).
+			On("DescribeCluster", mock.Anything, mock.Anything).
+			Return(&awseks.DescribeClusterOutput{
+				Cluster: &ekstypes.Cluster{
+					ComputeConfig: &ekstypes.ComputeConfigResponse{
+						Enabled: aws.Bool(autoMode),
+					},
+				},
+			}, nil).
 			Once()
+		if !autoMode {
+			mockProvider.MockEKS().
+				On("DescribeAddon", mock.Anything, mock.Anything).
+				Return(nil, err).
+				Once()
+
+		}
 	}
 
 	createFakeServiceAccount := func(clientSet *fake.Clientset, namespace, serviceAccountName, roleARN string) {
@@ -139,14 +152,14 @@ var _ = Describe("Create", func() {
 	},
 		Entry("[API errors] describing pod identity agent addon fails", migrateToPodIdentityAssociationEntry{
 			mockEKS: func(provider *mockprovider.MockProvider) {
-				mockDescribeAddon(provider, genericErr)
+				mockDescribeAddon(provider, genericErr, false)
 			},
 			expectedErr: fmt.Sprintf("calling %q", fmt.Sprintf("EKS::DescribeAddon::%s", api.PodIdentityAgentAddon)),
 		}),
 
 		Entry("[API errors] fetching iamserviceaccounts fails", migrateToPodIdentityAssociationEntry{
 			mockEKS: func(provider *mockprovider.MockProvider) {
-				mockDescribeAddon(provider, nil)
+				mockDescribeAddon(provider, nil, false)
 			},
 			mockCFN: func(stackUpdater *fakes.FakeStackUpdater) {
 				stackUpdater.GetIAMServiceAccountsReturns(nil, genericErr)
@@ -158,7 +171,7 @@ var _ = Describe("Create", func() {
 			mockEKS: func(provider *mockprovider.MockProvider) {
 				mockDescribeAddon(provider, &ekstypes.ResourceNotFoundException{
 					Message: aws.String(genericErr.Error()),
-				})
+				}, false)
 			},
 			mockCFN: func(stackUpdater *fakes.FakeStackUpdater) {
 				stackUpdater.GetIAMServiceAccountsReturns([]*api.ClusterIAMServiceAccount{}, nil)
@@ -173,7 +186,7 @@ var _ = Describe("Create", func() {
 
 		Entry("[taskTree] contains tasks to remove IRSAv1 EKS Role annotation if remove trust option is specified", migrateToPodIdentityAssociationEntry{
 			mockEKS: func(provider *mockprovider.MockProvider) {
-				mockDescribeAddon(provider, nil)
+				mockDescribeAddon(provider, nil, false)
 			},
 			mockCFN: func(stackUpdater *fakes.FakeStackUpdater) {
 				stackUpdater.GetIAMServiceAccountsReturns([]*api.ClusterIAMServiceAccount{}, nil)
@@ -191,7 +204,7 @@ var _ = Describe("Create", func() {
 
 		Entry("[taskTree] contains all other expected tasks", migrateToPodIdentityAssociationEntry{
 			mockEKS: func(provider *mockprovider.MockProvider) {
-				mockDescribeAddon(provider, nil)
+				mockDescribeAddon(provider, nil, false)
 			},
 			mockCFN: func(stackUpdater *fakes.FakeStackUpdater) {
 				stackUpdater.GetIAMServiceAccountsReturns([]*api.ClusterIAMServiceAccount{
@@ -220,7 +233,88 @@ var _ = Describe("Create", func() {
 
 		Entry("completes all tasks successfully", migrateToPodIdentityAssociationEntry{
 			mockEKS: func(provider *mockprovider.MockProvider) {
-				mockDescribeAddon(provider, nil)
+				mockDescribeAddon(provider, nil, false)
+
+				mockProvider.MockEKS().
+					On("CreatePodIdentityAssociation", mock.Anything, mock.Anything).
+					Run(func(args mock.Arguments) {
+						Expect(args).To(HaveLen(2))
+						Expect(args[1]).To(BeAssignableToTypeOf(&awseks.CreatePodIdentityAssociationInput{}))
+					}).
+					Return(nil, nil).
+					Twice()
+
+				mockProvider.MockIAM().
+					On("GetRole", mock.Anything, mock.Anything).
+					Return(&awsiam.GetRoleOutput{
+						Role: &iamtypes.Role{
+							AssumeRolePolicyDocument: policyDocument,
+						},
+					}, nil).
+					Twice()
+
+				mockProvider.MockIAM().
+					On("UpdateAssumeRolePolicy", mock.Anything, mock.Anything).
+					Run(func(args mock.Arguments) {
+						Expect(args).To(HaveLen(2))
+						Expect(args[1]).To(BeAssignableToTypeOf(&awsiam.UpdateAssumeRolePolicyInput{}))
+						input := args[1].(*awsiam.UpdateAssumeRolePolicyInput)
+
+						var trustPolicy api.IAMPolicyDocument
+						Expect(json.Unmarshal([]byte(*input.PolicyDocument), &trustPolicy)).NotTo(HaveOccurred())
+						Expect(trustPolicy.Statements).To(HaveLen(1))
+						value, exists := trustPolicy.Statements[0].Principal["Service"]
+						Expect(exists).To(BeTrue())
+						Expect(value).To(ConsistOf([]string{api.EKSServicePrincipal}))
+					}).
+					Return(nil, nil).
+					Once()
+			},
+			mockCFN: func(stackUpdater *fakes.FakeStackUpdater) {
+				stackUpdater.GetIAMServiceAccountsReturns([]*api.ClusterIAMServiceAccount{
+					{
+						Status: &api.ClusterIAMServiceAccountStatus{
+							RoleARN: aws.String(roleARN1),
+							StackName: aws.String(makeIRSAv1StackName(podidentityassociation.Identifier{
+								Namespace:          nsDefault,
+								ServiceAccountName: sa1,
+							})),
+							Capabilities: []string{"CAPABILITY_IAM"},
+						},
+					},
+				}, nil)
+
+				stackUpdater.GetStackTemplateReturnsOnCall(0, iamRoleStackTemplate(nsDefault, sa1), nil)
+				stackUpdater.GetStackTemplateReturnsOnCall(1, iamRoleStackTemplate(nsDefault, sa2), nil)
+
+				stackUpdater.MustUpdateStackStub = func(ctx context.Context, options manager.UpdateStackOptions) error {
+					Expect(options.Stack).NotTo(BeNil())
+					Expect(options.Stack.Tags).To(ConsistOf([]cfntypes.Tag{
+						{
+							Key:   aws.String(api.PodIdentityAssociationNameTag),
+							Value: aws.String(nsDefault + "/" + sa1),
+						},
+					}))
+					Expect(options.Stack.Capabilities).To(ConsistOf([]cfntypes.Capability{"CAPABILITY_IAM"}))
+					template := string(options.TemplateData.(manager.TemplateBody))
+					Expect(template).To(ContainSubstring(api.EKSServicePrincipal))
+					Expect(template).NotTo(ContainSubstring("oidc"))
+					return nil
+				}
+			},
+			mockK8s: func(clientSet *fake.Clientset) {
+				createFakeServiceAccount(clientSet, nsDefault, sa1, roleARN1)
+				createFakeServiceAccount(clientSet, nsDefault, sa2, roleARN2)
+			},
+			options: podidentityassociation.PodIdentityMigrationOptions{
+				RemoveOIDCProviderTrustRelationship: true,
+				Approve:                             true,
+			},
+		}),
+
+		Entry("completes all tasks successfully for auto-mode", migrateToPodIdentityAssociationEntry{
+			mockEKS: func(provider *mockprovider.MockProvider) {
+				mockDescribeAddon(provider, nil, true)
 
 				mockProvider.MockEKS().
 					On("CreatePodIdentityAssociation", mock.Anything, mock.Anything).
diff --git a/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go b/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go
index a7f4cf7a23..72f8c7df1c 100644
--- a/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go
+++ b/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go
@@ -264,6 +264,7 @@ func (in *AutoModeConfig) DeepCopyInto(out *AutoModeConfig) {
 		**out = **in
 	}
 	out.NodeRoleARN = in.NodeRoleARN
+	out.PermissionsBoundaryARN = in.PermissionsBoundaryARN
 	if in.NodePools != nil {
 		in, out := &in.NodePools, &out.NodePools
 		*out = new([]string)
@@ -788,6 +789,11 @@ func (in *ClusterIAMServiceAccountStatus) DeepCopy() *ClusterIAMServiceAccountSt
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ClusterMeta) DeepCopyInto(out *ClusterMeta) {
 	*out = *in
+	if in.ForceUpdateVersion != nil {
+		in, out := &in.ForceUpdateVersion, &out.ForceUpdateVersion
+		*out = new(bool)
+		**out = **in
+	}
 	if in.Tags != nil {
 		in, out := &in.Tags, &out.Tags
 		*out = make(map[string]string, len(*in))
@@ -1544,6 +1550,11 @@ func (in *NodeGroup) DeepCopyInto(out *NodeGroup) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.EnclaveEnabled != nil {
+		in, out := &in.EnclaveEnabled, &out.EnclaveEnabled
+		*out = new(bool)
+		**out = **in
+	}
 	return
 }
 
diff --git a/pkg/goformation/cloudformation/types/types.go b/pkg/goformation/cloudformation/types/types.go
index e8738f2467..af8bbf2dc3 100644
--- a/pkg/goformation/cloudformation/types/types.go
+++ b/pkg/goformation/cloudformation/types/types.go
@@ -32,7 +32,7 @@ func NewValueFromPrimitive(raw interface{}) (*Value, error) {
 	case json.Number:
 		i, err := p.Int64()
 		if err == nil {
-			if i <= int64(^uint(0) >> 1) {
+			if i <= int64(^uint(0)>>1) {
 				return NewInteger(int(i)), nil
 			}
 			return NewLong(i), nil
diff --git a/userdocs/src/usage/pod-identity-associations.md b/userdocs/src/usage/pod-identity-associations.md
index 014019af62..f4645e5fe7 100644
--- a/userdocs/src/usage/pod-identity-associations.md
+++ b/userdocs/src/usage/pod-identity-associations.md
@@ -10,6 +10,9 @@ As a result, IAM roles no longer need to reference an [OIDC provider](/usage/iam
 
 Behind the scenes, the implementation of pod identity associations is running an agent as a daemonset on the worker nodes. To run the pre-requisite agent on the cluster, EKS provides a new add-on called EKS Pod Identity Agent. Therefore, creating pod identity associations (in general, and with `eksctl`) requires the `eks-pod-identity-agent` addon pre-installed on the cluster. This addon can be [created using `eksctl`](/usage/addons/#creating-addons) in the same fashion any other supported addon is, e.g.
 
+???+ note
+    If you are using [EKS Auto Mode](https://eksctl.io/usage/auto-mode/) cluster the `eks-pod-identity-agent` comes pre-installed and you can skip creating the addon.
+
 ```
 eksctl create addon --cluster my-cluster --name eks-pod-identity-agent
 ```


Reviews:
user: login: github-actions[bot]
body: Hello dheeraj-coding :wave: Thank you for opening a Pull Request in `eksctl` project. The team will review the Pull Request and aim to respond within 1-10 business days. Meanwhile, please read about the Contribution and Code of Conduct guidelines [here](https://github.com/eksctl-io/eksctl#contributions). You can find out more information about `eksctl` on our [website](https://eksctl.io)
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T20:39:07Z
user: login: naclonts
body: Thanks for the PR @dheeraj-coding, this looks great! 

I left a few small suggestions/questions. It looks like the linter failed but just because go mod tidy needs to be ran:

```
go mod tidy && git commit go.mod go.sum --message "go mod tidy"
```
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T21:51:15Z
user: login: naclonts
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-28T21:06:12Z

Review comments:
diff_hunk: @@ -10,6 +10,9 @@ As a result, IAM roles no longer need to reference an [OIDC provider](/usage/iam
 
 Behind the scenes, the implementation of pod identity associations is running an agent as a daemonset on the worker nodes. To run the pre-requisite agent on the cluster, EKS provides a new add-on called EKS Pod Identity Agent. Therefore, creating pod identity associations (in general, and with `eksctl`) requires the `eks-pod-identity-agent` addon pre-installed on the cluster. This addon can be [created using `eksctl`](/usage/addons/#creating-addons) in the same fashion any other supported addon is, e.g.
 
+???+ note
+    If you are using [EKS Auto Mode](https://eksctl.io/usage/auto-mode/) cluster the `eks-pod-identity-agent` comes pre-installed and you can skip creating the addon.
+
path: userdocs/src/usage/pod-identity-associations.md
user: login: naclonts
body: Looks like this may not work with Github's markdown format (screenshot of preview in Github PR). 

Also, should we put the note below the command on line 17? It's a little hard to see the connection between the paragraph and the CLI command with the note in between.

![image](https://github.com/user-attachments/assets/a45a6c0d-9507-496b-be4a-c9f71d066196)

created_at: 2025-04-18T21:38:04Z
author_association: MEMBER
diff_hunk: @@ -228,6 +233,22 @@ func IsPodIdentityAgentInstalled(ctx context.Context, eksAPI awsapi.EKS, cluster
 	return true, nil
 }
 
+func IsAutoModeEnabled(ctx context.Context, eksAPI awsapi.EKS, clusterName string) (bool, error) {
+	clstrDescribeResponse, err := eksAPI.DescribeCluster(ctx, &awseks.DescribeClusterInput{
+		Name: aws.String(clusterName),
+	})
+
+	if err != nil {
+		return false, fmt.Errorf("calling EKS::DescribeCluster: %w", err)
+	}
+
+	if *clstrDescribeResponse.Cluster.ComputeConfig.Enabled {
path: pkg/actions/podidentityassociation/migrator.go
user: login: naclonts
body: Nit:
```suggestion
	clusterDescribeResponse, err := eksAPI.DescribeCluster(ctx, &awseks.DescribeClusterInput{
		Name: aws.String(clusterName),
	})

	if err != nil {
		return false, fmt.Errorf("calling EKS::DescribeCluster: %w", err)
	}

	if *clusterDescribeResponse.Cluster.ComputeConfig.Enabled {
```
created_at: 2025-04-18T21:39:41Z
author_association: MEMBER
diff_hunk: @@ -67,6 +67,16 @@ var _ = Describe("Create", func() {
 	)
 
 	mockDescribeAddon := func(provider *mockprovider.MockProvider, err error) {
+		mockProvider.MockEKS().
path: pkg/actions/podidentityassociation/migrator_test.go
user: login: naclonts
body: Thanks for updating the test here! Do you think it makes sense to add a test case to ensure `IsPodIdentityAgentInstalled` returns true when auto-mode is enabled? 
created_at: 2025-04-18T21:45:49Z
author_association: MEMBER

PR comments:
user: login: dheeraj-coding
created_at: 2025-04-23T17:07:01Z
body: I have addressed most of the comments, but facing some build errors after merging from main and it is taking some time to fix. Hopefully I can come around soon to fix that. 
author_association: CONTRIBUTOR
user: login: dheeraj-coding
created_at: 2025-04-25T05:04:34Z
body: I am not sure why unit-tests are failing locally for managed node groups. It is comparing some json files in ordered fashion but somehow the generated json files don't match the testdata. But I see current builds are all succeeding. I am not able to see the unit-tests are run or not in this PR. How can I check that? 
author_association: CONTRIBUTOR
user: login: dheeraj-coding
created_at: 2025-04-28T17:43:50Z
body: ok looks like unit-tests in CI/CD do pass probably because they are caching. Can you take a look again? I haven't changed the notes section as I just replicated it from other notes section used already in the docs and they don't render either in md. 
author_association: CONTRIBUTOR
user: login: naclonts
created_at: 2025-04-28T21:06:04Z
body: > ok looks like unit-tests in CI/CD do pass probably because they are caching. Can you take a look again? 

I think it looks all good, I notice the Karpenter tests fail locally for me (even on main) but work fine in CI, so I think that's a separate issue we need to fix for local development. 

Seems to be working well, so I'll go ahead and merge. Thanks for the contribution @dheeraj-coding! 
author_association: MEMBER