Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for pod_identity_associations argument for aws_eks_addon #39973

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changelog/38357.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:enhancement
resource/aws_eks_addon: Add `pod_identity_association` argument
```
```release-note:bug
resource/aws_eks_addon: Handle `ResourceNotFound` exceptions during resource destruction
```
99 changes: 99 additions & 0 deletions internal/service/eks/addon.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,23 @@ func resourceAddon() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"pod_identity_association": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
names.AttrRoleARN: {
Type: schema.TypeString,
Required: true,
ValidateFunc: verify.ValidARN,
},
"service_account": {
Type: schema.TypeString,
Required: true,
},
},
},
},
"preserve": {
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -148,6 +165,10 @@ func resourceAddonCreate(ctx context.Context, d *schema.ResourceData, meta inter
input.ConfigurationValues = aws.String(v.(string))
}

if v, ok := d.GetOk("pod_identity_association"); ok && v.(*schema.Set).Len() > 0 {
input.PodIdentityAssociations = expandAddonPodIdentityAssociations(v.(*schema.Set).List())
}

if v, ok := d.GetOk("resolve_conflicts"); ok {
input.ResolveConflicts = types.ResolveConflicts(v.(string))
} else if v, ok := d.GetOk("resolve_conflicts_on_create"); ok {
Expand Down Expand Up @@ -226,6 +247,13 @@ func resourceAddonRead(ctx context.Context, d *schema.ResourceData, meta interfa
d.Set("configuration_values", addon.ConfigurationValues)
d.Set(names.AttrCreatedAt, aws.ToTime(addon.CreatedAt).Format(time.RFC3339))
d.Set("modified_at", aws.ToTime(addon.ModifiedAt).Format(time.RFC3339))
flatPIAs, err := flattenAddonPodIdentityAssociations(ctx, addon.PodIdentityAssociations, clusterName, meta)
if err != nil {
return sdkdiag.AppendErrorf(diags, "flattening pod_identity_association: %s", err)
}
if err := d.Set("pod_identity_association", flatPIAs); err != nil {
return sdkdiag.AppendErrorf(diags, "setting pod_identity_association: %s", err)
}
d.Set("service_account_role_arn", addon.ServiceAccountRoleArn)

setTagsOut(ctx, addon.Tags)
Expand Down Expand Up @@ -257,6 +285,12 @@ func resourceAddonUpdate(ctx context.Context, d *schema.ResourceData, meta inter
input.ConfigurationValues = aws.String(d.Get("configuration_values").(string))
}

if d.HasChange("pod_identity_association") {
if v, ok := d.GetOk("pod_identity_association"); ok && len(v.([]interface{})) > 0 {
input.PodIdentityAssociations = expandAddonPodIdentityAssociations(v.([]interface{}))
}
}

var conflictResolutionAttr string
var conflictResolution types.ResolveConflicts

Expand Down Expand Up @@ -319,6 +353,10 @@ func resourceAddonDelete(ctx context.Context, d *schema.ResourceData, meta inter
log.Printf("[DEBUG] Deleting EKS Add-On: %s", d.Id())
_, err = conn.DeleteAddon(ctx, input)

if errs.IsA[*types.ResourceNotFoundException](err) {
return diags
}

if err != nil {
return sdkdiag.AppendErrorf(diags, "deleting EKS Add-On (%s): %s", d.Id(), err)
}
Expand All @@ -330,6 +368,67 @@ func resourceAddonDelete(ctx context.Context, d *schema.ResourceData, meta inter
return diags
}

func expandAddonPodIdentityAssociations(tfList []interface{}) []types.AddonPodIdentityAssociations {
if len(tfList) == 0 {
return nil
}

var addonPodIdentityAssociations []types.AddonPodIdentityAssociations
for _, raw := range tfList {
tfMap, ok := raw.(map[string]interface{})
if !ok {
continue
}

pia := types.AddonPodIdentityAssociations{}
if roleArn, ok := tfMap[names.AttrRoleARN].(string); ok {
pia.RoleArn = aws.String(roleArn)
}
if service_account, ok := tfMap["service_account"].(string); ok {
pia.ServiceAccount = aws.String(service_account)
}

addonPodIdentityAssociations = append(addonPodIdentityAssociations, pia)
}

return addonPodIdentityAssociations
}

func flattenAddonPodIdentityAssociations(ctx context.Context, associations []string, clusterName string, meta interface{}) ([]interface{}, error) {
if len(associations) == 0 {
return nil, nil
}

conn := meta.(*conns.AWSClient).EKSClient(ctx)
var results []interface{}

for _, associationArn := range associations {
// CreateAddon returns the associationARN. The associationId is extracted from the end of the ARN,
// which is used in the DescribePodIdentityAssociation call to get the RoleARN and ServiceAccount
//
// Ex. "arn:aws:eks:<region>:<account-id>:podidentityassociation/<cluster-name>/a-1v95i5dqqiylbo3ud"
parts := strings.Split(associationArn, "/")
if len(parts) != 3 {
return nil, fmt.Errorf(`unable to extract association ID from ARN "%s"`, associationArn)
}

associationId := parts[2]
pia, err := findPodIdentityAssociationByTwoPartKey(ctx, conn, associationId, clusterName)
if err != nil {
return nil, err
}

tfMap := map[string]interface{}{
names.AttrRoleARN: pia.RoleArn,
"service_account": pia.ServiceAccount,
}

results = append(results, tfMap)
}

return results, nil
}

func findAddonByTwoPartKey(ctx context.Context, conn *eks.Client, clusterName, addonName string) (*types.Addon, error) {
input := &eks.DescribeAddonInput{
AddonName: aws.String(addonName),
Expand Down
23 changes: 23 additions & 0 deletions internal/service/eks/addon_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ func dataSourceAddon() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"pod_identity_association": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
names.AttrRoleARN: {
Type: schema.TypeString,
Computed: true,
},
"service_account": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
"service_account_role_arn": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -83,6 +99,13 @@ func dataSourceAddonRead(ctx context.Context, d *schema.ResourceData, meta inter
d.Set("configuration_values", addon.ConfigurationValues)
d.Set(names.AttrCreatedAt, aws.ToTime(addon.CreatedAt).Format(time.RFC3339))
d.Set("modified_at", aws.ToTime(addon.ModifiedAt).Format(time.RFC3339))
flatPIAs, err := flattenAddonPodIdentityAssociations(ctx, addon.PodIdentityAssociations, clusterName, meta)
if err != nil {
return sdkdiag.AppendErrorf(diags, "flattening pod_identity_association: %s", err)
}
if err := d.Set("pod_identity_association", flatPIAs); err != nil {
return sdkdiag.AppendErrorf(diags, "setting pod_identity_association: %s", err)
}
d.Set("service_account_role_arn", addon.ServiceAccountRoleArn)

if err := d.Set(names.AttrTags, KeyValueTags(ctx, addon.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/service/eks/addon_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func TestAccEKSAddonDataSource_basic(t *testing.T) {
resource.TestCheckResourceAttrPair(resourceName, "configuration_values", dataSourceResourceName, "configuration_values"),
resource.TestCheckResourceAttrPair(resourceName, names.AttrCreatedAt, dataSourceResourceName, names.AttrCreatedAt),
resource.TestCheckResourceAttrPair(resourceName, "modified_at", dataSourceResourceName, "modified_at"),
resource.TestCheckResourceAttr(resourceName, "pod_identity_associations.#", "0"),
resource.TestCheckResourceAttrPair(resourceName, "service_account_role_arn", dataSourceResourceName, "service_account_role_arn"),
resource.TestCheckResourceAttrPair(resourceName, acctest.CtTagsPercent, dataSourceResourceName, acctest.CtTagsPercent),
),
Expand Down
84 changes: 78 additions & 6 deletions internal/service/eks/addon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func TestAccEKSAddon_basic(t *testing.T) {
acctest.MatchResourceAttrRegionalARN(addonResourceName, names.AttrARN, "eks", regexache.MustCompile(fmt.Sprintf("addon/%s/%s/.+$", rName, addonName))),
resource.TestCheckResourceAttrPair(addonResourceName, names.AttrClusterName, clusterResourceName, names.AttrName),
resource.TestCheckResourceAttr(addonResourceName, "configuration_values", ""),
resource.TestCheckResourceAttr(addonResourceName, "pod_identity_association.#", "0"),
resource.TestCheckNoResourceAttr(addonResourceName, "preserve"),
resource.TestCheckResourceAttr(addonResourceName, acctest.CtTagsPercent, "0"),
),
Expand Down Expand Up @@ -114,8 +115,8 @@ func TestAccEKSAddon_addonVersion(t *testing.T) {
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_eks_addon.test"
addonName := "vpc-cni"
addonVersion1 := "v1.14.1-eksbuild.1"
addonVersion2 := "v1.15.3-eksbuild.1"
addonVersion1 := "v1.17.1-eksbuild.1"
addonVersion2 := "v1.18.5-eksbuild.1"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t); testAccPreCheckAddon(ctx, t) },
Expand Down Expand Up @@ -273,7 +274,7 @@ func TestAccEKSAddon_serviceAccountRoleARN(t *testing.T) {
var addon types.Addon
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_eks_addon.test"
serviceRoleResourceName := "aws_iam_role.test-service-role"
serviceRoleResourceName := "aws_iam_role.test_service_role"
addonName := "vpc-cni"

resource.ParallelTest(t, resource.TestCase{
Expand Down Expand Up @@ -308,7 +309,7 @@ func TestAccEKSAddon_configurationValues(t *testing.T) {
emptyConfigurationValues := "{}"
invalidConfigurationValues := "{\"env\": {\"INVALID_FIELD\":\"2\"}}"
addonName := "vpc-cni"
addonVersion := "v1.15.3-eksbuild.1"
addonVersion := "v1.17.1-eksbuild.1"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t); testAccPreCheckAddon(ctx, t) },
Expand Down Expand Up @@ -351,6 +352,39 @@ func TestAccEKSAddon_configurationValues(t *testing.T) {
})
}

func TestAccEKSAddon_podIdentityAssociation(t *testing.T) {
ctx := acctest.Context(t)
var addon types.Addon
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_eks_addon.test"
podIdentityRoleResourceName := "aws_iam_role.test_pod_identity"
addonName := "vpc-cni"
serviceAccount := "aws-node"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t); testAccPreCheckAddon(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.EKSServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckAddonDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccAddonConfig_podIdentityAssociation(rName, addonName, serviceAccount),
Check: resource.ComposeTestCheckFunc(
testAccCheckAddonExists(ctx, resourceName, &addon),
resource.TestCheckResourceAttr(resourceName, "pod_identity_association.#", "1"),
resource.TestCheckResourceAttrPair(resourceName, "pod_identity_association.0.role_arn", podIdentityRoleResourceName, names.AttrARN),
resource.TestCheckResourceAttr(resourceName, "pod_identity_association.0.service_account", serviceAccount),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccEKSAddon_tags(t *testing.T) {
ctx := acctest.Context(t)
var addon1, addon2, addon3 types.Addon
Expand Down Expand Up @@ -575,6 +609,44 @@ resource "aws_eks_addon" "test" {
`, rName, addonName, resolveConflicts))
}

func testAccAddonConfig_podIdentityAssociation(rName, addonName, serviceAccount string) string {
return acctest.ConfigCompose(testAccAddonConfig_base(rName), fmt.Sprintf(`
resource "aws_iam_role" "test_pod_identity" {
name = "test-pod-identity"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"sts:AssumeRole",
"sts:TagSession"
],
"Principal": {
"Service": "pods.eks.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF

managed_policy_arns = ["arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKS_CNI_Policy"]
}

resource "aws_eks_addon" "test" {
cluster_name = aws_eks_cluster.test.name
addon_name = %[2]q

pod_identity_association {
role_arn = aws_iam_role.test_pod_identity.arn
service_account = %[3]q
}
}
`, rName, addonName, serviceAccount))
}

func testAccAddonConfig_resolveConflicts(rName, addonName, resolveConflictsOnCreate, resolveConflictsOnUpdate string) string {
return acctest.ConfigCompose(testAccAddonConfig_base(rName), fmt.Sprintf(`
resource "aws_eks_addon" "test" {
Expand All @@ -588,7 +660,7 @@ resource "aws_eks_addon" "test" {

func testAccAddonConfig_serviceAccountRoleARN(rName, addonName string) string {
return acctest.ConfigCompose(testAccAddonConfig_base(rName), fmt.Sprintf(`
resource "aws_iam_role" "test-service-role" {
resource "aws_iam_role" "test_service_role" {
name = "test-service-role"
assume_role_policy = <<EOF
{
Expand All @@ -610,7 +682,7 @@ EOF
resource "aws_eks_addon" "test" {
cluster_name = aws_eks_cluster.test.name
addon_name = %[2]q
service_account_role_arn = aws_iam_role.test-service-role.arn
service_account_role_arn = aws_iam_role.test_service_role.arn
}
`, rName, addonName))
}
Expand Down
3 changes: 3 additions & 0 deletions website/docs/d/eks_addon.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ This data source exports the following attributes in addition to the arguments a
* `configuration_values` - Configuration values for the addon with a single JSON string.
* `service_account_role_arn` - ARN of IAM role used for EKS add-on. If value is empty -
then add-on uses the IAM role assigned to the EKS Cluster node.
* `pod_identity_association` - Pod identity association for the EKS add-on.
* `role_arn` - ARN of the IAM role associated with the EKS add-on.
* `service_account` - Service account associated with the EKS add-on.
* `id` - EKS Cluster name and EKS add-on name separated by a colon (`:`).
* `created_at` - Date and time in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) that the EKS add-on was created.
* `modified_at` - Date and time in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) that the EKS add-on was updated.
6 changes: 6 additions & 0 deletions website/docs/r/eks_addon.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ The following arguments are optional:
* `resolve_conflicts_on_update` - (Optional) How to resolve field value conflicts for an Amazon EKS add-on if you've changed a value from the Amazon EKS default value. Valid values are `NONE`, `OVERWRITE`, and `PRESERVE`. For more details see the [UpdateAddon](https://docs.aws.amazon.com/eks/latest/APIReference/API_UpdateAddon.html) API Docs.
* `resolve_conflicts` - (**Deprecated** use the `resolve_conflicts_on_create` and `resolve_conflicts_on_update` attributes instead) Define how to resolve parameter value conflicts when migrating an existing add-on to an Amazon EKS add-on or when applying version updates to the add-on. Valid values are `NONE`, `OVERWRITE` and `PRESERVE`. Note that `PRESERVE` is only valid on addon update, not for initial addon creation. If you need to set this to `PRESERVE`, use the `resolve_conflicts_on_create` and `resolve_conflicts_on_update` attributes instead. For more details check [UpdateAddon](https://docs.aws.amazon.com/eks/latest/APIReference/API_UpdateAddon.html) API Docs.
* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `pod_identity_association` - (Optional) Configuration block with EKS Pod Identity association settings. See [`pod_identity_association`](#pod-identity-association) below for details.
* `preserve` - (Optional) Indicates if you want to preserve the created resources when deleting the EKS add-on.
* `service_account_role_arn` - (Optional) The Amazon Resource Name (ARN) of an
existing IAM role to bind to the add-on's service account. The role must be
Expand All @@ -148,6 +149,11 @@ The following arguments are optional:
for service accounts on your cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html)
in the Amazon EKS User Guide.

### pod_identity_association

* `role_arn` - (Required) The Amazon Resource Name (ARN) of the IAM role to associate with the service account. The EKS Pod Identity agent manages credentials to assume this role for applications in the containers in the pods that use this service account.
* `service_account` - (Required) The name of the Kubernetes service account inside the cluster to associate the IAM credentials with.

## Attribute Reference

This resource exports the following attributes in addition to the arguments above:
Expand Down
Loading