diff --git a/.changelog/35037.txt b/.changelog/35037.txt new file mode 100644 index 00000000000..ce366f50607 --- /dev/null +++ b/.changelog/35037.txt @@ -0,0 +1,19 @@ +```release-note:new-resource +aws_eks_access_entry +``` + +```release-note:new-data-source +aws_eks_access_entry +``` + +```release-note:new-resource +aws_eks_access_policy_association +``` + +```release-note:enhancement +resource/aws_eks_cluster: Add `access_config` configuration block +``` + +```release-note:enhancement +resource/aws_eks_cluster: Add `access_config` attribute +``` \ No newline at end of file diff --git a/internal/service/eks/access_entry.go b/internal/service/eks/access_entry.go new file mode 100644 index 00000000000..5cc817b8848 --- /dev/null +++ b/internal/service/eks/access_entry.go @@ -0,0 +1,256 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package eks + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "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/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_eks_access_entry", name="Access Entry") +// @Tags(identifierAttribute="access_entry_arn") +func resourceAccessEntry() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceAccessEntryCreate, + ReadWithoutTimeout: resourceAccessEntryRead, + UpdateWithoutTimeout: resourceAccessEntryUpdate, + DeleteWithoutTimeout: resourceAccessEntryDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "access_entry_arn": { + Type: schema.TypeString, + Computed: true, + }, + "cluster_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validClusterName, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "kubernetes_groups": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "modified_at": { + Type: schema.TypeString, + Computed: true, + }, + "principal_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + "type": { + Type: schema.TypeString, + Computed: true, + }, + "user_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAccessEntryCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).EKSClient(ctx) + + clusterName := d.Get("cluster_name").(string) + principalARN := d.Get("principal_arn").(string) + id := accessEntryCreateResourceID(clusterName, principalARN) + input := &eks.CreateAccessEntryInput{ + ClusterName: aws.String(clusterName), + PrincipalArn: aws.String(principalARN), + Tags: getTagsIn(ctx), + } + + if v, ok := d.GetOk("kubernetes_groups"); ok { + input.KubernetesGroups = flex.ExpandStringValueSet(v.(*schema.Set)) + } + + _, err := conn.CreateAccessEntry(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating EKS Access Entry (%s): %s", id, err) + } + + d.SetId(id) + + return append(diags, resourceAccessEntryRead(ctx, d, meta)...) +} + +func resourceAccessEntryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).EKSClient(ctx) + + clusterName, principalARN, err := accessEntryParseResourceID(d.Id()) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + output, err := findAccessEntryByTwoPartKey(ctx, conn, clusterName, principalARN) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EKS Access Entry (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading EKS Access Entry (%s): %s", d.Id(), err) + } + + d.Set("access_entry_arn", output.AccessEntryArn) + d.Set("cluster_name", output.ClusterName) + d.Set("created_at", aws.ToTime(output.CreatedAt).Format(time.RFC3339)) + d.Set("kubernetes_groups", output.KubernetesGroups) + d.Set("modified_at", aws.ToTime(output.ModifiedAt).Format(time.RFC3339)) + d.Set("principal_arn", output.PrincipalArn) + d.Set("type", output.Type) + d.Set("user_name", output.Username) + + setTagsOut(ctx, output.Tags) + + return diags +} + +func resourceAccessEntryUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).EKSClient(ctx) + + if d.HasChangesExcept("tags", "tags_all") { + clusterName, principalARN, err := accessEntryParseResourceID(d.Id()) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + input := &eks.UpdateAccessEntryInput{ + ClusterName: aws.String(clusterName), + PrincipalArn: aws.String(principalARN), + } + + if d.HasChange("kubernetes_groups") { + input.KubernetesGroups = flex.ExpandStringValueSet(d.Get("kubernetes_groups").(*schema.Set)) + } + + _, err = conn.UpdateAccessEntry(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating EKS Access Entry (%s): %s", d.Id(), err) + } + } + + return append(diags, resourceAccessEntryRead(ctx, d, meta)...) +} + +func resourceAccessEntryDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).EKSClient(ctx) + + clusterName, principalARN, err := accessEntryParseResourceID(d.Id()) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + log.Printf("[DEBUG] Deleting EKS Access Entry: %s", d.Id()) + _, err = conn.DeleteAccessEntry(ctx, &eks.DeleteAccessEntryInput{ + ClusterName: aws.String(clusterName), + PrincipalArn: aws.String(principalARN), + }) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting EKS Access Entry (%s): %s", d.Id(), err) + } + + return diags +} + +const accessEntryResourceIDSeparator = ":" + +func accessEntryCreateResourceID(clusterName, principal_arn string) string { + parts := []string{clusterName, principal_arn} + id := strings.Join(parts, accessEntryResourceIDSeparator) + + return id +} + +func accessEntryParseResourceID(id string) (string, string, error) { + parts := strings.SplitN(id, accessEntryResourceIDSeparator, 2) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected cluster-name%[2]sprincipal-arn", id, accessEntryResourceIDSeparator) +} + +func findAccessEntryByTwoPartKey(ctx context.Context, conn *eks.Client, clusterName, principalARN string) (*types.AccessEntry, error) { + input := &eks.DescribeAccessEntryInput{ + ClusterName: aws.String(clusterName), + PrincipalArn: aws.String(principalARN), + } + + output, err := conn.DescribeAccessEntry(ctx, input) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.AccessEntry == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.AccessEntry, nil +} diff --git a/internal/service/eks/access_entry_datasource.go b/internal/service/eks/access_entry_datasource.go new file mode 100644 index 00000000000..907222be6cb --- /dev/null +++ b/internal/service/eks/access_entry_datasource.go @@ -0,0 +1,95 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package eks + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKDataSource("aws_eks_access_entry", name="Access Entry") +func dataSourceAccessEntry() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceAccessEntryRead, + + Schema: map[string]*schema.Schema{ + "access_entry_arn": { + Type: schema.TypeString, + Computed: true, + }, + "cluster_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validClusterName, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "kubernetes_groups": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "modified_at": { + Type: schema.TypeString, + Computed: true, + }, + "principal_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "user_name": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + }, + } +} + +func dataSourceAccessEntryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).EKSClient(ctx) + + clusterName := d.Get("cluster_name").(string) + principalARN := d.Get("principal_arn").(string) + id := accessEntryCreateResourceID(clusterName, principalARN) + output, err := findAccessEntryByTwoPartKey(ctx, conn, clusterName, principalARN) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading EKS Access Entry (%s): %s", id, err) + } + + d.SetId(id) + d.Set("access_entry_arn", output.AccessEntryArn) + d.Set("cluster_name", output.ClusterName) + d.Set("created_at", aws.ToTime(output.CreatedAt).Format(time.RFC3339)) + d.Set("kubernetes_groups", output.KubernetesGroups) + d.Set("modified_at", aws.ToTime(output.ModifiedAt).Format(time.RFC3339)) + d.Set("principal_arn", output.PrincipalArn) + d.Set("type", output.Type) + d.Set("user_name", output.Username) + + setTagsOut(ctx, output.Tags) + + return diags +} diff --git a/internal/service/eks/access_entry_datasource_test.go b/internal/service/eks/access_entry_datasource_test.go new file mode 100644 index 00000000000..e60e1be9a8c --- /dev/null +++ b/internal/service/eks/access_entry_datasource_test.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package eks_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/eks" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccEKSAccessEntryDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceResourceName := "data.aws_eks_access_entry.test" + resourceName := "aws_eks_access_entry.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t); testAccPreCheckAddon(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, eks.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccAccessEntryDataSourceConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "access_entry_arn", dataSourceResourceName, "access_entry_arn"), + resource.TestCheckResourceAttrPair(resourceName, "cluster_name", dataSourceResourceName, "cluster_name"), + resource.TestCheckResourceAttrPair(resourceName, "created_at", dataSourceResourceName, "created_at"), + resource.TestCheckResourceAttrPair(resourceName, "kubernetes_groups.#", dataSourceResourceName, "kubernetes_groups.#"), + resource.TestCheckResourceAttrPair(resourceName, "principal_arn", dataSourceResourceName, "principal_arn"), + resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceResourceName, "tags.%"), + resource.TestCheckResourceAttrPair(resourceName, "type", dataSourceResourceName, "type"), + resource.TestCheckResourceAttrPair(resourceName, "user_name", dataSourceResourceName, "user_name"), + ), + }, + }, + }) +} + +func testAccAccessEntryDataSourceConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccAccessEntryConfig_base(rName), fmt.Sprintf(` +resource "aws_iam_user" "test" { + name = %[1]q +} + +resource "aws_eks_access_entry" "test" { + cluster_name = aws_eks_cluster.test.name + principal_arn = aws_iam_user.test.arn +} + +data "aws_eks_access_entry" "test" { + cluster_name = aws_eks_cluster.test.name + principal_arn = aws_iam_user.test.arn + + depends_on = [ + aws_eks_access_entry.test, + aws_eks_cluster.test, + ] +} +`, rName)) +} diff --git a/internal/service/eks/access_entry_test.go b/internal/service/eks/access_entry_test.go new file mode 100644 index 00000000000..3e9508733ae --- /dev/null +++ b/internal/service/eks/access_entry_test.go @@ -0,0 +1,339 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package eks_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/eks/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfeks "github.com/hashicorp/terraform-provider-aws/internal/service/eks" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccEKSAccessEntry_basic(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var accessentry types.AccessEntry + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_eks_access_entry.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EKSEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAccessEntryDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAccessEntryConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAccessEntryExists(ctx, resourceName, &accessentry), + resource.TestCheckResourceAttrSet(resourceName, "access_entry_arn"), + acctest.CheckResourceAttrRFC3339(resourceName, "created_at"), + resource.TestCheckResourceAttr(resourceName, "kubernetes_groups.#", "0"), + acctest.CheckResourceAttrRFC3339(resourceName, "modified_at"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "type"), + resource.TestCheckResourceAttrSet(resourceName, "user_name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccEKSAccessEntry_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var accessentry types.AccessEntry + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_eks_access_entry.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EKSEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAccessEntryDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAccessEntryConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccessEntryExists(ctx, resourceName, &accessentry), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfeks.ResourceAccessEntry(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccEKSAccessEntry_Disappears_cluster(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var accessentry types.AccessEntry + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_eks_access_entry.test" + clusterResourceName := "aws_eks_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EKSEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAccessEntryDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAccessEntryConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccessEntryExists(ctx, resourceName, &accessentry), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfeks.ResourceCluster(), clusterResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccEKSAccessEntry_tags(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var accessentry types.AccessEntry + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_eks_access_entry.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EKSEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAccessEntryDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAccessEntryConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccessEntryExists(ctx, resourceName, &accessentry), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAccessEntryConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccessEntryExists(ctx, resourceName, &accessentry), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAccessEntryConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccessEntryExists(ctx, resourceName, &accessentry), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckAccessEntryDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EKSClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_eks_access_entry" { + continue + } + + _, err := tfeks.FindAccessEntryByTwoPartKey(ctx, conn, rs.Primary.Attributes["cluster_name"], rs.Primary.Attributes["principal_arn"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("EKS Access Entry %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckAccessEntryExists(ctx context.Context, n string, v *types.AccessEntry) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).EKSClient(ctx) + + output, err := tfeks.FindAccessEntryByTwoPartKey(ctx, conn, rs.Primary.Attributes["cluster_name"], rs.Primary.Attributes["principal_arn"]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccAccessEntryConfig_base(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = < 0 { @@ -812,13 +917,12 @@ func expandOutpostConfigRequest(l []interface{}) *types.OutpostConfigRequest { return outpostConfigRequest } -func expandControlPlanePlacement(tfList []interface{}) *types.ControlPlanePlacementRequest { +func expandControlPlanePlacementRequest(tfList []interface{}) *types.ControlPlanePlacementRequest { if len(tfList) == 0 { return nil } tfMap, ok := tfList[0].(map[string]interface{}) - if !ok { return nil } @@ -832,44 +936,50 @@ func expandControlPlanePlacement(tfList []interface{}) *types.ControlPlanePlacem return apiObject } -func expandVPCConfigRequestForCreate(l []interface{}) *types.VpcConfigRequest { - if len(l) == 0 { +func expandVpcConfigRequest(tfList []interface{}) *types.VpcConfigRequest { // nosemgrep:ci.caps5-in-func-name + if len(tfList) == 0 { return nil } - m := l[0].(map[string]interface{}) + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } - vpcConfigRequest := &types.VpcConfigRequest{ - EndpointPrivateAccess: aws.Bool(m["endpoint_private_access"].(bool)), - EndpointPublicAccess: aws.Bool(m["endpoint_public_access"].(bool)), - SecurityGroupIds: flex.ExpandStringValueSet(m["security_group_ids"].(*schema.Set)), - SubnetIds: flex.ExpandStringValueSet(m["subnet_ids"].(*schema.Set)), + apiObject := &types.VpcConfigRequest{ + EndpointPrivateAccess: aws.Bool(tfMap["endpoint_private_access"].(bool)), + EndpointPublicAccess: aws.Bool(tfMap["endpoint_public_access"].(bool)), + SecurityGroupIds: flex.ExpandStringValueSet(tfMap["security_group_ids"].(*schema.Set)), + SubnetIds: flex.ExpandStringValueSet(tfMap["subnet_ids"].(*schema.Set)), } - if v, ok := m["public_access_cidrs"].(*schema.Set); ok && v.Len() > 0 { - vpcConfigRequest.PublicAccessCidrs = flex.ExpandStringValueSet(v) + if v, ok := tfMap["public_access_cidrs"].(*schema.Set); ok && v.Len() > 0 { + apiObject.PublicAccessCidrs = flex.ExpandStringValueSet(v) } - return vpcConfigRequest + return apiObject } func expandKubernetesNetworkConfigRequest(tfList []interface{}) *types.KubernetesNetworkConfigRequest { - tfMap, ok := tfList[0].(map[string]interface{}) + if len(tfList) == 0 { + return nil + } + tfMap, ok := tfList[0].(map[string]interface{}) if !ok { return nil } apiObject := &types.KubernetesNetworkConfigRequest{} - if v, ok := tfMap["service_ipv4_cidr"].(string); ok && v != "" { - apiObject.ServiceIpv4Cidr = aws.String(v) - } - if v, ok := tfMap["ip_family"].(string); ok && v != "" { apiObject.IpFamily = types.IpFamily(v) } + if v, ok := tfMap["service_ipv4_cidr"].(string); ok && v != "" { + apiObject.ServiceIpv4Cidr = aws.String(v) + } + return apiObject } @@ -927,7 +1037,23 @@ func flattenOIDC(oidc *types.OIDC) []map[string]interface{} { return []map[string]interface{}{m} } -func flattenEncryptionConfig(apiObjects []types.EncryptionConfig) []interface{} { +func flattenAccessConfigResponse(apiObject *types.AccessConfigResponse, bootstrapClusterCreatorAdminPermissions *bool) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "authentication_mode": apiObject.AuthenticationMode, + } + + if bootstrapClusterCreatorAdminPermissions != nil { + tfMap["bootstrap_cluster_creator_admin_permissions"] = aws.ToBool(bootstrapClusterCreatorAdminPermissions) + } + + return []interface{}{tfMap} +} + +func flattenEncryptionConfigs(apiObjects []types.EncryptionConfig) []interface{} { if len(apiObjects) == 0 { return nil } @@ -958,7 +1084,7 @@ func flattenProvider(apiObject *types.Provider) []interface{} { return []interface{}{tfMap} } -func flattenVPCConfigResponse(vpcConfig *types.VpcConfigResponse) []map[string]interface{} { +func flattenVPCConfigResponse(vpcConfig *types.VpcConfigResponse) []map[string]interface{} { // nosemgrep:ci.caps5-in-func-name if vpcConfig == nil { return []map[string]interface{}{} } diff --git a/internal/service/eks/cluster_data_source.go b/internal/service/eks/cluster_data_source.go index 29f947bd7ba..a3f6d6fc77b 100644 --- a/internal/service/eks/cluster_data_source.go +++ b/internal/service/eks/cluster_data_source.go @@ -20,6 +20,18 @@ func dataSourceCluster() *schema.Resource { ReadWithoutTimeout: dataSourceClusterRead, Schema: map[string]*schema.Schema{ + "access_config": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "authentication_mode": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, "arn": { Type: schema.TypeString, Computed: true, @@ -203,6 +215,9 @@ func dataSourceClusterRead(ctx context.Context, d *schema.ResourceData, meta int } d.SetId(name) + if err := d.Set("access_config", flattenAccessConfigResponse(cluster.AccessConfig, nil)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting access_config: %s", err) + } d.Set("arn", cluster.Arn) if err := d.Set("certificate_authority", flattenCertificate(cluster.CertificateAuthority)); err != nil { return sdkdiag.AppendErrorf(diags, "setting certificate_authority: %s", err) diff --git a/internal/service/eks/cluster_data_source_test.go b/internal/service/eks/cluster_data_source_test.go index 3f4a82aba5c..d06879bad63 100644 --- a/internal/service/eks/cluster_data_source_test.go +++ b/internal/service/eks/cluster_data_source_test.go @@ -27,10 +27,12 @@ func TestAccEKSClusterDataSource_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccClusterDataSourceConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "access_config.#", dataSourceResourceName, "access_config.#"), resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceResourceName, "arn"), resource.TestCheckResourceAttr(dataSourceResourceName, "certificate_authority.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "certificate_authority.0.data", dataSourceResourceName, "certificate_authority.0.data"), + resource.TestCheckNoResourceAttr(dataSourceResourceName, "cluster_id"), resource.TestCheckResourceAttrPair(resourceName, "created_at", dataSourceResourceName, "created_at"), resource.TestCheckResourceAttr(dataSourceResourceName, "enabled_cluster_log_types.#", "2"), resource.TestCheckTypeSetElemAttr(dataSourceResourceName, "enabled_cluster_log_types.*", "api"), @@ -43,6 +45,7 @@ func TestAccEKSClusterDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "kubernetes_network_config.0.ip_family", dataSourceResourceName, "kubernetes_network_config.0.ip_family"), resource.TestCheckResourceAttrPair(resourceName, "kubernetes_network_config.0.service_ipv4_cidr", dataSourceResourceName, "kubernetes_network_config.0.service_ipv4_cidr"), resource.TestCheckResourceAttrPair(resourceName, "kubernetes_network_config.0.service_ipv6_cidr", dataSourceResourceName, "kubernetes_network_config.0.service_ipv6_cidr"), + resource.TestCheckResourceAttrPair(resourceName, "outpost_config.#", dataSourceResourceName, "outpost_config.#"), resource.TestMatchResourceAttr(dataSourceResourceName, "platform_version", regexache.MustCompile(`^eks\.\d+$`)), resource.TestCheckResourceAttrPair(resourceName, "role_arn", dataSourceResourceName, "role_arn"), resource.TestCheckResourceAttrPair(resourceName, "status", dataSourceResourceName, "status"), diff --git a/internal/service/eks/cluster_test.go b/internal/service/eks/cluster_test.go index 200bbb9ca2f..0d35ee7010a 100644 --- a/internal/service/eks/cluster_test.go +++ b/internal/service/eks/cluster_test.go @@ -42,21 +42,26 @@ func TestAccEKSCluster_basic(t *testing.T) { CheckDestroy: testAccCheckClusterDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccClusterConfig_required(rName), - Check: resource.ComposeTestCheckFunc( + Config: testAccClusterConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckClusterExists(ctx, resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "access_config.#", "1"), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "eks", regexache.MustCompile(fmt.Sprintf("cluster/%s$", rName))), resource.TestCheckResourceAttr(resourceName, "certificate_authority.#", "1"), resource.TestCheckResourceAttrSet(resourceName, "certificate_authority.0.data"), resource.TestCheckNoResourceAttr(resourceName, "cluster_id"), + resource.TestCheckResourceAttrSet(resourceName, "created_at"), + resource.TestCheckResourceAttr(resourceName, "enabled_cluster_log_types.#", "0"), + resource.TestCheckResourceAttr(resourceName, "encryption_config.#", "0"), resource.TestMatchResourceAttr(resourceName, "endpoint", regexache.MustCompile(`^https://`)), resource.TestCheckResourceAttr(resourceName, "identity.#", "1"), resource.TestCheckResourceAttr(resourceName, "identity.0.oidc.#", "1"), resource.TestMatchResourceAttr(resourceName, "identity.0.oidc.0.issuer", regexache.MustCompile(`^https://`)), - resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "kubernetes_network_config.#", "1"), resource.TestCheckResourceAttrSet(resourceName, "kubernetes_network_config.0.service_ipv4_cidr"), resource.TestCheckResourceAttr(resourceName, "kubernetes_network_config.0.ip_family", "ipv4"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "outpost_config.#", "0"), resource.TestMatchResourceAttr(resourceName, "platform_version", regexache.MustCompile(`^eks\.\d+$`)), resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), resource.TestCheckResourceAttr(resourceName, "status", string(types.ClusterStatusActive)), @@ -92,7 +97,7 @@ func TestAccEKSCluster_disappears(t *testing.T) { CheckDestroy: testAccCheckClusterDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccClusterConfig_required(rName), + Config: testAccClusterConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterExists(ctx, resourceName, &cluster), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfeks.ResourceCluster(), resourceName), @@ -103,6 +108,84 @@ func TestAccEKSCluster_disappears(t *testing.T) { }) } +func TestAccEKSCluster_AccessConfig_create(t *testing.T) { + ctx := acctest.Context(t) + var cluster types.Cluster + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_eks_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EKSEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_accessConfig(rName, types.AuthenticationModeConfigMap), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "access_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "access_config.0.authentication_mode", string(types.AuthenticationModeConfigMap)), + resource.TestCheckResourceAttr(resourceName, "access_config.0.bootstrap_cluster_creator_admin_permissions", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +func TestAccEKSCluster_AccessConfig_update(t *testing.T) { + ctx := acctest.Context(t) + var cluster types.Cluster + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_eks_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EKSEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "access_config.#", "1"), + ), + }, + { + Config: testAccClusterConfig_accessConfig(rName, types.AuthenticationModeConfigMap), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "access_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "access_config.0.authentication_mode", string(types.AuthenticationModeConfigMap)), + resource.TestCheckResourceAttr(resourceName, "access_config.0.bootstrap_cluster_creator_admin_permissions", "true"), + ), + }, + { + Config: testAccClusterConfig_accessConfig(rName, types.AuthenticationModeApiAndConfigMap), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "access_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "access_config.0.authentication_mode", string(types.AuthenticationModeApiAndConfigMap)), + resource.TestCheckResourceAttr(resourceName, "access_config.0.bootstrap_cluster_creator_admin_permissions", "true"), + ), + }, + { + Config: testAccClusterConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "access_config.#", "1"), + ), + }, + }, + }) +} + func TestAccEKSCluster_Encryption_create(t *testing.T) { ctx := acctest.Context(t) var cluster types.Cluster @@ -149,7 +232,7 @@ func TestAccEKSCluster_Encryption_update(t *testing.T) { CheckDestroy: testAccCheckClusterDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccClusterConfig_required(rName), + Config: testAccClusterConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterExists(ctx, resourceName, &cluster1), resource.TestCheckResourceAttr(resourceName, "encryption_config.#", "0"), @@ -294,7 +377,7 @@ func TestAccEKSCluster_logging(t *testing.T) { }, // Disable all log types. { - Config: testAccClusterConfig_required(rName), + Config: testAccClusterConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterExists(ctx, resourceName, &cluster2), testAccCheckClusterNotRecreated(&cluster1, &cluster2), @@ -862,7 +945,7 @@ resource "aws_subnet" "test" { `, rName)) } -func testAccClusterConfig_required(rName string) string { +func testAccClusterConfig_basic(rName string) string { return acctest.ConfigCompose(testAccClusterConfig_base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { name = %[1]q @@ -877,6 +960,26 @@ resource "aws_eks_cluster" "test" { `, rName)) } +func testAccClusterConfig_accessConfig(rName string, authenticationMode types.AuthenticationMode) string { + return acctest.ConfigCompose(testAccClusterConfig_base(rName), fmt.Sprintf(` +resource "aws_eks_cluster" "test" { + name = %[1]q + role_arn = aws_iam_role.test.arn + + access_config { + bootstrap_cluster_creator_admin_permissions = true + authentication_mode = %[2]q + } + + vpc_config { + subnet_ids = aws_subnet.test[*].id + } + + depends_on = [aws_iam_role_policy_attachment.test-AmazonEKSClusterPolicy] +} +`, rName, authenticationMode)) +} + func testAccClusterConfig_version(rName, version string) string { return acctest.ConfigCompose(testAccClusterConfig_base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { diff --git a/internal/service/eks/clusters_data_source_test.go b/internal/service/eks/clusters_data_source_test.go index d4d02357276..043abb4f88c 100644 --- a/internal/service/eks/clusters_data_source_test.go +++ b/internal/service/eks/clusters_data_source_test.go @@ -34,7 +34,7 @@ func TestAccEKSClustersDataSource_basic(t *testing.T) { } func testAccClustersDataSourceConfig_basic(rName string) string { - return acctest.ConfigCompose(testAccClusterConfig_required(rName), ` + return acctest.ConfigCompose(testAccClusterConfig_basic(rName), ` data "aws_eks_clusters" "test" { depends_on = [aws_eks_cluster.test] } diff --git a/internal/service/eks/exports_test.go b/internal/service/eks/exports_test.go index ae7dc3b663b..b5cdf7e2aea 100644 --- a/internal/service/eks/exports_test.go +++ b/internal/service/eks/exports_test.go @@ -5,13 +5,17 @@ package eks // Exports for use in tests only. var ( - ResourceAddon = resourceAddon - ResourceCluster = resourceCluster - ResourceFargateProfile = resourceFargateProfile - ResourceIdentityProviderConfig = resourceIdentityProviderConfig - ResourceNodeGroup = resourceNodeGroup - ResourcePodIdentityAssociation = newPodIdentityAssociationResource + ResourceAccessEntry = resourceAccessEntry + ResourceAccessPolicyAssociation = resourceAccessPolicyAssociation + ResourceAddon = resourceAddon + ResourceCluster = resourceCluster + ResourceFargateProfile = resourceFargateProfile + ResourceIdentityProviderConfig = resourceIdentityProviderConfig + ResourceNodeGroup = resourceNodeGroup + ResourcePodIdentityAssociation = newPodIdentityAssociationResource + FindAccessEntryByTwoPartKey = findAccessEntryByTwoPartKey + FindAccessPolicyAssociationByThreePartKey = findAccessPolicyAssociationByThreePartKey FindAddonByTwoPartKey = findAddonByTwoPartKey FindClusterByName = findClusterByName FindFargateProfileByTwoPartKey = findFargateProfileByTwoPartKey diff --git a/internal/service/eks/find.go b/internal/service/eks/find.go new file mode 100644 index 00000000000..719f410ddb5 --- /dev/null +++ b/internal/service/eks/find.go @@ -0,0 +1,223 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package eks + +import ( + "context" + + "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/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/errs" +) + +func FindAddonByClusterNameAndAddonName(ctx context.Context, conn *eks.Client, clusterName, addonName string) (*types.Addon, error) { + input := &eks.DescribeAddonInput{ + AddonName: aws.String(addonName), + ClusterName: aws.String(clusterName), + } + + output, err := conn.DescribeAddon(ctx, input) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Addon == nil { + return nil, &retry.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Addon, nil +} + +func FindAddonUpdateByClusterNameAddonNameAndID(ctx context.Context, conn *eks.Client, clusterName, addonName, id string) (*types.Update, error) { + input := &eks.DescribeUpdateInput{ + AddonName: aws.String(addonName), + Name: aws.String(clusterName), + UpdateId: aws.String(id), + } + + output, err := conn.DescribeUpdate(ctx, input) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Update == nil { + return nil, &retry.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Update, nil +} + +func FindAddonVersionByAddonNameAndKubernetesVersion(ctx context.Context, conn *eks.Client, addonName, kubernetesVersion string, mostRecent bool) (*types.AddonVersionInfo, error) { + input := &eks.DescribeAddonVersionsInput{ + AddonName: aws.String(addonName), + KubernetesVersion: aws.String(kubernetesVersion), + } + var version *types.AddonVersionInfo + + output, err := conn.DescribeAddonVersions(ctx, input) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Addons == nil { + return nil, &retry.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return version, nil +} + +func FindFargateProfileByClusterNameAndFargateProfileName(ctx context.Context, conn *eks.Client, clusterName, fargateProfileName string) (*types.FargateProfile, error) { + input := &eks.DescribeFargateProfileInput{ + ClusterName: aws.String(clusterName), + FargateProfileName: aws.String(fargateProfileName), + } + + output, err := conn.DescribeFargateProfile(ctx, input) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.FargateProfile == nil { + return nil, &retry.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.FargateProfile, nil +} + +func FindNodegroupByClusterNameAndNodegroupName(ctx context.Context, conn *eks.Client, clusterName, nodeGroupName string) (*types.Nodegroup, error) { + input := &eks.DescribeNodegroupInput{ + ClusterName: aws.String(clusterName), + NodegroupName: aws.String(nodeGroupName), + } + + output, err := conn.DescribeNodegroup(ctx, input) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Nodegroup == nil { + return nil, &retry.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Nodegroup, nil +} + +func FindNodegroupUpdateByClusterNameNodegroupNameAndID(ctx context.Context, conn *eks.Client, clusterName, nodeGroupName, id string) (*types.Update, error) { + input := &eks.DescribeUpdateInput{ + Name: aws.String(clusterName), + NodegroupName: aws.String(nodeGroupName), + UpdateId: aws.String(id), + } + + output, err := conn.DescribeUpdate(ctx, input) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Update == nil { + return nil, &retry.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Update, nil +} + +func FindOIDCIdentityProviderConfigByClusterNameAndConfigName(ctx context.Context, conn *eks.Client, clusterName, configName string) (*types.OidcIdentityProviderConfig, error) { + input := &eks.DescribeIdentityProviderConfigInput{ + ClusterName: aws.String(clusterName), + IdentityProviderConfig: &types.IdentityProviderConfig{ + Name: aws.String(configName), + Type: aws.String(IdentityProviderConfigTypeOIDC), + }, + } + + output, err := conn.DescribeIdentityProviderConfig(ctx, input) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.IdentityProviderConfig == nil || output.IdentityProviderConfig.Oidc == nil { + return nil, &retry.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.IdentityProviderConfig.Oidc, nil +} diff --git a/internal/service/eks/service_package_gen.go b/internal/service/eks/service_package_gen.go index f798ef47f68..b250698fdad 100644 --- a/internal/service/eks/service_package_gen.go +++ b/internal/service/eks/service_package_gen.go @@ -32,6 +32,11 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { return []*types.ServicePackageSDKDataSource{ + { + Factory: dataSourceAccessEntry, + TypeName: "aws_eks_access_entry", + Name: "Access Entry", + }, { Factory: dataSourceAddon, TypeName: "aws_eks_addon", @@ -65,6 +70,19 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ + { + Factory: resourceAccessEntry, + TypeName: "aws_eks_access_entry", + Name: "Access Entry", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "access_entry_arn", + }, + }, + { + Factory: resourceAccessPolicyAssociation, + TypeName: "aws_eks_access_policy_association", + Name: "Access Policy Association", + }, { Factory: resourceAddon, TypeName: "aws_eks_addon", diff --git a/website/docs/d/eks_access_entry.html.markdown b/website/docs/d/eks_access_entry.html.markdown new file mode 100644 index 00000000000..b19c8149375 --- /dev/null +++ b/website/docs/d/eks_access_entry.html.markdown @@ -0,0 +1,41 @@ +--- +subcategory: "EKS (Elastic Kubernetes)" +layout: "aws" +page_title: "AWS: aws_eks_access_entry" +description: |- + Access Entry Configurations for an EKS Cluster. +--- + +# Data Source: aws_eks_access_entry + +Access Entry Configurations for an EKS Cluster. + +## Example Usage + +```terraform +data "aws_eks_access_entry" "example" { + cluster_name = aws_eks_cluster.example.name + principal_arn = aws_iam_role.example.arn +} + +output "eks_access_entry_outputs" { + value = aws_eks_access_entry.example +} +``` + +## Argument Reference + +* `cluster_name` – (Required) Name of the EKS Cluster. Must be between 1-100 characters in length. Must begin with an alphanumeric character, and must only contain alphanumeric characters, dashes and underscores (`^[0-9A-Za-z][A-Za-z0-9\-_]+$`). +* `principal_arn` – (Required) The IAM Princial ARN which requires Authentication access to the EKS cluster. + +## Attribute Reference + +This data source exports the following attributes in addition to the arguments above: + +* `access_entry_arn` - Amazon Resource Name (ARN) of the Access Entry. +* `created_at` - Date and time in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) that the EKS add-on was created. +* `kubernetes_groups` – List of string which can optionally specify the Kubernetes groups the user would belong to when creating an access entry. +* `modified_at` - Date and time in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) that the EKS add-on was updated. +* `user_name` - Defaults to principal ARN if user is principal else defaults to assume-role/session-name is role is used. +* `type` - Defaults to STANDARD which provides the standard workflow. EC2_LINUX, EC2_WINDOWS, FARGATE_LINUX types disallow users to input a username or groups, and prevent associations. +* `tags_all` - (Optional) Key-value map of resource tags, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). diff --git a/website/docs/d/eks_cluster.html.markdown b/website/docs/d/eks_cluster.html.markdown index cea2d6c498e..d0e9ab35250 100644 --- a/website/docs/d/eks_cluster.html.markdown +++ b/website/docs/d/eks_cluster.html.markdown @@ -41,6 +41,9 @@ This data source exports the following attributes in addition to the arguments a * `id` - Name of the cluster * `arn` - ARN of the cluster. +* `access_config` - Configuration block for access config. + * `authentication_mode` - Values returned are `CONFIG_MAP`, `API` or `API_AND_CONFIG_MAP` + * `bootstrap_cluster_creator_admin_permissions` - Default to `true`. * `certificate_authority` - Nested attribute containing `certificate-authority-data` for your cluster. * `data` - The base64 encoded certificate data required to communicate with your cluster. Add this to the `certificate-authority-data` section of the `kubeconfig` file for your cluster. * `cluster_id` - The ID of your local Amazon EKS cluster on the AWS Outpost. This attribute isn't available for an AWS EKS cluster on AWS cloud. diff --git a/website/docs/r/eks_access_entry.html.markdown b/website/docs/r/eks_access_entry.html.markdown new file mode 100644 index 00000000000..259802f174b --- /dev/null +++ b/website/docs/r/eks_access_entry.html.markdown @@ -0,0 +1,69 @@ +--- +subcategory: "EKS (Elastic Kubernetes)" +layout: "aws" +page_title: "AWS: aws_eks_access_entry" +description: |- + Access Entry Configurations for an EKS Cluster. +--- + +# Resource: aws_eks_access_entry + +Access Entry Configurations for an EKS Cluster. + +## Example Usage + +```terraform +resource "aws_eks_access_entry" "example" { + cluster_name = aws_eks_cluster.example.name + principal_arn = aws_iam_role.example.arn + kubernetes_groups = ["group-1", "group-2"] +} +``` + +## Argument Reference + +The following arguments are required: + +* `cluster_name` – (Required) Name of the EKS Cluster. Must be between 1-100 characters in length. Must begin with an alphanumeric character, and must only contain alphanumeric characters, dashes and underscores (`^[0-9A-Za-z][A-Za-z0-9\-_]+$`). +* `principal_arn` – (Required) The IAM Princial ARN which requires Authentication access to the EKS cluster. + +The following arguments are optional: + +* `kubernetes_groups` – (Optional) List of string which can optionally specify the Kubernetes groups the user would belong to when creating an access entry. +* `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. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `access_entry_arn` - Amazon Resource Name (ARN) of the Access Entry. +* `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. +* `user_name` - Defaults to principal ARN if user is principal else defaults to assume-role/session-name is role is used. +* `type` - Defaults to STANDARD which provides the standard workflow. EC2_LINUX, EC2_WINDOWS, FARGATE_LINUX types disallow users to input a username or groups, and prevent associations. +* `tags_all` - (Optional) Key-value map of resource tags, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `20m`) +* `update` - (Default `20m`) +* `delete` - (Default `40m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import EKS add-on using the `cluster_name` and `principal_arn` separated by a colon (`:`). For example: + +```terraform +import { + to = aws_eks_access_entry.my_eks_entry + id = "my_cluster_name:my_principal_arn" +} +``` + +Using `terraform import`, import EKS access entry using the `cluster_name` and `principal_arn` separated by a colon (`:`). For example: + +```console +% terraform import aws_eks_access_entry.my_eks_access_entry my_cluster_name:my_principal_arn +``` diff --git a/website/docs/r/eks_access_policy_association.html.markdown b/website/docs/r/eks_access_policy_association.html.markdown new file mode 100644 index 00000000000..1095c965da3 --- /dev/null +++ b/website/docs/r/eks_access_policy_association.html.markdown @@ -0,0 +1,70 @@ +--- +subcategory: "EKS (Elastic Kubernetes)" +layout: "aws" +page_title: "AWS: aws_eks_access_policy_associattion" +description: |- + Access Entry Policy Association for an EKS Cluster. +--- + +# Resource: aws_eks_access_policy_association + +Access Entry Policy Association for an EKS Cluster. + +## Example Usage + +```terraform +resource "aws_eks_access_policy_association" "example" { + cluster_name = aws_eks_cluster.example.name + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy" + principal_arn = aws_iam_user.example.arn + + access_scope = { + type = "namespace" + namespaces = ["example-namespace"] + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `cluster_name` – (Required) Name of the EKS Cluster. Must be between 1-100 characters in length. Must begin with an alphanumeric character, and must only contain alphanumeric characters, dashes and underscores (`^[0-9A-Za-z][A-Za-z0-9\-_]+$`). +* `policy_arn` – (Required) The ARN of the access policy that you're associating. +* `principal_arn` – (Required) The IAM Princial ARN which requires Authentication access to the EKS cluster. +* `access_scope` – (Required) The configuration block to determine the scope of the access. + * `type` - (Required) Valid values are `namespace` or `cluster`. + * `namespaces` - (Optional) The namespaces to which the access scope applies when type is namespace. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `associated_access_policy` - Configuraion block which consists of + * `associated_at` - Date and time in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) that the policy was associated. + * `modified_at` - Date and time in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) that the policy was updated. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `20m`) +* `update` - (Default `20m`) +* `delete` - (Default `40m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import EKS add-on using the `cluster_name`, `policy_arn` and `principal_arn` separated by a colon (`:`). For example: + +```terraform +import { + to = aws_eks_access_policy_association.my_eks_entry + id = "my_cluster_name:my_policy_arn:my_principal_arn" +} +``` + +Using `terraform import`, import EKS access entry using the `cluster_name` `policy_arn` and `principal_arn` separated by a colon (`:`). For example: + +```console +% terraform import aws_eks_access_policy_association.my_eks_access_entry my_cluster_name:my_policy_arn:my_principal_arn +``` diff --git a/website/docs/r/eks_cluster.html.markdown b/website/docs/r/eks_cluster.html.markdown index ee79fa371f0..a38bff5c2d4 100644 --- a/website/docs/r/eks_cluster.html.markdown +++ b/website/docs/r/eks_cluster.html.markdown @@ -177,6 +177,31 @@ resource "aws_eks_cluster" "example" { } ``` +### EKS Cluster with Access Config + +```terraform +resource "aws_iam_role" "example" { + assume_role_policy = data.aws_iam_policy_document.example_assume_role_policy.json + name = "example" +} + +resource "aws_eks_cluster" "example" { + name = "example-cluster" + role_arn = aws_iam_role.example.arn + + vpc_config { + endpoint_private_access = true + endpoint_public_access = false + # ... other configuration ... + } + + access_config { + authentication_mode = "CONFIG_MAP" + bootstrap_cluster_creator_admin_permissions = true + } +} +``` + After adding inline IAM Policies (e.g., [`aws_iam_role_policy` resource](/docs/providers/aws/r/iam_role_policy.html)) or attaching IAM Policies (e.g., [`aws_iam_policy` resource](/docs/providers/aws/r/iam_policy.html) and [`aws_iam_role_policy_attachment` resource](/docs/providers/aws/r/iam_role_policy_attachment.html)) with the desired permissions to the IAM Role, annotate the Kubernetes service account (e.g., [`kubernetes_service_account` resource](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service_account)) and recreate any pods. ## Argument Reference @@ -189,6 +214,7 @@ The following arguments are required: The following arguments are optional: +* `access_config` - (Optional) Configuration block for the access config associated with your cluster, see [Amazon EKS Control Plane Logging](https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html). * `enabled_cluster_log_types` - (Optional) List of the desired control plane logging to enable. For more information, see [Amazon EKS Control Plane Logging](https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html). * `encryption_config` - (Optional) Configuration block with encryption configuration for the cluster. Only available on Kubernetes 1.13 and above clusters created after March 6, 2020. Detailed below. * `kubernetes_network_config` - (Optional) Configuration block with kubernetes network configuration for the cluster. Detailed below. If removed, Terraform will only perform drift detection if a configuration value is provided. @@ -198,6 +224,13 @@ The following arguments are optional: ### encryption_config +The `access_config` configuration block supports the following arguments: + +* `authentication_mode` - (Optional) The authentication mode for the cluster. Valid values are `CONFIG_MAP`, `API` or `API_AND_CONFIG_MAP` +* `bootstrap_cluster_creator_admin_permissions` - (Optional) Whether or not to bootstrap the access config values to the cluster. Default is `true`. + +### encryption_config + The `encryption_config` configuration block supports the following arguments: * `provider` - (Required) Configuration block with provider for encryption. Detailed below.