From ed77fa02930111a2fc40edf0dabe7b537cd493e4 Mon Sep 17 00:00:00 2001 From: Stefan McShane Date: Fri, 19 Apr 2024 17:22:16 +0100 Subject: [PATCH 1/8] Add eks pod identities --- ...ster.x-k8s.io_awsmanagedcontrolplanes.yaml | 29 +++ controlplane/eks/api/v1beta1/conversion.go | 2 + .../api/v1beta1/zz_generated.conversion.go | 1 + .../v1beta2/awsmanagedcontrolplane_types.go | 5 + .../v1beta2/awsmanagedcontrolplane_webhook.go | 25 +++ .../eks/api/v1beta2/conditions_consts.go | 7 + controlplane/eks/api/v1beta2/types.go | 24 ++- .../eks/api/v1beta2/zz_generated.deepcopy.go | 20 ++ .../awsmanagedcontrolplane_controller_test.go | 11 +- docs/book/src/SUMMARY_PREFIX.md | 1 + .../topics/eks/pod-identity-associations.md | 89 +++++++++ pkg/cloud/services/eks/addons.go | 2 +- pkg/cloud/services/eks/eks.go | 7 + pkg/cloud/services/eks/pod_identity.go | 119 ++++++++++++ pkg/eks/podidentities/plan.go | 102 ++++++++++ pkg/eks/podidentities/plan_test.go | 177 ++++++++++++++++++ pkg/eks/podidentities/procedures.go | 89 +++++++++ pkg/eks/podidentities/types.go | 48 +++++ 18 files changed, 749 insertions(+), 9 deletions(-) create mode 100644 docs/book/src/topics/eks/pod-identity-associations.md create mode 100644 pkg/cloud/services/eks/pod_identity.go create mode 100644 pkg/eks/podidentities/plan.go create mode 100644 pkg/eks/podidentities/plan_test.go create mode 100644 pkg/eks/podidentities/procedures.go create mode 100644 pkg/eks/podidentities/types.go diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml index 29cc567267..551af9e8e6 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml @@ -3083,6 +3083,35 @@ spec: description: Partition is the AWS security partition being used. Defaults to "aws" type: string + podIdentityAssociations: + description: |- + PodIdentityAssociations represent Kubernetes Service Accounts mapping to AWS IAM Roles without IRSA, using EKS Pod Identity. + This requires using the AWS EKS Addon for Pod Identity. + items: + description: PodIdentityAssociation represents an association between + a Kubernetes Service Account in a namespace, and an AWS IAM role + which allows the service principal `pods.eks.amazonaws.com` in + its trust policy. + properties: + serviceAccountName: + description: ServiceAccountName is the name of the kubernetes + Service Account within the namespace + type: string + serviceAccountNamespace: + default: default + description: ServiceAccountNamespace is the kubernetes namespace, + which the kubernetes Service Account resides in. Defaults + to "default" namespace. + type: string + serviceAccountRoleARN: + description: RoleARN is the ARN of an IAM role which the Service + Account can assume. + type: string + required: + - serviceAccountName + - serviceAccountNamespace + type: object + type: array region: description: The AWS Region the cluster lives in. type: string diff --git a/controlplane/eks/api/v1beta1/conversion.go b/controlplane/eks/api/v1beta1/conversion.go index 9a0c2720c6..5e00658dd5 100644 --- a/controlplane/eks/api/v1beta1/conversion.go +++ b/controlplane/eks/api/v1beta1/conversion.go @@ -46,6 +46,8 @@ func (r *AWSManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.RolePermissionsBoundary = restored.Spec.RolePermissionsBoundary dst.Status.Version = restored.Status.Version dst.Spec.BootstrapSelfManagedAddons = restored.Spec.BootstrapSelfManagedAddons + dst.Spec.PodIdentityAssociations = restored.Spec.PodIdentityAssociations + return nil } diff --git a/controlplane/eks/api/v1beta1/zz_generated.conversion.go b/controlplane/eks/api/v1beta1/zz_generated.conversion.go index b7bb9b0a6f..145c50157f 100644 --- a/controlplane/eks/api/v1beta1/zz_generated.conversion.go +++ b/controlplane/eks/api/v1beta1/zz_generated.conversion.go @@ -374,6 +374,7 @@ func autoConvert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControl out.TokenMethod = (*EKSTokenMethod)(unsafe.Pointer(in.TokenMethod)) out.AssociateOIDCProvider = in.AssociateOIDCProvider out.Addons = (*[]Addon)(unsafe.Pointer(in.Addons)) + // WARNING: in.PodIdentityAssociations requires manual conversion: does not exist in peer-type out.OIDCIdentityProviderConfig = (*OIDCIdentityProviderConfig)(unsafe.Pointer(in.OIDCIdentityProviderConfig)) if err := Convert_v1beta2_VpcCni_To_v1beta1_VpcCni(&in.VpcCni, &out.VpcCni, s); err != nil { return err diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go index 4f4c559b81..ccf99edba1 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go @@ -188,6 +188,11 @@ type AWSManagedControlPlaneSpec struct { //nolint: maligned // +optional Addons *[]Addon `json:"addons,omitempty"` + // PodIdentityAssociations represent Kubernetes Service Accounts mapping to AWS IAM Roles without IRSA, using EKS Pod Identity. + // This requires using the AWS EKS Addon for Pod Identity. + // +optional + PodIdentityAssociations []PodIdentityAssociation `json:"podIdentityAssociations,omitempty"` + // IdentityProviderconfig is used to specify the oidc provider config // to be attached with this eks cluster // +optional diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go index 8970b29cd7..2b7bd7e986 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go @@ -25,6 +25,7 @@ import ( "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/version" "k8s.io/klog/v2" @@ -107,6 +108,7 @@ func (*awsManagedControlPlaneWebhook) ValidateCreate(_ context.Context, obj runt allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...) allErrs = append(allErrs, r.validateNetwork()...) allErrs = append(allErrs, r.validatePrivateDNSHostnameTypeOnLaunch()...) + allErrs = append(allErrs, r.validServiceAccountName()...) if len(allErrs) == 0 { return nil, nil @@ -148,6 +150,7 @@ func (*awsManagedControlPlaneWebhook) ValidateUpdate(ctx context.Context, oldObj allErrs = append(allErrs, r.validateKubeProxy()...) allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...) allErrs = append(allErrs, r.validatePrivateDNSHostnameTypeOnLaunch()...) + allErrs = append(allErrs, r.validServiceAccountName()...) if r.Spec.Region != oldAWSManagedControlplane.Spec.Region { allErrs = append(allErrs, @@ -436,6 +439,28 @@ func (r *AWSManagedControlPlane) validatePrivateDNSHostnameTypeOnLaunch() field. return allErrs } +func (r *AWSManagedControlPlane) validServiceAccountName() field.ErrorList { + var allErrs field.ErrorList + + if r.Spec.PodIdentityAssociations != nil { + for i, association := range r.Spec.PodIdentityAssociations { + associationPath := field.NewPath("spec", "podIdentityAssociations").Index(i) + if association.ServiceAccountName == "" { + allErrs = append(allErrs, field.Required(associationPath.Child("serviceAccountName"), "serviceAccountName is required")) + } + + // kubernetes uses ValidateServiceAccountName internally, which maps to IsDNS1123Subdomain + // https://github.com/kubernetes/apimachinery/blob/d794766488ac2892197a7cc8d0b4b46b0edbda80/pkg/api/validation/generic.go#L68 + + if validationErrs := validation.IsDNS1123Subdomain(association.ServiceAccountName); len(validationErrs) > 0 { + allErrs = append(allErrs, field.Invalid(associationPath.Child("serviceAccountName"), association.ServiceAccountName, fmt.Sprintf("serviceAccountName is invalid: %v", validationErrs))) + } + } + } + + return allErrs +} + func (r *AWSManagedControlPlane) validateNetwork() field.ErrorList { var allErrs field.ErrorList diff --git a/controlplane/eks/api/v1beta2/conditions_consts.go b/controlplane/eks/api/v1beta2/conditions_consts.go index fc8fa66721..563ac525a5 100644 --- a/controlplane/eks/api/v1beta2/conditions_consts.go +++ b/controlplane/eks/api/v1beta2/conditions_consts.go @@ -52,6 +52,13 @@ const ( EKSAddonsConfiguredFailedReason = "EKSAddonsConfiguredFailed" ) +const ( + // EKSPodIdentityAssociationConfiguredCondition condition reports on the successful reconciliation of EKS pod identity associations. + EKSPodIdentityAssociationConfiguredCondition clusterv1.ConditionType = "EKSPodIdentityAssociationConfigured" + // EKSPodIdentityAssociationFailedReason is used to report failures while reconciling the EKS pod identity associations. + EKSPodIdentityAssociationFailedReason = "EKSPodIdentityAssociationConfigurationFailed" +) + const ( // EKSIdentityProviderConfiguredCondition condition reports on the successful association of identity provider config. EKSIdentityProviderConfiguredCondition clusterv1.ConditionType = "EKSIdentityProviderConfigured" diff --git a/controlplane/eks/api/v1beta2/types.go b/controlplane/eks/api/v1beta2/types.go index c740f868f1..90d73ccb87 100644 --- a/controlplane/eks/api/v1beta2/types.go +++ b/controlplane/eks/api/v1beta2/types.go @@ -79,12 +79,10 @@ var ( EKSTokenMethodAWSCli = EKSTokenMethod("aws-cli") ) -var ( - // DefaultEKSControlPlaneRole is the name of the default IAM role to use for the EKS control plane - // if no other role is supplied in the spec and if iam role creation is not enabled. The default - // can be created using clusterawsadm or created manually. - DefaultEKSControlPlaneRole = fmt.Sprintf("eks-controlplane%s", iamv1.DefaultNameSuffix) -) +// DefaultEKSControlPlaneRole is the name of the default IAM role to use for the EKS control plane +// if no other role is supplied in the spec and if iam role creation is not enabled. The default +// can be created using clusterawsadm or created manually. +var DefaultEKSControlPlaneRole = fmt.Sprintf("eks-controlplane%s", iamv1.DefaultNameSuffix) // IAMAuthenticatorConfig represents an aws-iam-authenticator configuration. type IAMAuthenticatorConfig struct { @@ -279,3 +277,17 @@ type OIDCIdentityProviderConfig struct { // +optional Tags infrav1.Tags `json:"tags,omitempty"` } + +// PodIdentityAssociation represents an association between a Kubernetes Service Account in a namespace, and an AWS IAM role which allows the service principal `pods.eks.amazonaws.com` in its trust policy. +type PodIdentityAssociation struct { + // ServiceAccountName is the name of the kubernetes Service Account within the namespace + // +kubebuilder:validation:Required + ServiceAccountName string `json:"serviceAccountName"` + // ServiceAccountNamespace is the kubernetes namespace, which the kubernetes Service Account resides in. Defaults to "default" namespace. + // +kubebuilder:validation:Required + // +kubebuilder:default=default + ServiceAccountNamespace string `json:"serviceAccountNamespace"` + // RoleARN is the ARN of an IAM role which the Service Account can assume. + // +kubebuilder:validation:Required + RoleARN string `json:"serviceAccountRoleARN,omitempty"` +} diff --git a/controlplane/eks/api/v1beta2/zz_generated.deepcopy.go b/controlplane/eks/api/v1beta2/zz_generated.deepcopy.go index 216357e2fe..973b636144 100644 --- a/controlplane/eks/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/eks/api/v1beta2/zz_generated.deepcopy.go @@ -165,6 +165,11 @@ func (in *AWSManagedControlPlaneSpec) DeepCopyInto(out *AWSManagedControlPlaneSp } } } + if in.PodIdentityAssociations != nil { + in, out := &in.PodIdentityAssociations, &out.PodIdentityAssociations + *out = make([]PodIdentityAssociation, len(*in)) + copy(*out, *in) + } if in.OIDCIdentityProviderConfig != nil { in, out := &in.OIDCIdentityProviderConfig, &out.OIDCIdentityProviderConfig *out = new(OIDCIdentityProviderConfig) @@ -557,6 +562,21 @@ func (in *OIDCProviderStatus) DeepCopy() *OIDCProviderStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodIdentityAssociation) DeepCopyInto(out *PodIdentityAssociation) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodIdentityAssociation. +func (in *PodIdentityAssociation) DeepCopy() *PodIdentityAssociation { + if in == nil { + return nil + } + out := new(PodIdentityAssociation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RoleMapping) DeepCopyInto(out *RoleMapping) { *out = *in diff --git a/controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go b/controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go index aaa679e3f0..594048881b 100644 --- a/controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go +++ b/controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go @@ -454,7 +454,8 @@ func mockedCallsForMissingEverything(ec2Rec *mocks.MockEC2APIMockRecorder, subne Name: aws.String("tag-key"), Values: aws.StringSlice([]string{"sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"}), }, - }})).Return(&ec2.DescribeRouteTablesOutput{ + }, + })).Return(&ec2.DescribeRouteTablesOutput{ RouteTables: []*ec2.RouteTable{ { Routes: []*ec2.Route{ @@ -514,7 +515,8 @@ func mockedCallsForMissingEverything(ec2Rec *mocks.MockEC2APIMockRecorder, subne Name: aws.String("state"), Values: aws.StringSlice([]string{ec2.VpcStatePending, ec2.VpcStateAvailable}), }, - }}), gomock.Any()).Return(nil).MinTimes(1).MaxTimes(2) + }, + }), gomock.Any()).Return(nil).MinTimes(1).MaxTimes(2) ec2Rec.DescribeAddressesWithContext(context.TODO(), gomock.Eq(&ec2.DescribeAddressesInput{ Filters: []*ec2.Filter{ @@ -949,6 +951,11 @@ func mockedEKSCluster(ctx context.Context, g *WithT, eksRec *mock_eksiface.MockE }).Return(&eks.ListAddonsOutput{}, nil) eksRec.UpdateClusterConfig(ctx, gomock.AssignableToTypeOf(&eks.UpdateClusterConfigInput{})).After(waitUntilClusterActiveCall).Return(&eks.UpdateClusterConfigOutput{}, nil) + eksRec.ListPodIdentityAssociationsWqithContext(context.TODO(), gomock.Eq(&eks.ListPodIdentityAssociationsInput{ + ClusterName: aws.String("test-cluster"), + })).Return(&eks.ListPodIdentityAssociationsOutput{ + Associations: []*eks.PodIdentityAssociationSummary{}, + }, nil) awsNodeRec.ReconcileCNI(gomock.Any()).Return(nil) kubeProxyRec.ReconcileKubeProxy(gomock.Any()).Return(nil) diff --git a/docs/book/src/SUMMARY_PREFIX.md b/docs/book/src/SUMMARY_PREFIX.md index de1756f422..3a5dccc68e 100644 --- a/docs/book/src/SUMMARY_PREFIX.md +++ b/docs/book/src/SUMMARY_PREFIX.md @@ -20,6 +20,7 @@ - [Creating a cluster](./topics/eks/creating-a-cluster.md) - [Using EKS Console](./topics/eks/eks-console.md) - [Using EKS Addons](./topics/eks/addons.md) + - [Using EKS Pod Identity Associations](./topics/eks/pod-identity-associations.md) - [Enabling Encryption](./topics/eks/encryption.md) - [Cluster Upgrades](./topics/eks/cluster-upgrades.md) - [ROSA Support](./topics/rosa/index.md) diff --git a/docs/book/src/topics/eks/pod-identity-associations.md b/docs/book/src/topics/eks/pod-identity-associations.md new file mode 100644 index 0000000000..0f1a6b3e0e --- /dev/null +++ b/docs/book/src/topics/eks/pod-identity-associations.md @@ -0,0 +1,89 @@ +# EKS Pod Identity Associations + +[EKS Pod Identity Associations](https://aws.amazon.com/blogs/containers/introducing-amazon-eks-add-ons/) can be used with EKS clusters created using Cluster API Provider AWS. + +## Prerequisites + +### Setting up the IAM role in AWS + +Outside of CAPI/CAPA, you must first create an IAM Role which allows the `pods.eks.amazonaws.com` service principal in the trust policy. EKS Identities trust relationships must also include the `sts:TagSession` permission (on top of the `sts:AssumeRole` permission). + +This is a sample trust policy which allows a kubernetes service account to assume this role. We'll call the role `capi-test-role` in the next steps. + +```yaml +{ + "Version": "2012-10-17", + "Statement": + [ + { + "Effect": "Allow", + "Principal": { "Service": "pods.eks.amazonaws.com" }, + "Action": ["sts:AssumeRole", "sts:TagSession"], + }, + ], +} +``` + +### Installing the EKS Pod Identity Agent + +The EKS Pod Identity Agent can be installed as a Managed Add-on through the AWS Console, or through CAPA. +To install the addon through CAPA, add it to `AWSManagedControlPlane`. Please ensure that the version is up to date, according to the [addons section](addons.md). + +```yaml +# [...] +kind: AWSManagedControlPlane +spec: + # [...] + addons: + # [...] + - conflictResolution: overwrite + name: eks-pod-identity-agent + version: v1.1.0-eksbuild.1 +``` + +You can verify that this is running on your kubernetes cluster with `kubectl get deploy -A | grep eks` + +## Mapping a service account to an IAM role + +Now that you have created a role `capi-test-role` in AWS, and have added the EKS agent to your cluster, we must add the following to our `AWSManagedControlPlane` under `.spec.podIdentityAssociations` + +```yaml +# [...] +kind: AWSManagedControlPlane +spec: + # [...] + podIdentityAssociations: + - serviceAccount: + namespace: default + name: myserviceaccount + roleARN: arn:aws:iam::012345678901:role/capi-test-role +``` + +- `serviceAccount.namespace` and `serviceAccount.name` refer to the [`ServiceAccount`](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) object in the Kubernetes cluster +- `serviceAccount.roleARN` is the AWS ARN for the IAM role you created in step 1 (named `capi-test-role` in this tutorial). Make sure to copy this exactly from your AWS console (`IAM > Roles`). + +To use the same IAM role across multiple service accounts/namespaces, you must create multiple associations. + +A full CAPA example of everything mentioned above, including 2 role mappings, is shown below: + +```yaml +kind: AWSManagedControlPlane +apiVersion: controlplane.cluster.x-k8s.io/v1beta1 +metadata: + name: "capi-managed-test" +spec: + region: "eu-west-2" + sshKeyName: "capi-management" + version: "v1.27.0" + addons: + - conflictResolution: overwrite + name: eks-pod-identity-agent + version: v1.1.0-eksbuild.1 + podIdentityAssociations: + - serviceAccountNamespace: default + serviceAccountName: myserviceaccount + serviceAccountRoleARN: arn:aws:iam::012345678901:role/capi-test-role + - serviceAccountNamespace: another-namespace + serviceAccountName: another-service-account + serviceAccountRoleARN: arn:aws:iam::012345678901:role/capi-test-role +``` diff --git a/pkg/cloud/services/eks/addons.go b/pkg/cloud/services/eks/addons.go index 3d2e75f0a5..4e6f8b0d1a 100644 --- a/pkg/cloud/services/eks/addons.go +++ b/pkg/cloud/services/eks/addons.go @@ -40,7 +40,7 @@ func (s *Service) reconcileAddons(ctx context.Context) error { // Get available addon names for the cluster addonNames, err := s.listAddons(ctx, eksClusterName) if err != nil { - s.Error(err, "failed listing addons") + s.scope.Error(err, "failed listing addons") return fmt.Errorf("listing eks addons: %w", err) } diff --git a/pkg/cloud/services/eks/eks.go b/pkg/cloud/services/eks/eks.go index 05b760fa44..eb224c4632 100644 --- a/pkg/cloud/services/eks/eks.go +++ b/pkg/cloud/services/eks/eks.go @@ -56,6 +56,13 @@ func (s *Service) ReconcileControlPlane(ctx context.Context) error { } conditions.MarkTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSAddonsConfiguredCondition) + // EKS Pod Identity Associations + if err := s.reconcilePodIdentities(ctx); err != nil { + conditions.MarkFalse(s.scope.ControlPlane, ekscontrolplanev1.EKSPodIdentityAssociationConfiguredCondition, ekscontrolplanev1.EKSPodIdentityAssociationFailedReason, clusterv1.ConditionSeverityError, err.Error()) + return errors.Wrap(err, "failed reconciling eks pod identity associations") + } + conditions.MarkTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSPodIdentityAssociationConfiguredCondition) + // EKS Identity Provider if err := s.reconcileIdentityProvider(ctx); err != nil { conditions.MarkFalse(s.scope.ControlPlane, ekscontrolplanev1.EKSIdentityProviderConfiguredCondition, ekscontrolplanev1.EKSIdentityProviderConfiguredFailedReason, clusterv1.ConditionSeverityWarning, "%s", err.Error()) diff --git a/pkg/cloud/services/eks/pod_identity.go b/pkg/cloud/services/eks/pod_identity.go new file mode 100644 index 0000000000..3a537b9eed --- /dev/null +++ b/pkg/cloud/services/eks/pod_identity.go @@ -0,0 +1,119 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package eks + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go/service/eks" + + ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" + ekspodidentities "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/eks/podidentities" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/record" +) + +func (s *Service) reconcilePodIdentities(ctx context.Context) error { + s.scope.Info("Reconciling EKS Pod Identities") + + eksClusterName := s.scope.KubernetesClusterName() + + // Get existing eks pod identities on the cluster + currentAssociations, err := s.listEksPodIdentities(ctx, eksClusterName) + if err != nil { + s.Error(err, "failed listing eks pod identity assocations") + return fmt.Errorf("listing eks pod identity assocations: %w", err) + } + + if len(currentAssociations) == 0 && len(s.scope.ControlPlane.Spec.PodIdentityAssociations) == 0 { + s.scope.Debug("no eks pod identities found, no action needed") + return nil + } + + s.scope.Debug("eks pod identities found, creating reconciliation plan") + desiredAssociations := s.translateAPIToPodAssociation(s.scope.ControlPlane.Spec.PodIdentityAssociations) + existingAssociations := s.translateAWSToPodAssociation(currentAssociations) + + s.scope.Debug("creating eks pod identity association plan", "cluster", eksClusterName) + podAssociationsPlan := ekspodidentities.NewPlan(eksClusterName, desiredAssociations, existingAssociations, s.EKSClient) + procedures, err := podAssociationsPlan.Create(ctx) + if err != nil { + s.scope.Error(err, "failed creating eks pod identity association plan") + return fmt.Errorf("creating eks pod identity association plan: %w", err) + } + for _, procedure := range procedures { + s.scope.Debug("Executing pod association procedure", "name", procedure.Name()) + if err := procedure.Do(ctx); err != nil { + s.scope.Error(err, "failed executing pod association procedure", "name", procedure.Name()) + return fmt.Errorf("executing pod association procedure %s: %w", procedure.Name(), err) + } + } + + record.Eventf(s.scope.ControlPlane, "SuccessfulReconcileEKSClusterPodIdentityAssociations", "Reconciled Pod Identity associations for EKS Cluster %s", s.scope.KubernetesClusterName()) + s.scope.Info("Reconcile EKS pod identity associations completed successfully") + + return nil +} + +func (s *Service) listEksPodIdentities(ctx context.Context, eksClusterName string) ([]*eks.PodIdentityAssociationSummary, error) { + s.Debug("getting list of associated eks pod identities") + + input := &eks.ListPodIdentityAssociationsInput{ + ClusterName: &eksClusterName, + } + + output, err := s.EKSClient.ListPodIdentityAssociationsWithContext(ctx, input) + if err != nil { + return nil, fmt.Errorf("listing eks pod identity assocations: %w", err) + } + + return output.Associations, nil +} + +func (s *Service) translateAPIToPodAssociation(assocs []ekscontrolplanev1.PodIdentityAssociation) []ekspodidentities.EKSPodIdentityAssociation { + converted := []ekspodidentities.EKSPodIdentityAssociation{} + + for _, assoc := range assocs { + a := assoc + c := ekspodidentities.EKSPodIdentityAssociation{ + ServiceAccountName: a.ServiceAccountName, + ServiceAccountNamespace: a.ServiceAccountNamespace, + RoleARN: a.RoleARN, + } + + converted = append(converted, c) + } + + return converted +} + +func (s *Service) translateAWSToPodAssociation(assocs []*eks.PodIdentityAssociationSummary) []ekspodidentities.EKSPodIdentityAssociation { + converted := []ekspodidentities.EKSPodIdentityAssociation{} + + for _, assoc := range assocs { + c := ekspodidentities.EKSPodIdentityAssociation{ + ServiceAccountName: *assoc.ServiceAccount, + ServiceAccountNamespace: *assoc.Namespace, + RoleARN: *assoc.AssociationArn, + AssociationID: *assoc.AssociationId, + } + + converted = append(converted, c) + } + + return converted +} diff --git a/pkg/eks/podidentities/plan.go b/pkg/eks/podidentities/plan.go new file mode 100644 index 0000000000..a17b011b8e --- /dev/null +++ b/pkg/eks/podidentities/plan.go @@ -0,0 +1,102 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package podidentities provides a plan to manage EKS podidentities associations. +package podidentities + +import ( + "context" + + "github.com/aws/aws-sdk-go/service/eks/eksiface" + + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/planner" +) + +// NewPlan creates a new Plan to manage EKS pod identities. +func NewPlan(clusterName string, desiredAssociations, currentAssociations []EKSPodIdentityAssociation, client eksiface.EKSAPI) planner.Plan { + return &plan{ + currentAssociations: currentAssociations, + desiredAssociations: desiredAssociations, + eksClient: client, + clusterName: clusterName, + } +} + +// Plan is a plan that will manage EKS addons. +type plan struct { + currentAssociations []EKSPodIdentityAssociation + desiredAssociations []EKSPodIdentityAssociation + eksClient eksiface.EKSAPI + clusterName string +} + +func (a *plan) getCurrentAssociation(association EKSPodIdentityAssociation) bool { + for _, current := range a.currentAssociations { + if current.ServiceAccountName == association.ServiceAccountName && current.ServiceAccountNamespace == association.ServiceAccountNamespace { + return true + } + } + return false +} + +func (a *plan) getDesiredAssociation(association EKSPodIdentityAssociation) bool { + for _, desired := range a.desiredAssociations { + if desired.ServiceAccountName == association.ServiceAccountName && desired.ServiceAccountNamespace == association.ServiceAccountNamespace { + return true + } + } + return false +} + +// Create will create the plan (i.e. list of procedures) for managing EKS addons. +func (a *plan) Create(_ context.Context) ([]planner.Procedure, error) { + procedures := []planner.Procedure{} + + for _, d := range a.desiredAssociations { + desired := d + existsInCurrent := a.getCurrentAssociation(desired) + existsInDesired := a.getDesiredAssociation(desired) + + // Create pod association if is doesnt already exist + if existsInDesired && !existsInCurrent { + procedures = append(procedures, + &CreatePodIdentityAssociationProcedure{ + eksClient: a.eksClient, + clusterName: a.clusterName, + newAssociation: &desired, + }, + ) + } + } + + for _, current := range a.currentAssociations { + existsInCurrent := a.getCurrentAssociation(current) + existsInDesired := a.getDesiredAssociation(current) + + if !existsInDesired && existsInCurrent { + // Delete pod association if it exists + procedures = append(procedures, + &DeletePodIdentityAssociationProcedure{ + eksClient: a.eksClient, + clusterName: a.clusterName, + existingAssociationID: current.AssociationID, + }, + ) + } + } + + return procedures, nil +} diff --git a/pkg/eks/podidentities/plan_test.go b/pkg/eks/podidentities/plan_test.go new file mode 100644 index 0000000000..441a816932 --- /dev/null +++ b/pkg/eks/podidentities/plan_test.go @@ -0,0 +1,177 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package podidentities + +import ( + "context" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/eks" + "github.com/golang/mock/gomock" + . "github.com/onsi/gomega" + + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/eks/mock_eksiface" +) + +func TestEKSPodIdentityAssociationsPlan(t *testing.T) { + clusterName := "default.cluster" + namespace := "my-namespace" + roleArn := "aws://rolearn" + responseAssociationArn := "aws://association-arn" + associationID := "aws://association-id" + serviceAccount := "my-service-account" + created := time.Now() + + testCases := []struct { + name string + desired []EKSPodIdentityAssociation + current []EKSPodIdentityAssociation + expect func(m *mock_eksiface.MockEKSAPIMockRecorder) + expectCreateError bool + expectDoError bool + }{ + { + name: "no desired and no current", + expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { + // Do nothing + }, + expectCreateError: false, + expectDoError: false, + }, + { + name: "no current and 1 desired", + expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { + m. + CreatePodIdentityAssociation(gomock.Eq(&eks.CreatePodIdentityAssociationInput{ + Namespace: aws.String(namespace), + RoleArn: aws.String(roleArn), + ServiceAccount: aws.String(serviceAccount), + ClusterName: aws.String(clusterName), + })). + Return(&eks.CreatePodIdentityAssociationOutput{ + Association: &eks.PodIdentityAssociation{ + AssociationArn: aws.String(responseAssociationArn), + AssociationId: aws.String(associationID), + Namespace: aws.String(namespace), + RoleArn: aws.String(roleArn), + ServiceAccount: aws.String(serviceAccount), + ClusterName: aws.String(clusterName), + CreatedAt: &created, + ModifiedAt: &created, + }, + }, nil) + }, + desired: []EKSPodIdentityAssociation{ + { + ServiceAccountName: serviceAccount, + ServiceAccountNamespace: namespace, + RoleARN: roleArn, + }, + }, + current: []EKSPodIdentityAssociation{}, + expectCreateError: false, + expectDoError: false, + }, + { + name: "1 current and 1 desired", + expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {}, + desired: []EKSPodIdentityAssociation{ + { + ServiceAccountName: serviceAccount, + ServiceAccountNamespace: namespace, + RoleARN: roleArn, + }, + }, + current: []EKSPodIdentityAssociation{ + { + ServiceAccountName: serviceAccount, + ServiceAccountNamespace: namespace, + RoleARN: roleArn, + }, + }, + expectCreateError: false, + expectDoError: false, + }, + { + name: "1 current and 0 desired", + expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { + m. + DeletePodIdentityAssociation(gomock.Eq(&eks.DeletePodIdentityAssociationInput{ + AssociationId: aws.String(associationID), + ClusterName: aws.String(clusterName), + })). + Return(&eks.DeletePodIdentityAssociationOutput{ + Association: &eks.PodIdentityAssociation{ + AssociationArn: aws.String(responseAssociationArn), + AssociationId: aws.String(associationID), + Namespace: aws.String(namespace), + RoleArn: aws.String(roleArn), + ServiceAccount: aws.String(serviceAccount), + ClusterName: aws.String(clusterName), + CreatedAt: &created, + ModifiedAt: &created, + }, + }, nil) + }, + desired: []EKSPodIdentityAssociation{}, + current: []EKSPodIdentityAssociation{ + { + ServiceAccountName: serviceAccount, + ServiceAccountNamespace: namespace, + RoleARN: roleArn, + AssociationID: associationID, + }, + }, + expectCreateError: false, + expectDoError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + mockControl := gomock.NewController(t) + defer mockControl.Finish() + + eksMock := mock_eksiface.NewMockEKSAPI(mockControl) + tc.expect(eksMock.EXPECT()) + + ctx := context.TODO() + + planner := NewPlan(clusterName, tc.desired, tc.current, eksMock) + procedures, err := planner.Create(ctx) + if tc.expectCreateError { + g.Expect(err).To(HaveOccurred()) + return + } + g.Expect(err).To(BeNil()) + g.Expect(procedures).NotTo(BeNil()) + + for _, proc := range procedures { + procErr := proc.Do(ctx) + if tc.expectDoError { + g.Expect(procErr).To(HaveOccurred()) + return + } + g.Expect(procErr).To(BeNil()) + } + }) + } +} diff --git a/pkg/eks/podidentities/procedures.go b/pkg/eks/podidentities/procedures.go new file mode 100644 index 0000000000..3181b69e9d --- /dev/null +++ b/pkg/eks/podidentities/procedures.go @@ -0,0 +1,89 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package podidentities + +import ( + "context" + "errors" + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/eks" + "github.com/aws/aws-sdk-go/service/eks/eksiface" +) + +// errPodIdentityAssociationNotFound defines an error for when an eks pod identity is not found. +var errPodIdentityAssociationNotFound = errors.New("eks pod identity association not found") + +// DeletePodIdentityAssociationProcedure is a procedure that will delete an EKS eks pod identity. +type DeletePodIdentityAssociationProcedure struct { + eksClient eksiface.EKSAPI + clusterName string + existingAssociationID string +} + +// Do implements the logic for the procedure. +func (p *DeletePodIdentityAssociationProcedure) Do(_ context.Context) error { + input := &eks.DeletePodIdentityAssociationInput{ + AssociationId: aws.String(p.existingAssociationID), + ClusterName: aws.String(p.clusterName), + } + + if _, err := p.eksClient.DeletePodIdentityAssociation(input); err != nil { + return fmt.Errorf("deleting eks pod identity %s: %w", p.existingAssociationID, err) + } + + return nil +} + +// Name is the name of the procedure. +func (p *DeletePodIdentityAssociationProcedure) Name() string { + return "eks_pod_identity_delete" +} + +// CreatePodIdentityAssociationProcedure is a procedure that will create an EKS eks pod identity for a cluster. +type CreatePodIdentityAssociationProcedure struct { + eksClient eksiface.EKSAPI + clusterName string + newAssociation *EKSPodIdentityAssociation +} + +// Do implements the logic for the procedure. +func (p *CreatePodIdentityAssociationProcedure) Do(_ context.Context) error { + if p.newAssociation == nil { + return fmt.Errorf("getting desired eks pod identity for cluster %s: %w", p.clusterName, errPodIdentityAssociationNotFound) + } + + input := &eks.CreatePodIdentityAssociationInput{ + ClusterName: aws.String(p.clusterName), + Namespace: &p.newAssociation.ServiceAccountNamespace, + RoleArn: &p.newAssociation.RoleARN, + ServiceAccount: &p.newAssociation.ServiceAccountName, + } + + _, err := p.eksClient.CreatePodIdentityAssociation(input) + if err != nil { + return fmt.Errorf("creating desired eks pod identity for cluster %s: %w", p.clusterName, err) + } + + return nil +} + +// Name is the name of the procedure. +func (p *CreatePodIdentityAssociationProcedure) Name() string { + return "eks_pod_identity_create" +} diff --git a/pkg/eks/podidentities/types.go b/pkg/eks/podidentities/types.go new file mode 100644 index 0000000000..4b36527a5e --- /dev/null +++ b/pkg/eks/podidentities/types.go @@ -0,0 +1,48 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package podidentities + +import ( + "github.com/google/go-cmp/cmp" +) + +// EKSPodIdentityAssociation represents an EKS pod identity association. +type EKSPodIdentityAssociation struct { + ServiceAccountName string + ServiceAccountNamespace string + RoleARN string + AssociationID string +} + +// IsEqual determines if 2 EKSPodIdentityAssociation are equal. +func (e *EKSPodIdentityAssociation) IsEqual(other *EKSPodIdentityAssociation) bool { + // NOTE: we don't compare the ARN as thats only for existing addons + if e == other { + return true + } + if !cmp.Equal(e.ServiceAccountName, other.ServiceAccountName) { + return false + } + if !cmp.Equal(e.ServiceAccountNamespace, other.ServiceAccountNamespace) { + return false + } + if !cmp.Equal(e.RoleARN, other.RoleARN) { + return false + } + + return true +} From 562fae822b3a37f1aabcda4c4216b8c6ef3da644 Mon Sep 17 00:00:00 2001 From: Stefan McShane Date: Wed, 1 May 2024 17:56:10 -0400 Subject: [PATCH 2/8] semantic changes --- pkg/eks/podidentities/plan.go | 13 +++++-------- pkg/eks/podidentities/types.go | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pkg/eks/podidentities/plan.go b/pkg/eks/podidentities/plan.go index a17b011b8e..5652a3aea4 100644 --- a/pkg/eks/podidentities/plan.go +++ b/pkg/eks/podidentities/plan.go @@ -35,7 +35,7 @@ func NewPlan(clusterName string, desiredAssociations, currentAssociations []EKSP } } -// Plan is a plan that will manage EKS addons. +// Plan is a plan that will manage EKS pod identities. type plan struct { currentAssociations []EKSPodIdentityAssociation desiredAssociations []EKSPodIdentityAssociation @@ -61,17 +61,15 @@ func (a *plan) getDesiredAssociation(association EKSPodIdentityAssociation) bool return false } -// Create will create the plan (i.e. list of procedures) for managing EKS addons. +// Create will create the plan (i.e. list of procedures) for managing EKS pod identities. func (a *plan) Create(_ context.Context) ([]planner.Procedure, error) { procedures := []planner.Procedure{} - for _, d := range a.desiredAssociations { - desired := d + for _, desired := range a.desiredAssociations { existsInCurrent := a.getCurrentAssociation(desired) - existsInDesired := a.getDesiredAssociation(desired) // Create pod association if is doesnt already exist - if existsInDesired && !existsInCurrent { + if !existsInCurrent { procedures = append(procedures, &CreatePodIdentityAssociationProcedure{ eksClient: a.eksClient, @@ -83,10 +81,9 @@ func (a *plan) Create(_ context.Context) ([]planner.Procedure, error) { } for _, current := range a.currentAssociations { - existsInCurrent := a.getCurrentAssociation(current) existsInDesired := a.getDesiredAssociation(current) - if !existsInDesired && existsInCurrent { + if !existsInDesired { // Delete pod association if it exists procedures = append(procedures, &DeletePodIdentityAssociationProcedure{ diff --git a/pkg/eks/podidentities/types.go b/pkg/eks/podidentities/types.go index 4b36527a5e..a3c789fa1b 100644 --- a/pkg/eks/podidentities/types.go +++ b/pkg/eks/podidentities/types.go @@ -30,7 +30,6 @@ type EKSPodIdentityAssociation struct { // IsEqual determines if 2 EKSPodIdentityAssociation are equal. func (e *EKSPodIdentityAssociation) IsEqual(other *EKSPodIdentityAssociation) bool { - // NOTE: we don't compare the ARN as thats only for existing addons if e == other { return true } From 62c907e5dbc34a94ea7acde3be7864d96026696d Mon Sep 17 00:00:00 2001 From: Stefan McShane Date: Thu, 2 May 2024 10:22:00 -0400 Subject: [PATCH 3/8] memory aliasing patch --- pkg/eks/podidentities/plan.go | 3 ++- pkg/eks/podidentities/types.go | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/eks/podidentities/plan.go b/pkg/eks/podidentities/plan.go index 5652a3aea4..48a052545a 100644 --- a/pkg/eks/podidentities/plan.go +++ b/pkg/eks/podidentities/plan.go @@ -65,7 +65,8 @@ func (a *plan) getDesiredAssociation(association EKSPodIdentityAssociation) bool func (a *plan) Create(_ context.Context) ([]planner.Procedure, error) { procedures := []planner.Procedure{} - for _, desired := range a.desiredAssociations { + for _, d := range a.desiredAssociations { + desired := d existsInCurrent := a.getCurrentAssociation(desired) // Create pod association if is doesnt already exist diff --git a/pkg/eks/podidentities/types.go b/pkg/eks/podidentities/types.go index a3c789fa1b..45be6e5f42 100644 --- a/pkg/eks/podidentities/types.go +++ b/pkg/eks/podidentities/types.go @@ -17,6 +17,8 @@ limitations under the License. package podidentities import ( + "net/http" + "github.com/google/go-cmp/cmp" ) @@ -42,6 +44,6 @@ func (e *EKSPodIdentityAssociation) IsEqual(other *EKSPodIdentityAssociation) bo if !cmp.Equal(e.RoleARN, other.RoleARN) { return false } - + x := http.DefaultClient return true } From 81bfe2133aadd09b8aefb88c1e2f48c10e643f89 Mon Sep 17 00:00:00 2001 From: Stefan McShane Date: Thu, 2 May 2024 17:56:14 -0400 Subject: [PATCH 4/8] memory aliasing patch --- pkg/eks/podidentities/types.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/eks/podidentities/types.go b/pkg/eks/podidentities/types.go index 45be6e5f42..a3c789fa1b 100644 --- a/pkg/eks/podidentities/types.go +++ b/pkg/eks/podidentities/types.go @@ -17,8 +17,6 @@ limitations under the License. package podidentities import ( - "net/http" - "github.com/google/go-cmp/cmp" ) @@ -44,6 +42,6 @@ func (e *EKSPodIdentityAssociation) IsEqual(other *EKSPodIdentityAssociation) bo if !cmp.Equal(e.RoleARN, other.RoleARN) { return false } - x := http.DefaultClient + return true } From 097fda61b68e55271aeff0f2826bba0dbe86c4f5 Mon Sep 17 00:00:00 2001 From: Stefan McShane Date: Mon, 3 Jun 2024 11:39:54 -0400 Subject: [PATCH 5/8] Update types.go --- pkg/eks/podidentities/types.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/pkg/eks/podidentities/types.go b/pkg/eks/podidentities/types.go index a3c789fa1b..e8331fbbfa 100644 --- a/pkg/eks/podidentities/types.go +++ b/pkg/eks/podidentities/types.go @@ -27,21 +27,3 @@ type EKSPodIdentityAssociation struct { RoleARN string AssociationID string } - -// IsEqual determines if 2 EKSPodIdentityAssociation are equal. -func (e *EKSPodIdentityAssociation) IsEqual(other *EKSPodIdentityAssociation) bool { - if e == other { - return true - } - if !cmp.Equal(e.ServiceAccountName, other.ServiceAccountName) { - return false - } - if !cmp.Equal(e.ServiceAccountNamespace, other.ServiceAccountNamespace) { - return false - } - if !cmp.Equal(e.RoleARN, other.RoleARN) { - return false - } - - return true -} From 8d26f3de9fb6deded797fad2ec4cf31e1dd09f97 Mon Sep 17 00:00:00 2001 From: Stefan McShane Date: Mon, 3 Jun 2024 17:18:04 -0400 Subject: [PATCH 6/8] update json tag --- ....cluster.x-k8s.io_awsmanagedcontrolplanes.yaml | 8 ++++---- controlplane/eks/api/v1beta2/types.go | 2 +- .../src/topics/eks/pod-identity-associations.md | 15 +++++++-------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml index 551af9e8e6..1556047b07 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml @@ -3093,6 +3093,10 @@ spec: which allows the service principal `pods.eks.amazonaws.com` in its trust policy. properties: + roleARN: + description: RoleARN is the ARN of an IAM role which the Service + Account can assume. + type: string serviceAccountName: description: ServiceAccountName is the name of the kubernetes Service Account within the namespace @@ -3103,10 +3107,6 @@ spec: which the kubernetes Service Account resides in. Defaults to "default" namespace. type: string - serviceAccountRoleARN: - description: RoleARN is the ARN of an IAM role which the Service - Account can assume. - type: string required: - serviceAccountName - serviceAccountNamespace diff --git a/controlplane/eks/api/v1beta2/types.go b/controlplane/eks/api/v1beta2/types.go index 90d73ccb87..a7208d61ce 100644 --- a/controlplane/eks/api/v1beta2/types.go +++ b/controlplane/eks/api/v1beta2/types.go @@ -289,5 +289,5 @@ type PodIdentityAssociation struct { ServiceAccountNamespace string `json:"serviceAccountNamespace"` // RoleARN is the ARN of an IAM role which the Service Account can assume. // +kubebuilder:validation:Required - RoleARN string `json:"serviceAccountRoleARN,omitempty"` + RoleARN string `json:"roleARN,omitempty"` } diff --git a/docs/book/src/topics/eks/pod-identity-associations.md b/docs/book/src/topics/eks/pod-identity-associations.md index 0f1a6b3e0e..05611dbeff 100644 --- a/docs/book/src/topics/eks/pod-identity-associations.md +++ b/docs/book/src/topics/eks/pod-identity-associations.md @@ -53,14 +53,13 @@ kind: AWSManagedControlPlane spec: # [...] podIdentityAssociations: - - serviceAccount: - namespace: default - name: myserviceaccount - roleARN: arn:aws:iam::012345678901:role/capi-test-role + - serviceAccountNamespace: default + serviceAccountName: myserviceaccount + roleARN: arn:aws:iam::012345678901:role/capi-test-role ``` -- `serviceAccount.namespace` and `serviceAccount.name` refer to the [`ServiceAccount`](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) object in the Kubernetes cluster -- `serviceAccount.roleARN` is the AWS ARN for the IAM role you created in step 1 (named `capi-test-role` in this tutorial). Make sure to copy this exactly from your AWS console (`IAM > Roles`). +- `.serviceAccountNamespace` and `.serviceAccountName` refer to the [`ServiceAccount`](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) object in the Kubernetes cluster +- `.roleARN` is the AWS ARN for the IAM role you created in step 1 (named `capi-test-role` in this tutorial). Make sure to copy this exactly from your AWS console (`IAM > Roles`). To use the same IAM role across multiple service accounts/namespaces, you must create multiple associations. @@ -82,8 +81,8 @@ spec: podIdentityAssociations: - serviceAccountNamespace: default serviceAccountName: myserviceaccount - serviceAccountRoleARN: arn:aws:iam::012345678901:role/capi-test-role + roleARN: arn:aws:iam::012345678901:role/capi-test-role - serviceAccountNamespace: another-namespace serviceAccountName: another-service-account - serviceAccountRoleARN: arn:aws:iam::012345678901:role/capi-test-role + roleARN: arn:aws:iam::012345678901:role/capi-test-role ``` From a915858c2f57a75d25e8ddc979f76b73b08dcc2c Mon Sep 17 00:00:00 2001 From: Stefan McShane Date: Wed, 5 Jun 2024 17:11:14 -0400 Subject: [PATCH 7/8] update deps --- pkg/eks/podidentities/types.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/eks/podidentities/types.go b/pkg/eks/podidentities/types.go index e8331fbbfa..3469aea0b8 100644 --- a/pkg/eks/podidentities/types.go +++ b/pkg/eks/podidentities/types.go @@ -16,10 +16,6 @@ limitations under the License. package podidentities -import ( - "github.com/google/go-cmp/cmp" -) - // EKSPodIdentityAssociation represents an EKS pod identity association. type EKSPodIdentityAssociation struct { ServiceAccountName string From 7ecc96a8e1de3b12933a01b8a81eb13aa2266009 Mon Sep 17 00:00:00 2001 From: Danil Grigorev Date: Fri, 25 Jul 2025 12:14:52 +0100 Subject: [PATCH 8/8] Rebase on AWS SDK v2 Signed-off-by: Danil Grigorev --- ...ster.x-k8s.io_awsmanagedcontrolplanes.yaml | 1 + .../awsmanagedcontrolplane_controller_test.go | 4 +- go.mod | 8 +-- go.sum | 16 ++--- .../services/eks/mock_eksiface/eksapi_mock.go | 60 +++++++++++++++++++ pkg/cloud/services/eks/pod_identity.go | 9 +-- pkg/cloud/services/eks/service.go | 3 + pkg/eks/podidentities/plan.go | 12 +++- pkg/eks/podidentities/plan_test.go | 13 ++-- pkg/eks/podidentities/procedures.go | 17 +++--- 10 files changed, 107 insertions(+), 36 deletions(-) diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml index 1556047b07..0f2abac50e 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml @@ -3108,6 +3108,7 @@ spec: to "default" namespace. type: string required: + - roleARN - serviceAccountName - serviceAccountNamespace type: object diff --git a/controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go b/controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go index 594048881b..309f419ca6 100644 --- a/controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go +++ b/controlplane/eks/controllers/awsmanagedcontrolplane_controller_test.go @@ -951,10 +951,10 @@ func mockedEKSCluster(ctx context.Context, g *WithT, eksRec *mock_eksiface.MockE }).Return(&eks.ListAddonsOutput{}, nil) eksRec.UpdateClusterConfig(ctx, gomock.AssignableToTypeOf(&eks.UpdateClusterConfigInput{})).After(waitUntilClusterActiveCall).Return(&eks.UpdateClusterConfigOutput{}, nil) - eksRec.ListPodIdentityAssociationsWqithContext(context.TODO(), gomock.Eq(&eks.ListPodIdentityAssociationsInput{ + eksRec.ListPodIdentityAssociations(context.TODO(), gomock.Eq(&eks.ListPodIdentityAssociationsInput{ ClusterName: aws.String("test-cluster"), })).Return(&eks.ListPodIdentityAssociationsOutput{ - Associations: []*eks.PodIdentityAssociationSummary{}, + Associations: []ekstypes.PodIdentityAssociationSummary{}, }, nil) awsNodeRec.ReconcileCNI(gomock.Any()).Return(nil) diff --git a/go.mod b/go.mod index f4faa4a69a..f2027436e2 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,12 @@ require ( github.com/aws/amazon-vpc-cni-k8s v1.15.5 github.com/aws/aws-lambda-go v1.41.0 github.com/aws/aws-sdk-go v1.55.7 - github.com/aws/aws-sdk-go-v2 v1.36.5 + github.com/aws/aws-sdk-go-v2 v1.36.6 github.com/aws/aws-sdk-go-v2/config v1.27.11 github.com/aws/aws-sdk-go-v2/credentials v1.17.11 github.com/aws/aws-sdk-go-v2/service/autoscaling v1.52.4 github.com/aws/aws-sdk-go-v2/service/ec2 v1.159.0 - github.com/aws/aws-sdk-go-v2/service/eks v1.64.0 + github.com/aws/aws-sdk-go-v2/service/eks v1.66.2 github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.6 github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2 github.com/aws/aws-sdk-go-v2/service/iam v1.32.0 @@ -86,8 +86,8 @@ require ( github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect github.com/aws/aws-sdk-go-v2/service/cloudformation v1.50.0 // indirect diff --git a/go.sum b/go.sum index ea5310f7d1..13392c95f3 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,8 @@ github.com/aws/aws-lambda-go v1.41.0 h1:l/5fyVb6Ud9uYd411xdHZzSf2n86TakxzpvIoz7l github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0= -github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU= +github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA= @@ -58,10 +58,10 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHH github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs= @@ -72,8 +72,8 @@ github.com/aws/aws-sdk-go-v2/service/cloudformation v1.50.0 h1:Ap5tOJfeAH1hO2UQc github.com/aws/aws-sdk-go-v2/service/cloudformation v1.50.0/go.mod h1:/v2KYdCW4BaHKayenaWEXOOdxItIwEA3oU0XzuQY3F0= github.com/aws/aws-sdk-go-v2/service/ec2 v1.159.0 h1:DmmVmiLPlcntOcjWMRwDPMNx/wi2kAVrf2ZmSN5gkAg= github.com/aws/aws-sdk-go-v2/service/ec2 v1.159.0/go.mod h1:xejKuuRDjz6z5OqyeLsz01MlOqqW7CqpAB4PabNvpu8= -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/eks v1.66.2 h1:gDvxe1rFYhU9sfA/S8TePGE7gfC0vB9pCs6B4zbm5Ng= +github.com/aws/aws-sdk-go-v2/service/eks v1.66.2/go.mod h1:lpcShMkoQ94JiSVoEF1yE2WP40IV02bbnaT6oYP7cQo= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.6 h1:9grU/+HRwLXJV8XUjEPThJj/H+0oHkeNBFpSSfZekeg= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.29.6/go.mod h1:N4fs285CsnBHlAkzBpQapefR/noggTyF09fWs72EzB4= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.45.2 h1:vX70Z4lNSr7XsioU0uJq5yvxgI50sB66MvD+V/3buS4= diff --git a/pkg/cloud/services/eks/mock_eksiface/eksapi_mock.go b/pkg/cloud/services/eks/mock_eksiface/eksapi_mock.go index cbc119e961..6f44e0ecee 100644 --- a/pkg/cloud/services/eks/mock_eksiface/eksapi_mock.go +++ b/pkg/cloud/services/eks/mock_eksiface/eksapi_mock.go @@ -172,6 +172,26 @@ func (mr *MockEKSAPIMockRecorder) CreateNodegroup(arg0, arg1 interface{}, arg2 . return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNodegroup", reflect.TypeOf((*MockEKSAPI)(nil).CreateNodegroup), varargs...) } +// CreatePodIdentityAssociation mocks base method. +func (m *MockEKSAPI) CreatePodIdentityAssociation(arg0 context.Context, arg1 *eks.CreatePodIdentityAssociationInput, arg2 ...func(*eks.Options)) (*eks.CreatePodIdentityAssociationOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreatePodIdentityAssociation", varargs...) + ret0, _ := ret[0].(*eks.CreatePodIdentityAssociationOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreatePodIdentityAssociation indicates an expected call of CreatePodIdentityAssociation. +func (mr *MockEKSAPIMockRecorder) CreatePodIdentityAssociation(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePodIdentityAssociation", reflect.TypeOf((*MockEKSAPI)(nil).CreatePodIdentityAssociation), varargs...) +} + // DeleteAddon mocks base method. func (m *MockEKSAPI) DeleteAddon(arg0 context.Context, arg1 *eks.DeleteAddonInput, arg2 ...func(*eks.Options)) (*eks.DeleteAddonOutput, error) { m.ctrl.T.Helper() @@ -252,6 +272,26 @@ func (mr *MockEKSAPIMockRecorder) DeleteNodegroup(arg0, arg1 interface{}, arg2 . return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNodegroup", reflect.TypeOf((*MockEKSAPI)(nil).DeleteNodegroup), varargs...) } +// DeletePodIdentityAssociation mocks base method. +func (m *MockEKSAPI) DeletePodIdentityAssociation(arg0 context.Context, arg1 *eks.DeletePodIdentityAssociationInput, arg2 ...func(*eks.Options)) (*eks.DeletePodIdentityAssociationOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeletePodIdentityAssociation", varargs...) + ret0, _ := ret[0].(*eks.DeletePodIdentityAssociationOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeletePodIdentityAssociation indicates an expected call of DeletePodIdentityAssociation. +func (mr *MockEKSAPIMockRecorder) DeletePodIdentityAssociation(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePodIdentityAssociation", reflect.TypeOf((*MockEKSAPI)(nil).DeletePodIdentityAssociation), varargs...) +} + // DescribeAddon mocks base method. func (m *MockEKSAPI) DescribeAddon(arg0 context.Context, arg1 *eks.DescribeAddonInput, arg2 ...func(*eks.Options)) (*eks.DescribeAddonOutput, error) { m.ctrl.T.Helper() @@ -492,6 +532,26 @@ func (mr *MockEKSAPIMockRecorder) ListIdentityProviderConfigs(arg0, arg1 interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListIdentityProviderConfigs", reflect.TypeOf((*MockEKSAPI)(nil).ListIdentityProviderConfigs), varargs...) } +// ListPodIdentityAssociations mocks base method. +func (m *MockEKSAPI) ListPodIdentityAssociations(arg0 context.Context, arg1 *eks.ListPodIdentityAssociationsInput, arg2 ...func(*eks.Options)) (*eks.ListPodIdentityAssociationsOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListPodIdentityAssociations", varargs...) + ret0, _ := ret[0].(*eks.ListPodIdentityAssociationsOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListPodIdentityAssociations indicates an expected call of ListPodIdentityAssociations. +func (mr *MockEKSAPIMockRecorder) ListPodIdentityAssociations(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPodIdentityAssociations", reflect.TypeOf((*MockEKSAPI)(nil).ListPodIdentityAssociations), varargs...) +} + // TagResource mocks base method. func (m *MockEKSAPI) TagResource(arg0 context.Context, arg1 *eks.TagResourceInput, arg2 ...func(*eks.Options)) (*eks.TagResourceOutput, error) { m.ctrl.T.Helper() diff --git a/pkg/cloud/services/eks/pod_identity.go b/pkg/cloud/services/eks/pod_identity.go index 3a537b9eed..ca795a1d99 100644 --- a/pkg/cloud/services/eks/pod_identity.go +++ b/pkg/cloud/services/eks/pod_identity.go @@ -20,7 +20,8 @@ import ( "context" "fmt" - "github.com/aws/aws-sdk-go/service/eks" + "github.com/aws/aws-sdk-go-v2/service/eks" + "github.com/aws/aws-sdk-go-v2/service/eks/types" ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" ekspodidentities "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/eks/podidentities" @@ -69,14 +70,14 @@ func (s *Service) reconcilePodIdentities(ctx context.Context) error { return nil } -func (s *Service) listEksPodIdentities(ctx context.Context, eksClusterName string) ([]*eks.PodIdentityAssociationSummary, error) { +func (s *Service) listEksPodIdentities(ctx context.Context, eksClusterName string) ([]types.PodIdentityAssociationSummary, error) { s.Debug("getting list of associated eks pod identities") input := &eks.ListPodIdentityAssociationsInput{ ClusterName: &eksClusterName, } - output, err := s.EKSClient.ListPodIdentityAssociationsWithContext(ctx, input) + output, err := s.EKSClient.ListPodIdentityAssociations(ctx, input) if err != nil { return nil, fmt.Errorf("listing eks pod identity assocations: %w", err) } @@ -101,7 +102,7 @@ func (s *Service) translateAPIToPodAssociation(assocs []ekscontrolplanev1.PodIde return converted } -func (s *Service) translateAWSToPodAssociation(assocs []*eks.PodIdentityAssociationSummary) []ekspodidentities.EKSPodIdentityAssociation { +func (s *Service) translateAWSToPodAssociation(assocs []types.PodIdentityAssociationSummary) []ekspodidentities.EKSPodIdentityAssociation { converted := []ekspodidentities.EKSPodIdentityAssociation{} for _, assoc := range assocs { diff --git a/pkg/cloud/services/eks/service.go b/pkg/cloud/services/eks/service.go index c4c274a123..e46a917984 100644 --- a/pkg/cloud/services/eks/service.go +++ b/pkg/cloud/services/eks/service.go @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/eks/iam" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/eks/podidentities" ) // EKSAPI defines the EKS API interface. @@ -62,6 +63,8 @@ type EKSAPI interface { TagResource(ctx context.Context, params *eks.TagResourceInput, optFns ...func(*eks.Options)) (*eks.TagResourceOutput, error) UntagResource(ctx context.Context, params *eks.UntagResourceInput, optFns ...func(*eks.Options)) (*eks.UntagResourceOutput, error) DisassociateIdentityProviderConfig(ctx context.Context, params *eks.DisassociateIdentityProviderConfigInput, optFns ...func(*eks.Options)) (*eks.DisassociateIdentityProviderConfigOutput, error) + ListPodIdentityAssociations(ctx context.Context, params *eks.ListPodIdentityAssociationsInput, optFns ...func(*eks.Options)) (*eks.ListPodIdentityAssociationsOutput, error) + podidentities.EKSAPIPodIdentity // Waiters for EKS Cluster WaitUntilClusterActive(ctx context.Context, params *eks.DescribeClusterInput, maxWait time.Duration) error diff --git a/pkg/eks/podidentities/plan.go b/pkg/eks/podidentities/plan.go index 48a052545a..cdadbde3d0 100644 --- a/pkg/eks/podidentities/plan.go +++ b/pkg/eks/podidentities/plan.go @@ -20,13 +20,19 @@ package podidentities import ( "context" - "github.com/aws/aws-sdk-go/service/eks/eksiface" + "github.com/aws/aws-sdk-go-v2/service/eks" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/planner" ) +// EKSAPIPodIdentity defines the EKS API interface. +type EKSAPIPodIdentity interface { + CreatePodIdentityAssociation(ctx context.Context, params *eks.CreatePodIdentityAssociationInput, optFns ...func(*eks.Options)) (*eks.CreatePodIdentityAssociationOutput, error) + DeletePodIdentityAssociation(ctx context.Context, params *eks.DeletePodIdentityAssociationInput, optFns ...func(*eks.Options)) (*eks.DeletePodIdentityAssociationOutput, error) +} + // NewPlan creates a new Plan to manage EKS pod identities. -func NewPlan(clusterName string, desiredAssociations, currentAssociations []EKSPodIdentityAssociation, client eksiface.EKSAPI) planner.Plan { +func NewPlan(clusterName string, desiredAssociations, currentAssociations []EKSPodIdentityAssociation, client EKSAPIPodIdentity) planner.Plan { return &plan{ currentAssociations: currentAssociations, desiredAssociations: desiredAssociations, @@ -39,7 +45,7 @@ func NewPlan(clusterName string, desiredAssociations, currentAssociations []EKSP type plan struct { currentAssociations []EKSPodIdentityAssociation desiredAssociations []EKSPodIdentityAssociation - eksClient eksiface.EKSAPI + eksClient EKSAPIPodIdentity clusterName string } diff --git a/pkg/eks/podidentities/plan_test.go b/pkg/eks/podidentities/plan_test.go index 441a816932..8b5faed211 100644 --- a/pkg/eks/podidentities/plan_test.go +++ b/pkg/eks/podidentities/plan_test.go @@ -21,8 +21,9 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/eks" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/eks" + "github.com/aws/aws-sdk-go-v2/service/eks/types" "github.com/golang/mock/gomock" . "github.com/onsi/gomega" @@ -58,14 +59,14 @@ func TestEKSPodIdentityAssociationsPlan(t *testing.T) { name: "no current and 1 desired", expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { m. - CreatePodIdentityAssociation(gomock.Eq(&eks.CreatePodIdentityAssociationInput{ + CreatePodIdentityAssociation(gomock.Any(), gomock.Eq(&eks.CreatePodIdentityAssociationInput{ Namespace: aws.String(namespace), RoleArn: aws.String(roleArn), ServiceAccount: aws.String(serviceAccount), ClusterName: aws.String(clusterName), })). Return(&eks.CreatePodIdentityAssociationOutput{ - Association: &eks.PodIdentityAssociation{ + Association: &types.PodIdentityAssociation{ AssociationArn: aws.String(responseAssociationArn), AssociationId: aws.String(associationID), Namespace: aws.String(namespace), @@ -112,12 +113,12 @@ func TestEKSPodIdentityAssociationsPlan(t *testing.T) { name: "1 current and 0 desired", expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { m. - DeletePodIdentityAssociation(gomock.Eq(&eks.DeletePodIdentityAssociationInput{ + DeletePodIdentityAssociation(gomock.Any(), gomock.Eq(&eks.DeletePodIdentityAssociationInput{ AssociationId: aws.String(associationID), ClusterName: aws.String(clusterName), })). Return(&eks.DeletePodIdentityAssociationOutput{ - Association: &eks.PodIdentityAssociation{ + Association: &types.PodIdentityAssociation{ AssociationArn: aws.String(responseAssociationArn), AssociationId: aws.String(associationID), Namespace: aws.String(namespace), diff --git a/pkg/eks/podidentities/procedures.go b/pkg/eks/podidentities/procedures.go index 3181b69e9d..9f92c379dc 100644 --- a/pkg/eks/podidentities/procedures.go +++ b/pkg/eks/podidentities/procedures.go @@ -21,9 +21,8 @@ import ( "errors" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/eks" - "github.com/aws/aws-sdk-go/service/eks/eksiface" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/eks" ) // errPodIdentityAssociationNotFound defines an error for when an eks pod identity is not found. @@ -31,19 +30,19 @@ var errPodIdentityAssociationNotFound = errors.New("eks pod identity association // DeletePodIdentityAssociationProcedure is a procedure that will delete an EKS eks pod identity. type DeletePodIdentityAssociationProcedure struct { - eksClient eksiface.EKSAPI + eksClient EKSAPIPodIdentity clusterName string existingAssociationID string } // Do implements the logic for the procedure. -func (p *DeletePodIdentityAssociationProcedure) Do(_ context.Context) error { +func (p *DeletePodIdentityAssociationProcedure) Do(ctx context.Context) error { input := &eks.DeletePodIdentityAssociationInput{ AssociationId: aws.String(p.existingAssociationID), ClusterName: aws.String(p.clusterName), } - if _, err := p.eksClient.DeletePodIdentityAssociation(input); err != nil { + if _, err := p.eksClient.DeletePodIdentityAssociation(ctx, input); err != nil { return fmt.Errorf("deleting eks pod identity %s: %w", p.existingAssociationID, err) } @@ -57,13 +56,13 @@ func (p *DeletePodIdentityAssociationProcedure) Name() string { // CreatePodIdentityAssociationProcedure is a procedure that will create an EKS eks pod identity for a cluster. type CreatePodIdentityAssociationProcedure struct { - eksClient eksiface.EKSAPI + eksClient EKSAPIPodIdentity clusterName string newAssociation *EKSPodIdentityAssociation } // Do implements the logic for the procedure. -func (p *CreatePodIdentityAssociationProcedure) Do(_ context.Context) error { +func (p *CreatePodIdentityAssociationProcedure) Do(ctx context.Context) error { if p.newAssociation == nil { return fmt.Errorf("getting desired eks pod identity for cluster %s: %w", p.clusterName, errPodIdentityAssociationNotFound) } @@ -75,7 +74,7 @@ func (p *CreatePodIdentityAssociationProcedure) Do(_ context.Context) error { ServiceAccount: &p.newAssociation.ServiceAccountName, } - _, err := p.eksClient.CreatePodIdentityAssociation(input) + _, err := p.eksClient.CreatePodIdentityAssociation(ctx, input) if err != nil { return fmt.Errorf("creating desired eks pod identity for cluster %s: %w", p.clusterName, err) }