From cdeb9cb010d198995d98151ce3b708fa54a0f4c6 Mon Sep 17 00:00:00 2001 From: Lily Date: Wed, 28 Apr 2021 12:32:36 -0700 Subject: [PATCH 01/20] EKS supports adding KMS envelope encryption to existing clusters https://aws.amazon.com/about-aws/whats-new/2021/03/amazon-eks-supports-adding-kms-envelope-encryption-to-existing-clusters/ Fixes #17952 --- aws/resource_aws_eks_cluster.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index 683d67b04a1..fb930a6ab3c 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -60,20 +60,17 @@ func resourceAwsEksCluster() *schema.Resource { Type: schema.TypeList, MaxItems: 1, Optional: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "provider": { Type: schema.TypeList, MaxItems: 1, Required: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key_arn": { Type: schema.TypeString, Required: true, - ForceNew: true, }, }, }, @@ -82,7 +79,6 @@ func resourceAwsEksCluster() *schema.Resource { Type: schema.TypeSet, MinItems: 1, Required: true, - ForceNew: true, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.StringInSlice([]string{ From 8ac9ea3eb925647e06799ed6f464d9ccdf6245e3 Mon Sep 17 00:00:00 2001 From: Lily Date: Mon, 3 May 2021 10:00:35 -0700 Subject: [PATCH 02/20] Add api calls to update encryption config --- aws/resource_aws_eks_cluster.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index fb930a6ab3c..09492a97ee3 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -388,6 +388,31 @@ func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error } } + if d.HasChange("encryption_config") { + input := &eks.AssociateEncryptionConfigInput{ + ClusterName: aws.String(d.Id()), + EncryptionConfig: expandEksEncryptionConfig(d.Get("encryption_config").([]interface{})), + } + + log.Printf("[DEBUG] Updating EKS Cluster (%s) version: %s", d.Id(), input) + output, err := conn.AssociateEncryptionConfig(input) + + if err != nil { + return fmt.Errorf("error updating EKS Cluster (%s) version: %s", d.Id(), err) + } + + if output == nil || output.Update == nil || output.Update.Id == nil { + return fmt.Errorf("error determining EKS Cluster (%s) version update ID: empty response", d.Id()) + } + + updateID := aws.StringValue(output.Update.Id) + + err = waitForUpdateEksCluster(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return fmt.Errorf("error waiting for EKS Cluster (%s) version update (%s): %s", d.Id(), updateID, err) + } + } + if d.HasChange("version") { input := &eks.UpdateClusterVersionInput{ Name: aws.String(d.Id()), From 8ee979ac5da8021a5c4b7162d2c22294bba39167 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 17 Jun 2021 15:49:44 -0400 Subject: [PATCH 03/20] Add CHANGELOG entry. --- .changelog/19144.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/19144.txt diff --git a/.changelog/19144.txt b/.changelog/19144.txt new file mode 100644 index 00000000000..2356f160f69 --- /dev/null +++ b/.changelog/19144.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_eks_cluster: Allow updates to `encryption_config` +``` \ No newline at end of file From f318deca584179a148924d60b1c28bb325c4d706 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 17 Jun 2021 16:41:31 -0400 Subject: [PATCH 04/20] Add 'tfeks.Resources_Values()'. --- aws/internal/service/eks/enum.go | 11 ++++++++ aws/resource_aws_eks_cluster.go | 45 ++++++++++++++------------------ 2 files changed, 31 insertions(+), 25 deletions(-) create mode 100644 aws/internal/service/eks/enum.go diff --git a/aws/internal/service/eks/enum.go b/aws/internal/service/eks/enum.go new file mode 100644 index 00000000000..8e0f2c21f43 --- /dev/null +++ b/aws/internal/service/eks/enum.go @@ -0,0 +1,11 @@ +package eks + +const ( + ResourcesSecrets = "secrets" +) + +func Resources_Values() []string { + return []string{ + ResourcesSecrets, + } +} diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index 09492a97ee3..42147bcb148 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" ) @@ -22,7 +23,6 @@ func resourceAwsEksCluster() *schema.Resource { Read: resourceAwsEksClusterRead, Update: resourceAwsEksClusterUpdate, Delete: resourceAwsEksClusterDelete, - Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -56,6 +56,14 @@ func resourceAwsEksCluster() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "enabled_cluster_log_types": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(eks.LogType_Values(), true), + }, + }, "encryption_config": { Type: schema.TypeList, MaxItems: 1, @@ -80,10 +88,8 @@ func resourceAwsEksCluster() *schema.Resource { MinItems: 1, Required: true, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{ - "secrets", - }, false), + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(tfeks.Resources_Values(), false), }, }, }, @@ -113,7 +119,6 @@ func resourceAwsEksCluster() *schema.Resource { }, }, }, - "kubernetes_network_config": { Type: schema.TypeList, Optional: true, @@ -134,7 +139,6 @@ func resourceAwsEksCluster() *schema.Resource { }, }, }, - "name": { Type: schema.TypeString, Required: true, @@ -183,6 +187,15 @@ func resourceAwsEksCluster() *schema.Resource { Optional: true, Default: true, }, + "public_access_cidrs": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCIDRNetworkAddress, + }, + }, "security_group_ids": { Type: schema.TypeSet, Optional: true, @@ -196,15 +209,6 @@ func resourceAwsEksCluster() *schema.Resource { MinItems: 1, Elem: &schema.Schema{Type: schema.TypeString}, }, - "public_access_cidrs": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validateCIDRNetworkAddress, - }, - }, "vpc_id": { Type: schema.TypeString, Computed: true, @@ -212,15 +216,6 @@ func resourceAwsEksCluster() *schema.Resource { }, }, }, - "enabled_cluster_log_types": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(eks.LogType_Values(), true), - }, - Set: schema.HashString, - }, }, } } From 18655a1579c109c163c41abdb28b051af91f0724 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 17 Jun 2021 17:17:33 -0400 Subject: [PATCH 05/20] r/aws_eks_cluster: Call resource Delete method in acceptance test sweeper. --- aws/resource_aws_eks_cluster_test.go | 35 ++++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/aws/resource_aws_eks_cluster_test.go b/aws/resource_aws_eks_cluster_test.go index 74dcbaf9d63..667182ca2ac 100644 --- a/aws/resource_aws_eks_cluster_test.go +++ b/aws/resource_aws_eks_cluster_test.go @@ -35,36 +35,41 @@ func testSweepEksClusters(region string) error { return fmt.Errorf("error getting client: %s", err) } conn := client.(*AWSClient).eksconn - - var errors error input := &eks.ListClustersInput{} + var sweeperErrs *multierror.Error + err = conn.ListClustersPages(input, func(page *eks.ListClustersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + for _, cluster := range page.Clusters { - name := aws.StringValue(cluster) + r := resourceAwsEksCluster() + d := r.Data(nil) + d.SetId(aws.StringValue(cluster)) + + err = r.Delete(d, client) - log.Printf("[INFO] Deleting EKS Cluster: %s", name) - err := deleteEksCluster(conn, name) - if err != nil { - errors = multierror.Append(errors, fmt.Errorf("error deleting EKS Cluster %q: %w", name, err)) - continue - } - err = waitForDeleteEksCluster(conn, name, 15*time.Minute) if err != nil { - errors = multierror.Append(errors, fmt.Errorf("error waiting for EKS Cluster %q deletion: %w", name, err)) + log.Printf("[ERROR] %s", err) + sweeperErrs = multierror.Append(sweeperErrs, err) continue } } - return true + + return !lastPage }) + if testSweepSkipSweepError(err) { log.Printf("[WARN] Skipping EKS Clusters sweep for %s: %s", region, err) - return errors // In case we have completed some pages, but had errors + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors } + if err != nil { - errors = multierror.Append(errors, fmt.Errorf("error retrieving EKS Clusters: %w", err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Clusters: %w", err)) } - return errors + return sweeperErrs.ErrorOrNil() } func TestAccAWSEksCluster_basic(t *testing.T) { From 2687259ea812f150a5c26b2c54e5484e48bdc195 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 17 Jun 2021 17:34:32 -0400 Subject: [PATCH 06/20] r/aws_eks_cluster: Parallelize acceptance test sweeper. --- aws/resource_aws_eks_cluster_test.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_eks_cluster_test.go b/aws/resource_aws_eks_cluster_test.go index 667182ca2ac..a21177bab86 100644 --- a/aws/resource_aws_eks_cluster_test.go +++ b/aws/resource_aws_eks_cluster_test.go @@ -11,7 +11,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" - multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -36,7 +35,7 @@ func testSweepEksClusters(region string) error { } conn := client.(*AWSClient).eksconn input := &eks.ListClustersInput{} - var sweeperErrs *multierror.Error + sweepResources := make([]*testSweepResource, 0) err = conn.ListClustersPages(input, func(page *eks.ListClustersOutput, lastPage bool) bool { if page == nil { @@ -48,13 +47,7 @@ func testSweepEksClusters(region string) error { d := r.Data(nil) d.SetId(aws.StringValue(cluster)) - err = r.Delete(d, client) - - if err != nil { - log.Printf("[ERROR] %s", err) - sweeperErrs = multierror.Append(sweeperErrs, err) - continue - } + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) } return !lastPage @@ -62,14 +55,20 @@ func testSweepEksClusters(region string) error { if testSweepSkipSweepError(err) { log.Printf("[WARN] Skipping EKS Clusters sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + return nil } if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Clusters: %w", err)) + return fmt.Errorf("error listing EKS Clusters (%s): %w", region, err) } - return sweeperErrs.ErrorOrNil() + err = testSweepResourceOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping EKS Clusters (%s): %w", region, err) + } + + return nil } func TestAccAWSEksCluster_basic(t *testing.T) { From 2ac70f78df280fe03c2b57b84c2cbb1429a81933 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 18 Jun 2021 08:56:31 -0400 Subject: [PATCH 07/20] r/aws_eks_cluster: Add and use internal finder package. Add '_disappears' test (#13826). Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSEksCluster_basic\|TestAccAWSEksCluster_disappears' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSEksCluster_basic\|TestAccAWSEksCluster_disappears -timeout 180m === RUN TestAccAWSEksCluster_basic === PAUSE TestAccAWSEksCluster_basic === RUN TestAccAWSEksCluster_disappears === PAUSE TestAccAWSEksCluster_disappears === CONT TestAccAWSEksCluster_basic === CONT TestAccAWSEksCluster_disappears --- PASS: TestAccAWSEksCluster_basic (673.27s) --- PASS: TestAccAWSEksCluster_disappears (681.94s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 685.380s --- aws/internal/service/eks/finder/finder.go | 38 +++++++ aws/resource_aws_eks_cluster.go | 130 ++++++++++++---------- aws/resource_aws_eks_cluster_test.go | 69 ++++++------ 3 files changed, 144 insertions(+), 93 deletions(-) create mode 100644 aws/internal/service/eks/finder/finder.go diff --git a/aws/internal/service/eks/finder/finder.go b/aws/internal/service/eks/finder/finder.go new file mode 100644 index 00000000000..c6a5565bda9 --- /dev/null +++ b/aws/internal/service/eks/finder/finder.go @@ -0,0 +1,38 @@ +package finder + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/eks" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func ClusterByName(conn *eks.EKS, name string) (*eks.Cluster, error) { + input := &eks.DescribeClusterInput{ + Name: aws.String(name), + } + + output, err := conn.DescribeCluster(input) + + // Sometimes the EKS API returns the ResourceNotFound error in this form: + // ClientException: No cluster found for name: tf-acc-test-0o1f8 + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, eks.ErrCodeClientException, "No cluster found for name:") { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Cluster == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Cluster, nil +} diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index 42147bcb148..620b754e09e 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -9,12 +9,15 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsEksCluster() *schema.Resource { @@ -301,50 +304,53 @@ func resourceAwsEksClusterRead(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - input := &eks.DescribeClusterInput{ - Name: aws.String(d.Id()), - } + cluster, err := finder.ClusterByName(conn, d.Id()) - log.Printf("[DEBUG] Reading EKS Cluster: %s", input) - output, err := conn.DescribeCluster(input) - if err != nil { - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - log.Printf("[WARN] EKS Cluster (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - return fmt.Errorf("error reading EKS Cluster (%s): %s", d.Id(), err) - } - - cluster := output.Cluster - if cluster == nil { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] EKS Cluster (%s) not found, removing from state", d.Id()) d.SetId("") return nil } + if err != nil { + return fmt.Errorf("error reading EKS Cluster (%s): %w", d.Id(), err) + } + d.Set("arn", cluster.Arn) if err := d.Set("certificate_authority", flattenEksCertificate(cluster.CertificateAuthority)); err != nil { - return fmt.Errorf("error setting certificate_authority: %s", err) + return fmt.Errorf("error setting certificate_authority: %w", err) } d.Set("created_at", aws.TimeValue(cluster.CreatedAt).String()) + if err := d.Set("enabled_cluster_log_types", flattenEksEnabledLogTypes(cluster.Logging)); err != nil { + return fmt.Errorf("error setting enabled_cluster_log_types: %w", err) + } + if err := d.Set("encryption_config", flattenEksEncryptionConfig(cluster.EncryptionConfig)); err != nil { - return fmt.Errorf("error setting encryption_config: %s", err) + return fmt.Errorf("error setting encryption_config: %w", err) } d.Set("endpoint", cluster.Endpoint) if err := d.Set("identity", flattenEksIdentity(cluster.Identity)); err != nil { - return fmt.Errorf("error setting identity: %s", err) + return fmt.Errorf("error setting identity: %w", err) + } + + if err := d.Set("kubernetes_network_config", flattenEksNetworkConfig(cluster.KubernetesNetworkConfig)); err != nil { + return fmt.Errorf("error setting kubernetes_network_config: %w", err) } d.Set("name", cluster.Name) d.Set("platform_version", cluster.PlatformVersion) d.Set("role_arn", cluster.RoleArn) d.Set("status", cluster.Status) + d.Set("version", cluster.Version) + + if err := d.Set("vpc_config", flattenEksVpcConfigResponse(cluster.ResourcesVpcConfig)); err != nil { + return fmt.Errorf("error setting vpc_config: %w", err) + } tags := keyvaluetags.EksKeyValueTags(cluster.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) @@ -357,32 +363,12 @@ func resourceAwsEksClusterRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting tags_all: %w", err) } - d.Set("version", cluster.Version) - if err := d.Set("enabled_cluster_log_types", flattenEksEnabledLogTypes(cluster.Logging)); err != nil { - return fmt.Errorf("error setting enabled_cluster_log_types: %s", err) - } - - if err := d.Set("vpc_config", flattenEksVpcConfigResponse(cluster.ResourcesVpcConfig)); err != nil { - return fmt.Errorf("error setting vpc_config: %s", err) - } - - if err := d.Set("kubernetes_network_config", flattenEksNetworkConfig(cluster.KubernetesNetworkConfig)); err != nil { - return fmt.Errorf("error setting kubernetes_network_config: %w", err) - } - return nil } func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).eksconn - if d.HasChange("tags_all") { - o, n := d.GetChange("tags_all") - if err := keyvaluetags.EksUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating tags: %s", err) - } - } - if d.HasChange("encryption_config") { input := &eks.AssociateEncryptionConfigInput{ ClusterName: aws.String(d.Id()), @@ -484,6 +470,13 @@ func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error } } + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := keyvaluetags.EksUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating tags: %w", err) + } + } + return resourceAwsEksClusterRead(d, meta) } @@ -491,35 +484,27 @@ func resourceAwsEksClusterDelete(d *schema.ResourceData, meta interface{}) error conn := meta.(*AWSClient).eksconn log.Printf("[DEBUG] Deleting EKS Cluster: %s", d.Id()) - err := deleteEksCluster(conn, d.Id()) - if err != nil { - return fmt.Errorf("error deleting EKS Cluster (%s): %s", d.Id(), err) - } + _, err := conn.DeleteCluster(&eks.DeleteClusterInput{ + Name: aws.String(d.Id()), + }) - err = waitForDeleteEksCluster(conn, d.Id(), d.Timeout(schema.TimeoutDelete)) - if err != nil { - return fmt.Errorf("error waiting for EKS Cluster (%s) deletion: %s", d.Id(), err) + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { + return nil } - return nil -} + // Sometimes the EKS API returns the ResourceNotFound error in this form: + // ClientException: No cluster found for name: tf-acc-test-0o1f8 + if tfawserr.ErrMessageContains(err, eks.ErrCodeClientException, "No cluster found for name:") { + return nil + } -func deleteEksCluster(conn *eks.EKS, clusterName string) error { - input := &eks.DeleteClusterInput{ - Name: aws.String(clusterName), + if err != nil { + return fmt.Errorf("error deleting EKS Cluster (%s): %s", d.Id(), err) } - _, err := conn.DeleteCluster(input) + err = waitForDeleteEksCluster(conn, d.Id(), d.Timeout(schema.TimeoutDelete)) if err != nil { - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - return nil - } - // Sometimes the EKS API returns the ResourceNotFound error in this form: - // ClientException: No cluster found for name: tf-acc-test-0o1f8 - if isAWSErr(err, eks.ErrCodeClientException, "No cluster found for name:") { - return nil - } - return err + return fmt.Errorf("error waiting for EKS Cluster (%s) deletion: %s", d.Id(), err) } return nil @@ -798,6 +783,29 @@ func refreshEksUpdateStatus(conn *eks.EKS, clusterName, updateID string) resourc } func waitForDeleteEksCluster(conn *eks.EKS, clusterName string, timeout time.Duration) error { + /* + TODO + + // Handle eventual consistency + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + output, err := conn.DescribeCluster(&eks.DescribeClusterInput{ + Name: aws.String(rs.Primary.ID), + }) + + if err != nil { + if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { + return nil + } + return resource.NonRetryableError(err) + } + + if output != nil && output.Cluster != nil && aws.StringValue(output.Cluster.Name) == rs.Primary.ID { + return resource.RetryableError(fmt.Errorf("EKS Cluster %s still exists", rs.Primary.ID)) + } + + return nil + }) + */ stateConf := resource.StateChangeConf{ Pending: []string{ eks.ClusterStatusActive, diff --git a/aws/resource_aws_eks_cluster_test.go b/aws/resource_aws_eks_cluster_test.go index a21177bab86..d255f5e1a94 100644 --- a/aws/resource_aws_eks_cluster_test.go +++ b/aws/resource_aws_eks_cluster_test.go @@ -7,13 +7,14 @@ import ( "regexp" "strings" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func init() { @@ -73,7 +74,6 @@ func testSweepEksClusters(region string) error { func TestAccAWSEksCluster_basic(t *testing.T) { var cluster eks.Cluster - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) resourceName := "aws_eks_cluster.test" @@ -119,6 +119,29 @@ func TestAccAWSEksCluster_basic(t *testing.T) { }) } +func TestAccAWSEksCluster_disappears(t *testing.T) { + var cluster eks.Cluster + rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + resourceName := "aws_eks_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEks(t) }, + ErrorCheck: testAccErrorCheck(t, eks.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEksClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEksClusterConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEksClusterExists(resourceName, &cluster), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEksCluster(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func TestAccAWSEksCluster_EncryptionConfig(t *testing.T) { var cluster eks.Cluster kmsKeyResourceName := "aws_kms_key.test" @@ -514,22 +537,14 @@ func testAccCheckAWSEksClusterExists(resourceName string, cluster *eks.Cluster) } conn := testAccProvider.Meta().(*AWSClient).eksconn - output, err := conn.DescribeCluster(&eks.DescribeClusterInput{ - Name: aws.String(rs.Primary.ID), - }) - if err != nil { - return err - } - if output == nil || output.Cluster == nil { - return fmt.Errorf("EKS Cluster (%s) not found", rs.Primary.ID) - } + output, err := finder.ClusterByName(conn, rs.Primary.ID) - if aws.StringValue(output.Cluster.Name) != rs.Primary.ID { - return fmt.Errorf("EKS Cluster (%s) not found", rs.Primary.ID) + if err != nil { + return err } - *cluster = *output.Cluster + *cluster = *output return nil } @@ -543,27 +558,17 @@ func testAccCheckAWSEksClusterDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).eksconn - // Handle eventual consistency - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - output, err := conn.DescribeCluster(&eks.DescribeClusterInput{ - Name: aws.String(rs.Primary.ID), - }) + _, err := finder.ClusterByName(conn, rs.Primary.ID) - if err != nil { - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - return nil - } - return resource.NonRetryableError(err) - } - - if output != nil && output.Cluster != nil && aws.StringValue(output.Cluster.Name) == rs.Primary.ID { - return resource.RetryableError(fmt.Errorf("EKS Cluster %s still exists", rs.Primary.ID)) - } + if tfresource.NotFound(err) { + continue + } - return nil - }) + if err != nil { + return err + } - return err + return fmt.Errorf("EKS Cluster %s still exists", rs.Primary.ID) } return nil From f4ac852ac0d0cc219783290df3a87f4c7516a0ae Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 18 Jun 2021 09:56:30 -0400 Subject: [PATCH 08/20] Additional waiters. --- aws/internal/service/eks/finder/finder.go | 29 +++++++++++++++++++ aws/internal/service/eks/waiter/status.go | 34 +++++++++++++++++++++++ aws/internal/service/eks/waiter/waiter.go | 17 ++++++++++++ 3 files changed, 80 insertions(+) diff --git a/aws/internal/service/eks/finder/finder.go b/aws/internal/service/eks/finder/finder.go index c6a5565bda9..a1b8fc712d7 100644 --- a/aws/internal/service/eks/finder/finder.go +++ b/aws/internal/service/eks/finder/finder.go @@ -36,3 +36,32 @@ func ClusterByName(conn *eks.EKS, name string) (*eks.Cluster, error) { return output.Cluster, nil } + +func UpdateByNameAndID(conn *eks.EKS, name, id string) (*eks.Update, error) { + input := &eks.DescribeUpdateInput{ + Name: aws.String(name), + UpdateId: aws.String(id), + } + + output, err := conn.DescribeUpdate(input) + + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Update == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Update, nil +} diff --git a/aws/internal/service/eks/waiter/status.go b/aws/internal/service/eks/waiter/status.go index f5013e37097..97b22250a2f 100644 --- a/aws/internal/service/eks/waiter/status.go +++ b/aws/internal/service/eks/waiter/status.go @@ -8,8 +8,42 @@ import ( "github.com/aws/aws-sdk-go/service/eks" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) +func ClusterStatus(conn *eks.EKS, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.ClusterByName(conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + +func UodateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.UpdateByNameAndID(conn, name, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + func EksAddonStatus(ctx context.Context, conn *eks.EKS, addonName, clusterName string) resource.StateRefreshFunc { return func() (interface{}, string, error) { output, err := conn.DescribeAddonWithContext(ctx, &eks.DescribeAddonInput{ diff --git a/aws/internal/service/eks/waiter/waiter.go b/aws/internal/service/eks/waiter/waiter.go index badfeb90cd5..41d1446328f 100644 --- a/aws/internal/service/eks/waiter/waiter.go +++ b/aws/internal/service/eks/waiter/waiter.go @@ -18,6 +18,23 @@ const ( EksAddonDeletedTimeout = 40 * time.Minute ) +func ClusterDeleted(conn *eks.EKS, name string, timeout time.Duration) (*eks.Cluster, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.ClusterStatusActive, eks.ClusterStatusDeleting}, + Target: []string{}, + Refresh: ClusterStatus(conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.Cluster); ok { + return output, err + } + + return nil, err +} + // EksAddonCreated waits for a EKS add-on to return status "ACTIVE" or "CREATE_FAILED" func EksAddonCreated(ctx context.Context, conn *eks.EKS, clusterName, addonName string) (*eks.Addon, error) { stateConf := resource.StateChangeConf{ From f685bcd0c0d4b0778c051e7039090bb5bf10c691 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 18 Jun 2021 12:26:20 -0400 Subject: [PATCH 09/20] Add unit tests. --- aws/internal/tfresource/errors.go | 1 + aws/internal/tfresource/errors_test.go | 72 ++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/aws/internal/tfresource/errors.go b/aws/internal/tfresource/errors.go index 703eb55aa7d..032ea8f42e9 100644 --- a/aws/internal/tfresource/errors.go +++ b/aws/internal/tfresource/errors.go @@ -25,6 +25,7 @@ func TimedOut(err error) bool { } // SetLastError sets the LastError field on the error if supported. +// If lastErr is nil it is ignored. func SetLastError(err, lastErr error) { var te *resource.TimeoutError var use *resource.UnexpectedStateError diff --git a/aws/internal/tfresource/errors_test.go b/aws/internal/tfresource/errors_test.go index da75f2cec9e..36883294882 100644 --- a/aws/internal/tfresource/errors_test.go +++ b/aws/internal/tfresource/errors_test.go @@ -3,6 +3,7 @@ package tfresource_test import ( "errors" "fmt" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -97,3 +98,74 @@ func TestTimedOut(t *testing.T) { }) } } + +func TestSetLastError(t *testing.T) { + testCases := []struct { + Name string + Err error + LastErr error + Expected bool + }{ + { + Name: "nil error", + }, + { + Name: "other error", + Err: errors.New("test"), + LastErr: errors.New("last"), + }, + { + Name: "timeout error lastErr is nil", + Err: &resource.TimeoutError{}, + }, + { + Name: "timeout error", + Err: &resource.TimeoutError{}, + LastErr: errors.New("lasttest"), + Expected: true, + }, + { + Name: "timeout error non-nil last error lastErr is nil", + Err: &resource.TimeoutError{LastError: errors.New("test")}, + }, + { + Name: "timeout error non-nil last error no overwrite", + Err: &resource.TimeoutError{LastError: errors.New("test")}, + LastErr: errors.New("lasttest"), + }, + { + Name: "unexpected state error lastErr is nil", + Err: &resource.UnexpectedStateError{}, + }, + { + Name: "unexpected state error", + Err: &resource.UnexpectedStateError{}, + LastErr: errors.New("lasttest"), + Expected: true, + }, + { + Name: "unexpected state error non-nil last error lastErr is nil", + Err: &resource.UnexpectedStateError{LastError: errors.New("test")}, + }, + { + Name: "unexpected state error non-nil last error no overwrite", + Err: &resource.UnexpectedStateError{LastError: errors.New("test")}, + LastErr: errors.New("lasttest"), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tfresource.SetLastError(testCase.Err, testCase.LastErr) + + if testCase.Err != nil { + got := testCase.Err.Error() + contains := strings.Contains(got, "lasttest") + + if (testCase.Expected && !contains) || (!testCase.Expected && contains) { + t.Errorf("got %s", got) + } + } + }) + } +} From 46eea7a6e7e3c268fca9fc22e7cd2b007c27748f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 18 Jun 2021 13:58:20 -0400 Subject: [PATCH 10/20] Tweak use of 'tfresource.SetLastError'. --- aws/internal/service/amplify/waiter/waiter.go | 2 +- aws/internal/service/ec2/waiter/waiter.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aws/internal/service/amplify/waiter/waiter.go b/aws/internal/service/amplify/waiter/waiter.go index 08abc2675ac..02105061a37 100644 --- a/aws/internal/service/amplify/waiter/waiter.go +++ b/aws/internal/service/amplify/waiter/waiter.go @@ -26,7 +26,7 @@ func DomainAssociationCreated(conn *amplify.Amplify, appID, domainName string) ( outputRaw, err := stateConf.WaitForState() if v, ok := outputRaw.(*amplify.DomainAssociation); ok { - if v != nil && aws.StringValue(v.DomainStatus) == amplify.DomainStatusFailed { + if status := aws.StringValue(v.DomainStatus); status == amplify.DomainStatusFailed { tfresource.SetLastError(err, errors.New(aws.StringValue(v.StatusReason))) } diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index 73ff47bfbd7..1daf05b25f9 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -358,7 +358,7 @@ func RouteTableAssociationCreated(conn *ec2.EC2, id string) (*ec2.RouteTableAsso outputRaw, err := stateConf.WaitForState() if output, ok := outputRaw.(*ec2.RouteTableAssociationState); ok { - if output != nil && aws.StringValue(output.State) == ec2.RouteTableAssociationStateCodeFailed { + if state := aws.StringValue(output.State); state == ec2.RouteTableAssociationStateCodeFailed { tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) } @@ -379,7 +379,7 @@ func RouteTableAssociationDeleted(conn *ec2.EC2, id string) (*ec2.RouteTableAsso outputRaw, err := stateConf.WaitForState() if output, ok := outputRaw.(*ec2.RouteTableAssociationState); ok { - if output != nil && aws.StringValue(output.State) == ec2.RouteTableAssociationStateCodeFailed { + if state := aws.StringValue(output.State); state == ec2.RouteTableAssociationStateCodeFailed { tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) } @@ -400,7 +400,7 @@ func RouteTableAssociationUpdated(conn *ec2.EC2, id string) (*ec2.RouteTableAsso outputRaw, err := stateConf.WaitForState() if output, ok := outputRaw.(*ec2.RouteTableAssociationState); ok { - if output != nil && aws.StringValue(output.State) == ec2.RouteTableAssociationStateCodeFailed { + if state := aws.StringValue(output.State); state == ec2.RouteTableAssociationStateCodeFailed { tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusMessage))) } @@ -704,8 +704,8 @@ func VpcEndpointAccepted(conn *ec2.EC2, vpcEndpointID string, timeout time.Durat outputRaw, err := stateConf.WaitForState() if output, ok := outputRaw.(*ec2.VpcEndpoint); ok { - if output != nil && aws.StringValue(output.State) == tfec2.VpcEndpointStateFailed && output.LastError != nil { - tfresource.SetLastError(err, fmt.Errorf("%s: %s", aws.StringValue(output.LastError.Code), aws.StringValue(output.LastError.Message))) + if state, lastError := aws.StringValue(output.State), output.LastError; state == tfec2.VpcEndpointStateFailed && lastError != nil { + tfresource.SetLastError(err, fmt.Errorf("%s: %s", aws.StringValue(lastError.Code), aws.StringValue(lastError.Message))) } return output, err From 716e26755f8c4c3f8b249ec9985dc0bfad0e566f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 18 Jun 2021 13:59:23 -0400 Subject: [PATCH 11/20] Add and use waiters in 'internal/service/eks'. --- aws/internal/service/eks/waiter/status.go | 2 +- aws/internal/service/eks/waiter/waiter.go | 45 ++++ aws/resource_aws_eks_addon_test.go | 25 +- aws/resource_aws_eks_cluster.go | 285 +++++++--------------- 4 files changed, 131 insertions(+), 226 deletions(-) diff --git a/aws/internal/service/eks/waiter/status.go b/aws/internal/service/eks/waiter/status.go index 97b22250a2f..a2e73046049 100644 --- a/aws/internal/service/eks/waiter/status.go +++ b/aws/internal/service/eks/waiter/status.go @@ -28,7 +28,7 @@ func ClusterStatus(conn *eks.EKS, name string) resource.StateRefreshFunc { } } -func UodateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { +func UpdateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { output, err := finder.UpdateByNameAndID(conn, name, id) diff --git a/aws/internal/service/eks/waiter/waiter.go b/aws/internal/service/eks/waiter/waiter.go index 41d1446328f..c05ef448c64 100644 --- a/aws/internal/service/eks/waiter/waiter.go +++ b/aws/internal/service/eks/waiter/waiter.go @@ -9,7 +9,9 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" "github.com/hashicorp/aws-sdk-go-base/tfawserr" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) const ( @@ -18,6 +20,23 @@ const ( EksAddonDeletedTimeout = 40 * time.Minute ) +func ClusterCreated(conn *eks.EKS, name string, timeout time.Duration) (*eks.Cluster, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.ClusterStatusCreating}, + Target: []string{eks.ClusterStatusActive}, + Refresh: ClusterStatus(conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.Cluster); ok { + return output, err + } + + return nil, err +} + func ClusterDeleted(conn *eks.EKS, name string, timeout time.Duration) (*eks.Cluster, error) { stateConf := &resource.StateChangeConf{ Pending: []string{eks.ClusterStatusActive, eks.ClusterStatusDeleting}, @@ -35,6 +54,32 @@ func ClusterDeleted(conn *eks.EKS, name string, timeout time.Duration) (*eks.Clu return nil, err } +func UpdateSuccessful(conn *eks.EKS, name, id string, timeout time.Duration) (*eks.Update, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.UpdateStatusInProgress}, + Target: []string{eks.UpdateStatusSuccessful}, + Refresh: UpdateStatus(conn, name, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.Update); ok { + if status := aws.StringValue(output.Status); status == eks.UpdateStatusCancelled || status == eks.UpdateStatusFailed { + var errs *multierror.Error + + for _, e := range output.Errors { + errs = multierror.Append(errs, fmt.Errorf("%s: %s", aws.StringValue(e.ErrorCode), aws.StringValue(e.ErrorMessage))) + } + tfresource.SetLastError(err, errs.ErrorOrNil()) + } + + return output, err + } + + return nil, err +} + // EksAddonCreated waits for a EKS add-on to return status "ACTIVE" or "CREATE_FAILED" func EksAddonCreated(ctx context.Context, conn *eks.EKS, clusterName, addonName string) (*eks.Addon, error) { stateConf := resource.StateChangeConf{ diff --git a/aws/resource_aws_eks_addon_test.go b/aws/resource_aws_eks_addon_test.go index ce403ac430b..f79c68c52c7 100644 --- a/aws/resource_aws_eks_addon_test.go +++ b/aws/resource_aws_eks_addon_test.go @@ -147,6 +147,7 @@ func TestAccAWSEksAddon_disappears_Cluster(t *testing.T) { var addon eks.Addon rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_addon.test" + clusterResourceName := "aws_eks_cluster" addonName := "vpc-cni" ctx := context.TODO() @@ -160,7 +161,7 @@ func TestAccAWSEksAddon_disappears_Cluster(t *testing.T) { Config: testAccAWSEksAddon_Basic(rName, addonName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSEksAddonExists(ctx, resourceName, &addon), - testAccCheckAWSEksClusterDisappears(ctx, &addon), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEksCluster(), clusterResourceName), ), ExpectNonEmptyPlan: true, }, @@ -796,28 +797,6 @@ func testAccCheckAWSEksAddonDestroy(s *terraform.State) error { return nil } -func testAccCheckAWSEksClusterDisappears(ctx context.Context, addon *eks.Addon) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).eksconn - - input := &eks.DeleteClusterInput{ - Name: addon.ClusterName, - } - - _, err := conn.DeleteClusterWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { - return nil - } - - if err != nil { - return err - } - - return waitForDeleteEksCluster(conn, aws.StringValue(addon.ClusterName), 30*time.Minute) - } -} - func testAccPreCheckAWSEksAddon(t *testing.T) { conn := testAccProvider.Meta().(*AWSClient).eksconn diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index 620b754e09e..1d1251abb92 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "regexp" - "strings" "time" "github.com/aws/aws-sdk-go/aws" @@ -16,6 +15,7 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/waiter" iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -67,6 +67,9 @@ func resourceAwsEksCluster() *schema.Resource { ValidateFunc: validation.StringInSlice(eks.LogType_Values(), true), }, }, + // TODO + // TODO You cannot disable envelope encryption after enabling it. This action is irreversible. + // TODO "encryption_config": { Type: schema.TypeList, MaxItems: 1, @@ -231,14 +234,10 @@ func resourceAwsEksClusterCreate(d *schema.ResourceData, meta interface{}) error input := &eks.CreateClusterInput{ EncryptionConfig: expandEksEncryptionConfig(d.Get("encryption_config").([]interface{})), + Logging: expandEksLoggingTypes(d.Get("enabled_cluster_log_types").(*schema.Set)), Name: aws.String(name), - RoleArn: aws.String(d.Get("role_arn").(string)), ResourcesVpcConfig: expandEksVpcConfigRequest(d.Get("vpc_config").([]interface{})), - Logging: expandEksLoggingTypes(d.Get("enabled_cluster_log_types").(*schema.Set)), - } - - if len(tags) > 0 { - input.Tags = tags.IgnoreAws().EksTags() + RoleArn: aws.String(d.Get("role_arn").(string)), } if _, ok := d.GetOk("kubernetes_network_config"); ok { @@ -249,51 +248,62 @@ func resourceAwsEksClusterCreate(d *schema.ResourceData, meta interface{}) error input.Version = aws.String(v.(string)) } + if len(tags) > 0 { + input.Tags = tags.IgnoreAws().EksTags() + } + log.Printf("[DEBUG] Creating EKS Cluster: %s", input) + var output *eks.CreateClusterOutput err := resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError { - _, err := conn.CreateCluster(input) + var err error + + output, err = conn.CreateCluster(input) + + // InvalidParameterException: roleArn, arn:aws:iam::123456789012:role/XXX, does not exist + if isAWSErr(err, eks.ErrCodeInvalidParameterException, "does not exist") { + return resource.RetryableError(err) + } + + // InvalidParameterException: Error in role params + if isAWSErr(err, eks.ErrCodeInvalidParameterException, "Error in role params") { + return resource.RetryableError(err) + } + + if isAWSErr(err, eks.ErrCodeInvalidParameterException, "Role could not be assumed because the trusted entity is not correct") { + return resource.RetryableError(err) + } + + // InvalidParameterException: The provided role doesn't have the Amazon EKS Managed Policies associated with it. Please ensure the following policy is attached: arn:aws:iam::aws:policy/AmazonEKSClusterPolicy + if isAWSErr(err, eks.ErrCodeInvalidParameterException, "The provided role doesn't have the Amazon EKS Managed Policies associated with it") { + return resource.RetryableError(err) + } + + // InvalidParameterException: IAM role's policy must include the `ec2:DescribeSubnets` action + if isAWSErr(err, eks.ErrCodeInvalidParameterException, "IAM role's policy must include") { + return resource.RetryableError(err) + } + if err != nil { - // InvalidParameterException: roleArn, arn:aws:iam::123456789012:role/XXX, does not exist - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "does not exist") { - return resource.RetryableError(err) - } - // InvalidParameterException: Error in role params - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "Error in role params") { - return resource.RetryableError(err) - } - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "Role could not be assumed because the trusted entity is not correct") { - return resource.RetryableError(err) - } - // InvalidParameterException: The provided role doesn't have the Amazon EKS Managed Policies associated with it. Please ensure the following policy is attached: arn:aws:iam::aws:policy/AmazonEKSClusterPolicy - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "The provided role doesn't have the Amazon EKS Managed Policies associated with it") { - return resource.RetryableError(err) - } - // InvalidParameterException: IAM role's policy must include the `ec2:DescribeSubnets` action - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "IAM role's policy must include") { - return resource.RetryableError(err) - } return resource.NonRetryableError(err) } + return nil }) - if isResourceTimeoutError(err) { - _, err = conn.CreateCluster(input) + + if tfresource.TimedOut(err) { + output, err = conn.CreateCluster(input) } + if err != nil { - return fmt.Errorf("error creating EKS Cluster (%s): %s", name, err) + return fmt.Errorf("error creating EKS Cluster (%s): %w", name, err) } - d.SetId(name) + d.SetId(aws.StringValue(output.Cluster.Name)) + + _, err = waiter.ClusterCreated(conn, d.Id(), d.Timeout(schema.TimeoutCreate)) - stateConf := resource.StateChangeConf{ - Pending: []string{eks.ClusterStatusCreating}, - Target: []string{eks.ClusterStatusActive}, - Timeout: d.Timeout(schema.TimeoutCreate), - Refresh: refreshEksClusterStatus(conn, name), - } - _, err = stateConf.WaitForState() if err != nil { - return err + return fmt.Errorf("error waiting for EKS Cluster (%s) to create: %w", d.Id(), err) } return resourceAwsEksClusterRead(d, meta) @@ -369,79 +379,70 @@ func resourceAwsEksClusterRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).eksconn - if d.HasChange("encryption_config") { - input := &eks.AssociateEncryptionConfigInput{ - ClusterName: aws.String(d.Id()), - EncryptionConfig: expandEksEncryptionConfig(d.Get("encryption_config").([]interface{})), + // Do any version update first. + if d.HasChange("version") { + input := &eks.UpdateClusterVersionInput{ + Name: aws.String(d.Id()), + Version: aws.String(d.Get("version").(string)), } log.Printf("[DEBUG] Updating EKS Cluster (%s) version: %s", d.Id(), input) - output, err := conn.AssociateEncryptionConfig(input) + output, err := conn.UpdateClusterVersion(input) if err != nil { - return fmt.Errorf("error updating EKS Cluster (%s) version: %s", d.Id(), err) - } - - if output == nil || output.Update == nil || output.Update.Id == nil { - return fmt.Errorf("error determining EKS Cluster (%s) version update ID: empty response", d.Id()) + return fmt.Errorf("error updating EKS Cluster (%s) version: %w", d.Id(), err) } updateID := aws.StringValue(output.Update.Id) - err = waitForUpdateEksCluster(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.UpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + if err != nil { - return fmt.Errorf("error waiting for EKS Cluster (%s) version update (%s): %s", d.Id(), updateID, err) + return fmt.Errorf("error waiting for EKS Cluster (%s) version update (%s): %w", d.Id(), updateID, err) } } - if d.HasChange("version") { - input := &eks.UpdateClusterVersionInput{ - Name: aws.String(d.Id()), - Version: aws.String(d.Get("version").(string)), + if d.HasChange("encryption_config") { + input := &eks.AssociateEncryptionConfigInput{ + ClusterName: aws.String(d.Id()), + EncryptionConfig: expandEksEncryptionConfig(d.Get("encryption_config").([]interface{})), } - log.Printf("[DEBUG] Updating EKS Cluster (%s) version: %s", d.Id(), input) - output, err := conn.UpdateClusterVersion(input) + log.Printf("[DEBUG] Associating EKS Cluster (%s) encryption config: %s", d.Id(), input) + output, err := conn.AssociateEncryptionConfig(input) if err != nil { - return fmt.Errorf("error updating EKS Cluster (%s) version: %s", d.Id(), err) - } - - if output == nil || output.Update == nil || output.Update.Id == nil { - return fmt.Errorf("error determining EKS Cluster (%s) version update ID: empty response", d.Id()) + return fmt.Errorf("error associating EKS Cluster (%s) encryption config: %w", d.Id(), err) } updateID := aws.StringValue(output.Update.Id) - err = waitForUpdateEksCluster(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.UpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + if err != nil { - return fmt.Errorf("error waiting for EKS Cluster (%s) version update (%s): %s", d.Id(), updateID, err) + return fmt.Errorf("error waiting for EKS Cluster (%s) encryption config association (%s): %w", d.Id(), updateID, err) } } if d.HasChange("enabled_cluster_log_types") { - _, v := d.GetChange("enabled_cluster_log_types") input := &eks.UpdateClusterConfigInput{ + Logging: expandEksLoggingTypes(d.Get("enabled_cluster_log_types").(*schema.Set)), Name: aws.String(d.Id()), - Logging: expandEksLoggingTypes(v.(*schema.Set)), } log.Printf("[DEBUG] Updating EKS Cluster (%s) logging: %s", d.Id(), input) output, err := conn.UpdateClusterConfig(input) if err != nil { - return fmt.Errorf("error updating EKS Cluster (%s) logging: %s", d.Id(), err) - } - - if output == nil || output.Update == nil || output.Update.Id == nil { - return fmt.Errorf("error determining EKS Cluster (%s) logging update ID: empty response", d.Id()) + return fmt.Errorf("error updating EKS Cluster (%s) logging: %w", d.Id(), err) } updateID := aws.StringValue(output.Update.Id) - err = waitForUpdateEksCluster(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.UpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + if err != nil { - return fmt.Errorf("error waiting for EKS Cluster (%s) logging update (%s): %s", d.Id(), updateID, err) + return fmt.Errorf("error waiting for EKS Cluster (%s) logging update (%s): %w", d.Id(), updateID, err) } } @@ -451,22 +452,19 @@ func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error ResourcesVpcConfig: expandEksVpcConfigUpdateRequest(d.Get("vpc_config").([]interface{})), } - log.Printf("[DEBUG] Updating EKS Cluster (%s) config: %s", d.Id(), input) + log.Printf("[DEBUG] Updating EKS Cluster (%s) VPC config: %s", d.Id(), input) output, err := conn.UpdateClusterConfig(input) if err != nil { - return fmt.Errorf("error updating EKS Cluster (%s) config: %s", d.Id(), err) - } - - if output == nil || output.Update == nil || output.Update.Id == nil { - return fmt.Errorf("error determining EKS Cluster (%s) config update ID: empty response", d.Id()) + return fmt.Errorf("error updating EKS Cluster (%s) VPC config: %w", d.Id(), err) } updateID := aws.StringValue(output.Update.Id) - err = waitForUpdateEksCluster(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.UpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + if err != nil { - return fmt.Errorf("error waiting for EKS Cluster (%s) config update (%s): %s", d.Id(), updateID, err) + return fmt.Errorf("error waiting for EKS Cluster (%s) VPC config update (%s): %w", d.Id(), updateID, err) } } @@ -499,12 +497,13 @@ func resourceAwsEksClusterDelete(d *schema.ResourceData, meta interface{}) error } if err != nil { - return fmt.Errorf("error deleting EKS Cluster (%s): %s", d.Id(), err) + return fmt.Errorf("error deleting EKS Cluster (%s): %w", d.Id(), err) } - err = waitForDeleteEksCluster(conn, d.Id(), d.Timeout(schema.TimeoutDelete)) + _, err = waiter.ClusterDeleted(conn, d.Id(), d.Timeout(schema.TimeoutDelete)) + if err != nil { - return fmt.Errorf("error waiting for EKS Cluster (%s) deletion: %s", d.Id(), err) + return fmt.Errorf("error waiting for EKS Cluster (%s) delete: %w", d.Id(), err) } return nil @@ -744,121 +743,3 @@ func flattenEksNetworkConfig(apiObject *eks.KubernetesNetworkConfigResponse) []i return []interface{}{tfMap} } - -func refreshEksClusterStatus(conn *eks.EKS, clusterName string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := conn.DescribeCluster(&eks.DescribeClusterInput{ - Name: aws.String(clusterName), - }) - if err != nil { - return 42, "", err - } - cluster := output.Cluster - if cluster == nil { - return cluster, "", fmt.Errorf("EKS Cluster (%s) missing", clusterName) - } - return cluster, aws.StringValue(cluster.Status), nil - } -} - -func refreshEksUpdateStatus(conn *eks.EKS, clusterName, updateID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - input := &eks.DescribeUpdateInput{ - Name: aws.String(clusterName), - UpdateId: aws.String(updateID), - } - - output, err := conn.DescribeUpdate(input) - - if err != nil { - return nil, "", err - } - - if output == nil || output.Update == nil { - return nil, "", fmt.Errorf("EKS Cluster (%s) update (%s) missing", clusterName, updateID) - } - - return output.Update, aws.StringValue(output.Update.Status), nil - } -} - -func waitForDeleteEksCluster(conn *eks.EKS, clusterName string, timeout time.Duration) error { - /* - TODO - - // Handle eventual consistency - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - output, err := conn.DescribeCluster(&eks.DescribeClusterInput{ - Name: aws.String(rs.Primary.ID), - }) - - if err != nil { - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - return nil - } - return resource.NonRetryableError(err) - } - - if output != nil && output.Cluster != nil && aws.StringValue(output.Cluster.Name) == rs.Primary.ID { - return resource.RetryableError(fmt.Errorf("EKS Cluster %s still exists", rs.Primary.ID)) - } - - return nil - }) - */ - stateConf := resource.StateChangeConf{ - Pending: []string{ - eks.ClusterStatusActive, - eks.ClusterStatusDeleting, - }, - Target: []string{""}, - Timeout: timeout, - Refresh: refreshEksClusterStatus(conn, clusterName), - } - cluster, err := stateConf.WaitForState() - if err != nil { - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - return nil - } - // Sometimes the EKS API returns the ResourceNotFound error in this form: - // ClientException: No cluster found for name: tf-acc-test-0o1f8 - if isAWSErr(err, eks.ErrCodeClientException, "No cluster found for name:") { - return nil - } - } - if cluster == nil { - return nil - } - return err -} - -func waitForUpdateEksCluster(conn *eks.EKS, clusterName, updateID string, timeout time.Duration) error { - stateConf := resource.StateChangeConf{ - Pending: []string{eks.UpdateStatusInProgress}, - Target: []string{ - eks.UpdateStatusCancelled, - eks.UpdateStatusFailed, - eks.UpdateStatusSuccessful, - }, - Timeout: timeout, - Refresh: refreshEksUpdateStatus(conn, clusterName, updateID), - } - updateRaw, err := stateConf.WaitForState() - - if err != nil { - return err - } - - update := updateRaw.(*eks.Update) - - if aws.StringValue(update.Status) == eks.UpdateStatusSuccessful { - return nil - } - - var detailedErrors []string - for i, updateError := range update.Errors { - detailedErrors = append(detailedErrors, fmt.Sprintf("Error %d: Code: %s / Message: %s", i+1, aws.StringValue(updateError.ErrorCode), aws.StringValue(updateError.ErrorMessage))) - } - - return fmt.Errorf("EKS Cluster (%s) update (%s) status (%s) not successful: Errors:\n%s", clusterName, updateID, aws.StringValue(update.Status), strings.Join(detailedErrors, "\n")) -} From 7723e0e52e1fcaa80fe1057372329c27ab32b622 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 18 Jun 2021 15:51:56 -0400 Subject: [PATCH 12/20] r/aws_eks_cluster: Add 'TestAccAWSEksCluster_EncryptionConfig_Update'. Acceptance test output: % make testacc TEST=./aws TESTARGS='-run=TestAccAWSEksCluster_EncryptionConfig_Update' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./aws -v -count 1 -parallel 20 -run=TestAccAWSEksCluster_EncryptionConfig_Update -timeout 180m === RUN TestAccAWSEksCluster_EncryptionConfig_Update === PAUSE TestAccAWSEksCluster_EncryptionConfig_Update === CONT TestAccAWSEksCluster_EncryptionConfig_Update --- PASS: TestAccAWSEksCluster_EncryptionConfig_Update (3710.17s) PASS ok github.com/terraform-providers/terraform-provider-aws/aws 3713.264s --- aws/resource_aws_eks_cluster_test.go | 107 +++++++++++++++++---------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/aws/resource_aws_eks_cluster_test.go b/aws/resource_aws_eks_cluster_test.go index d255f5e1a94..c7878053131 100644 --- a/aws/resource_aws_eks_cluster_test.go +++ b/aws/resource_aws_eks_cluster_test.go @@ -74,7 +74,7 @@ func testSweepEksClusters(region string) error { func TestAccAWSEksCluster_basic(t *testing.T) { var cluster eks.Cluster - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -121,7 +121,7 @@ func TestAccAWSEksCluster_basic(t *testing.T) { func TestAccAWSEksCluster_disappears(t *testing.T) { var cluster eks.Cluster - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -142,11 +142,42 @@ func TestAccAWSEksCluster_disappears(t *testing.T) { }) } -func TestAccAWSEksCluster_EncryptionConfig(t *testing.T) { +func TestAccAWSEksCluster_EncryptionConfig_Create(t *testing.T) { var cluster eks.Cluster - kmsKeyResourceName := "aws_kms_key.test" + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" + kmsKeyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEks(t) }, + ErrorCheck: testAccErrorCheck(t, eks.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEksClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEksClusterConfig_EncryptionConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEksClusterExists(resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "encryption_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "encryption_config.0.provider.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "encryption_config.0.provider.0.key_arn", kmsKeyResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "encryption_config.0.resources.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSEksCluster_EncryptionConfig_Update(t *testing.T) { + var cluster eks.Cluster rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_eks_cluster.test" + kmsKeyResourceName := "aws_kms_key.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEks(t) }, @@ -154,6 +185,13 @@ func TestAccAWSEksCluster_EncryptionConfig(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEksClusterDestroy, Steps: []resource.TestStep{ + { + Config: testAccAWSEksClusterConfig_Required(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEksClusterExists(resourceName, &cluster), + resource.TestCheckResourceAttr(resourceName, "encryption_config.#", "0"), + ), + }, { Config: testAccAWSEksClusterConfig_EncryptionConfig(rName), Check: resource.ComposeTestCheckFunc( @@ -175,8 +213,7 @@ func TestAccAWSEksCluster_EncryptionConfig(t *testing.T) { func TestAccAWSEksCluster_Version(t *testing.T) { var cluster1, cluster2 eks.Cluster - - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -211,8 +248,7 @@ func TestAccAWSEksCluster_Version(t *testing.T) { func TestAccAWSEksCluster_Logging(t *testing.T) { var cluster1, cluster2 eks.Cluster - - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -304,8 +340,7 @@ func TestAccAWSEksCluster_Tags(t *testing.T) { func TestAccAWSEksCluster_VpcConfig_SecurityGroupIds(t *testing.T) { var cluster eks.Cluster - - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -333,8 +368,7 @@ func TestAccAWSEksCluster_VpcConfig_SecurityGroupIds(t *testing.T) { func TestAccAWSEksCluster_VpcConfig_EndpointPrivateAccess(t *testing.T) { var cluster1, cluster2, cluster3 eks.Cluster - - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -380,8 +414,7 @@ func TestAccAWSEksCluster_VpcConfig_EndpointPrivateAccess(t *testing.T) { func TestAccAWSEksCluster_VpcConfig_EndpointPublicAccess(t *testing.T) { var cluster1, cluster2, cluster3 eks.Cluster - - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -426,9 +459,8 @@ func TestAccAWSEksCluster_VpcConfig_EndpointPublicAccess(t *testing.T) { } func TestAccAWSEksCluster_VpcConfig_PublicAccessCidrs(t *testing.T) { - var cluster1 eks.Cluster - - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + var cluster eks.Cluster + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -440,7 +472,7 @@ func TestAccAWSEksCluster_VpcConfig_PublicAccessCidrs(t *testing.T) { { Config: testAccAWSEksClusterConfig_VpcConfig_PublicAccessCidrs(rName, `["1.2.3.4/32", "5.6.7.8/32"]`), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSEksClusterExists(resourceName, &cluster1), + testAccCheckAWSEksClusterExists(resourceName, &cluster), resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "1"), resource.TestCheckResourceAttr(resourceName, "vpc_config.0.public_access_cidrs.#", "2"), ), @@ -453,7 +485,7 @@ func TestAccAWSEksCluster_VpcConfig_PublicAccessCidrs(t *testing.T) { { Config: testAccAWSEksClusterConfig_VpcConfig_PublicAccessCidrs(rName, `["4.3.2.1/32", "8.7.6.5/32"]`), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSEksClusterExists(resourceName, &cluster1), + testAccCheckAWSEksClusterExists(resourceName, &cluster), resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "1"), resource.TestCheckResourceAttr(resourceName, "vpc_config.0.public_access_cidrs.#", "2"), ), @@ -464,8 +496,7 @@ func TestAccAWSEksCluster_VpcConfig_PublicAccessCidrs(t *testing.T) { func TestAccAWSEksCluster_NetworkConfig_ServiceIpv4Cidr(t *testing.T) { var cluster1, cluster2 eks.Cluster - - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -651,7 +682,7 @@ resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-eks-cluster-base" + Name = %[1]q "kubernetes.io/cluster/%[1]s" = "shared" } } @@ -664,7 +695,7 @@ resource "aws_subnet" "test" { vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-eks-cluster-base" + Name = %[1]q "kubernetes.io/cluster/%[1]s" = "shared" } } @@ -674,7 +705,7 @@ resource "aws_subnet" "test" { func testAccAWSEksClusterConfig_Required(rName string) string { return composeConfig(testAccAWSEksClusterConfig_Base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { - name = %q + name = %[1]q role_arn = aws_iam_role.test.arn vpc_config { @@ -689,9 +720,9 @@ resource "aws_eks_cluster" "test" { func testAccAWSEksClusterConfig_Version(rName, version string) string { return composeConfig(testAccAWSEksClusterConfig_Base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { - name = %q + name = %[1]q role_arn = aws_iam_role.test.arn - version = %q + version = %[2]q vpc_config { subnet_ids = aws_subnet.test[*].id @@ -705,9 +736,9 @@ resource "aws_eks_cluster" "test" { func testAccAWSEksClusterConfig_Logging(rName string, logTypes []string) string { return composeConfig(testAccAWSEksClusterConfig_Base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { - name = %q + name = %[1]q role_arn = aws_iam_role.test.arn - enabled_cluster_log_types = ["%v"] + enabled_cluster_log_types = ["%[2]v"] vpc_config { subnet_ids = aws_subnet.test[*].id @@ -791,12 +822,12 @@ resource "aws_security_group" "test" { vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-eks-cluster-sg" + Name = %[1]q } } resource "aws_eks_cluster" "test" { - name = %q + name = %[1]q role_arn = aws_iam_role.test.arn vpc_config { @@ -812,11 +843,11 @@ resource "aws_eks_cluster" "test" { func testAccAWSEksClusterConfig_VpcConfig_EndpointPrivateAccess(rName string, endpointPrivateAccess bool) string { return composeConfig(testAccAWSEksClusterConfig_Base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { - name = %q + name = %[1]q role_arn = aws_iam_role.test.arn vpc_config { - endpoint_private_access = %t + endpoint_private_access = %[2]t endpoint_public_access = true subnet_ids = aws_subnet.test[*].id } @@ -829,12 +860,12 @@ resource "aws_eks_cluster" "test" { func testAccAWSEksClusterConfig_VpcConfig_EndpointPublicAccess(rName string, endpointPublicAccess bool) string { return composeConfig(testAccAWSEksClusterConfig_Base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { - name = %q + name = %[1]q role_arn = aws_iam_role.test.arn vpc_config { endpoint_private_access = true - endpoint_public_access = %t + endpoint_public_access = %[2]t subnet_ids = aws_subnet.test[*].id } @@ -846,13 +877,13 @@ resource "aws_eks_cluster" "test" { func testAccAWSEksClusterConfig_VpcConfig_PublicAccessCidrs(rName string, publicAccessCidr string) string { return composeConfig(testAccAWSEksClusterConfig_Base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { - name = %q + name = %[1]q role_arn = aws_iam_role.test.arn vpc_config { endpoint_private_access = true endpoint_public_access = true - public_access_cidrs = %s + public_access_cidrs = %[2]s subnet_ids = aws_subnet.test[*].id } @@ -864,7 +895,7 @@ resource "aws_eks_cluster" "test" { func testAccAWSEksClusterConfig_NetworkConfig_ServiceIpv4Cidr(rName string, serviceIpv4Cidr string) string { return composeConfig(testAccAWSEksClusterConfig_Base(rName), fmt.Sprintf(` resource "aws_eks_cluster" "test" { - name = %q + name = %[1]q role_arn = aws_iam_role.test.arn vpc_config { @@ -872,7 +903,7 @@ resource "aws_eks_cluster" "test" { } kubernetes_network_config { - service_ipv4_cidr = %s + service_ipv4_cidr = %[2]s } depends_on = [aws_iam_role_policy_attachment.test-AmazonEKSClusterPolicy] From 45c5a0ac6bc09c69fb6fd3521dcfcbe07140561a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 18 Jun 2021 17:19:09 -0400 Subject: [PATCH 13/20] r/aws_eks_cluster: You cannot disable envelope encryption after enabling it. --- aws/resource_aws_eks_cluster.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index 1d1251abb92..2447342f072 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -1,6 +1,7 @@ package aws import ( + "context" "fmt" "log" "regexp" @@ -9,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -30,7 +32,13 @@ func resourceAwsEksCluster() *schema.Resource { State: schema.ImportStatePassthrough, }, - CustomizeDiff: SetTagsDiff, + CustomizeDiff: customdiff.Sequence( + SetTagsDiff, + customdiff.ForceNewIfChange("encryption_config", func(_ context.Context, old, new, meta interface{}) bool { + // You cannot disable envelope encryption after enabling it. This action is irreversible. + return len(old.([]interface{})) == 1 && len(new.([]interface{})) == 0 + }), + ), Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), @@ -67,9 +75,6 @@ func resourceAwsEksCluster() *schema.Resource { ValidateFunc: validation.StringInSlice(eks.LogType_Values(), true), }, }, - // TODO - // TODO You cannot disable envelope encryption after enabling it. This action is irreversible. - // TODO "encryption_config": { Type: schema.TypeList, MaxItems: 1, @@ -260,26 +265,26 @@ func resourceAwsEksClusterCreate(d *schema.ResourceData, meta interface{}) error output, err = conn.CreateCluster(input) // InvalidParameterException: roleArn, arn:aws:iam::123456789012:role/XXX, does not exist - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "does not exist") { + if tfawserr.ErrMessageContains(err, eks.ErrCodeInvalidParameterException, "does not exist") { return resource.RetryableError(err) } // InvalidParameterException: Error in role params - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "Error in role params") { + if tfawserr.ErrMessageContains(err, eks.ErrCodeInvalidParameterException, "Error in role params") { return resource.RetryableError(err) } - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "Role could not be assumed because the trusted entity is not correct") { + if tfawserr.ErrMessageContains(err, eks.ErrCodeInvalidParameterException, "Role could not be assumed because the trusted entity is not correct") { return resource.RetryableError(err) } // InvalidParameterException: The provided role doesn't have the Amazon EKS Managed Policies associated with it. Please ensure the following policy is attached: arn:aws:iam::aws:policy/AmazonEKSClusterPolicy - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "The provided role doesn't have the Amazon EKS Managed Policies associated with it") { + if tfawserr.ErrMessageContains(err, eks.ErrCodeInvalidParameterException, "The provided role doesn't have the Amazon EKS Managed Policies associated with it") { return resource.RetryableError(err) } // InvalidParameterException: IAM role's policy must include the `ec2:DescribeSubnets` action - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "IAM role's policy must include") { + if tfawserr.ErrMessageContains(err, eks.ErrCodeInvalidParameterException, "IAM role's policy must include") { return resource.RetryableError(err) } From f8a4f01c9089fedc05360f6bcb0d6477fe8b6c15 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sun, 20 Jun 2021 17:47:43 -0400 Subject: [PATCH 14/20] r/aws_eks_fargate_profile: Use internal waiter package. --- aws/internal/service/eks/finder/finder.go | 29 ++++ aws/internal/service/eks/id.go | 19 +++ aws/internal/service/eks/waiter/status.go | 16 ++ aws/internal/service/eks/waiter/waiter.go | 34 ++++ aws/resource_aws_eks_cluster.go | 2 +- aws/resource_aws_eks_fargate_profile.go | 134 ++++------------ aws/resource_aws_eks_fargate_profile_test.go | 158 +++++++++---------- aws/resource_aws_eks_node_group.go | 1 - 8 files changed, 207 insertions(+), 186 deletions(-) diff --git a/aws/internal/service/eks/finder/finder.go b/aws/internal/service/eks/finder/finder.go index a1b8fc712d7..ccea06c8265 100644 --- a/aws/internal/service/eks/finder/finder.go +++ b/aws/internal/service/eks/finder/finder.go @@ -37,6 +37,35 @@ func ClusterByName(conn *eks.EKS, name string) (*eks.Cluster, error) { return output.Cluster, nil } +func FargateProfileByClusterNameAndFargateProfileName(conn *eks.EKS, clusterName, fargateProfileName string) (*eks.FargateProfile, error) { + input := &eks.DescribeFargateProfileInput{ + ClusterName: aws.String(clusterName), + FargateProfileName: aws.String(fargateProfileName), + } + + output, err := conn.DescribeFargateProfile(input) + + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.FargateProfile == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.FargateProfile, nil +} + func UpdateByNameAndID(conn *eks.EKS, name, id string) (*eks.Update, error) { input := &eks.DescribeUpdateInput{ Name: aws.String(name), diff --git a/aws/internal/service/eks/id.go b/aws/internal/service/eks/id.go index 67651b3c435..3fe8d2a776c 100644 --- a/aws/internal/service/eks/id.go +++ b/aws/internal/service/eks/id.go @@ -5,6 +5,25 @@ import ( "strings" ) +const fargateProfileResourceIDSeparator = ":" + +func FargateProfileCreateResourceID(clusterName, fargateProfileName string) string { + parts := []string{clusterName, fargateProfileName} + id := strings.Join(parts, fargateProfileResourceIDSeparator) + + return id +} + +func FargateProfileParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, fargateProfileResourceIDSeparator) + + 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]sfargate-profile-name", id, fargateProfileResourceIDSeparator) +} + const nodeGroupResourceIDSeparator = ":" func NodeGroupCreateResourceID(clusterName, nodeGroupName string) string { diff --git a/aws/internal/service/eks/waiter/status.go b/aws/internal/service/eks/waiter/status.go index a2e73046049..4552bf78a3a 100644 --- a/aws/internal/service/eks/waiter/status.go +++ b/aws/internal/service/eks/waiter/status.go @@ -28,6 +28,22 @@ func ClusterStatus(conn *eks.EKS, name string) resource.StateRefreshFunc { } } +func FargateProfileStatus(conn *eks.EKS, clusterName, fargateProfileName string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.FargateProfileByClusterNameAndFargateProfileName(conn, clusterName, fargateProfileName) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + func UpdateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { output, err := finder.UpdateByNameAndID(conn, name, id) diff --git a/aws/internal/service/eks/waiter/waiter.go b/aws/internal/service/eks/waiter/waiter.go index c05ef448c64..d433fc7c607 100644 --- a/aws/internal/service/eks/waiter/waiter.go +++ b/aws/internal/service/eks/waiter/waiter.go @@ -54,6 +54,40 @@ func ClusterDeleted(conn *eks.EKS, name string, timeout time.Duration) (*eks.Clu return nil, err } +func FargateProfileCreated(conn *eks.EKS, clusterName, fargateProfileName string, timeout time.Duration) (*eks.FargateProfile, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.FargateProfileStatusCreating}, + Target: []string{eks.FargateProfileStatusActive}, + Refresh: FargateProfileStatus(conn, clusterName, fargateProfileName), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.FargateProfile); ok { + return output, err + } + + return nil, err +} + +func FargateProfileDeleted(conn *eks.EKS, clusterName, fargateProfileName string, timeout time.Duration) (*eks.FargateProfile, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.FargateProfileStatusActive, eks.FargateProfileStatusDeleting}, + Target: []string{}, + Refresh: FargateProfileStatus(conn, clusterName, fargateProfileName), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.FargateProfile); ok { + return output, err + } + + return nil, err +} + func UpdateSuccessful(conn *eks.EKS, name, id string, timeout time.Duration) (*eks.Update, error) { stateConf := &resource.StateChangeConf{ Pending: []string{eks.UpdateStatusInProgress}, diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index 2447342f072..fd0a6e4da6e 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -508,7 +508,7 @@ func resourceAwsEksClusterDelete(d *schema.ResourceData, meta interface{}) error _, err = waiter.ClusterDeleted(conn, d.Id(), d.Timeout(schema.TimeoutDelete)) if err != nil { - return fmt.Errorf("error waiting for EKS Cluster (%s) delete: %w", d.Id(), err) + return fmt.Errorf("error waiting for EKS Cluster (%s) to delete: %w", d.Id(), err) } return nil diff --git a/aws/resource_aws_eks_fargate_profile.go b/aws/resource_aws_eks_fargate_profile.go index 0a8f6bcee28..509f97bd65f 100644 --- a/aws/resource_aws_eks_fargate_profile.go +++ b/aws/resource_aws_eks_fargate_profile.go @@ -3,16 +3,20 @@ package aws import ( "fmt" "log" - "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/waiter" iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsEksFargateProfile() *schema.Resource { @@ -21,7 +25,6 @@ func resourceAwsEksFargateProfile() *schema.Resource { Read: resourceAwsEksFargateProfileRead, Update: resourceAwsEksFargateProfileUpdate, Delete: resourceAwsEksFargateProfileDelete, - Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -99,9 +102,10 @@ func resourceAwsEksFargateProfileCreate(d *schema.ResourceData, meta interface{} conn := meta.(*AWSClient).eksconn defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + clusterName := d.Get("cluster_name").(string) fargateProfileName := d.Get("fargate_profile_name").(string) - id := fmt.Sprintf("%s:%s", clusterName, fargateProfileName) + id := tfeks.FargateProfileCreateResourceID(clusterName, fargateProfileName) input := &eks.CreateFargateProfileInput{ ClientRequestToken: aws.String(resource.UniqueId()), @@ -117,7 +121,7 @@ func resourceAwsEksFargateProfileCreate(d *schema.ResourceData, meta interface{} } // mutex lock for creation/deletion serialization - mutexKey := fmt.Sprintf("%s-fargate-profiles", d.Get("cluster_name").(string)) + mutexKey := fmt.Sprintf("%s-fargate-profiles", clusterName) awsMutexKV.Lock(mutexKey) defer awsMutexKV.Unlock(mutexKey) @@ -126,7 +130,7 @@ func resourceAwsEksFargateProfileCreate(d *schema.ResourceData, meta interface{} // Retry for IAM eventual consistency on error: // InvalidParameterException: Misconfigured PodExecutionRole Trust Policy; Please add the eks-fargate-pods.amazonaws.com Service Principal - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "Misconfigured PodExecutionRole Trust Policy") { + if tfawserr.ErrMessageContains(err, eks.ErrCodeInvalidParameterException, "Misconfigured PodExecutionRole Trust Policy") { return resource.RetryableError(err) } @@ -137,25 +141,20 @@ func resourceAwsEksFargateProfileCreate(d *schema.ResourceData, meta interface{} return nil }) - if isResourceTimeoutError(err) { + if tfresource.TimedOut(err) { _, err = conn.CreateFargateProfile(input) } if err != nil { - return fmt.Errorf("error creating EKS Fargate Profile (%s): %s", id, err) + return fmt.Errorf("error creating EKS Fargate Profile (%s): %w", id, err) } d.SetId(id) - stateConf := resource.StateChangeConf{ - Pending: []string{eks.FargateProfileStatusCreating}, - Target: []string{eks.FargateProfileStatusActive}, - Timeout: d.Timeout(schema.TimeoutCreate), - Refresh: refreshEksFargateProfileStatus(conn, clusterName, fargateProfileName), - } + _, err = waiter.FargateProfileCreated(conn, clusterName, fargateProfileName, d.Timeout(schema.TimeoutCreate)) - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("error waiting for EKS Fargate Profile (%s) creation: %s", d.Id(), err) + if err != nil { + return fmt.Errorf("error waiting for EKS Fargate Profile (%s) to create: %w", d.Id(), err) } return resourceAwsEksFargateProfileRead(d, meta) @@ -166,34 +165,22 @@ func resourceAwsEksFargateProfileRead(d *schema.ResourceData, meta interface{}) defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - clusterName, fargateProfileName, err := resourceAwsEksFargateProfileParseId(d.Id()) + clusterName, fargateProfileName, err := tfeks.FargateProfileParseResourceID(d.Id()) + if err != nil { return err } - input := &eks.DescribeFargateProfileInput{ - ClusterName: aws.String(clusterName), - FargateProfileName: aws.String(fargateProfileName), - } - - output, err := conn.DescribeFargateProfile(input) + fargateProfile, err := finder.FargateProfileByClusterNameAndFargateProfileName(conn, clusterName, fargateProfileName) - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] EKS Fargate Profile (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error reading EKS Fargate Profile (%s): %s", d.Id(), err) - } - - fargateProfile := output.FargateProfile - - if fargateProfile == nil { - log.Printf("[WARN] EKS Fargate Profile (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil + return fmt.Errorf("error reading EKS Fargate Profile (%s): %w", d.Id(), err) } d.Set("arn", fargateProfile.FargateProfileArn) @@ -202,13 +189,13 @@ func resourceAwsEksFargateProfileRead(d *schema.ResourceData, meta interface{}) d.Set("pod_execution_role_arn", fargateProfile.PodExecutionRoleArn) if err := d.Set("selector", flattenEksFargateProfileSelectors(fargateProfile.Selectors)); err != nil { - return fmt.Errorf("error setting selector: %s", err) + return fmt.Errorf("error setting selector: %w", err) } d.Set("status", fargateProfile.Status) if err := d.Set("subnet_ids", aws.StringValueSlice(fargateProfile.Subnets)); err != nil { - return fmt.Errorf("error setting subnets: %s", err) + return fmt.Errorf("error setting subnet_ids: %w", err) } tags := keyvaluetags.EksKeyValueTags(fargateProfile.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) @@ -231,7 +218,7 @@ func resourceAwsEksFargateProfileUpdate(d *schema.ResourceData, meta interface{} if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") if err := keyvaluetags.EksUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating tags: %s", err) + return fmt.Errorf("error updating tags: %w", err) } } @@ -241,33 +228,35 @@ func resourceAwsEksFargateProfileUpdate(d *schema.ResourceData, meta interface{} func resourceAwsEksFargateProfileDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).eksconn - clusterName, fargateProfileName, err := resourceAwsEksFargateProfileParseId(d.Id()) + clusterName, fargateProfileName, err := tfeks.FargateProfileParseResourceID(d.Id()) + if err != nil { return err } - input := &eks.DeleteFargateProfileInput{ - ClusterName: aws.String(clusterName), - FargateProfileName: aws.String(fargateProfileName), - } - // mutex lock for creation/deletion serialization mutexKey := fmt.Sprintf("%s-fargate-profiles", d.Get("cluster_name").(string)) awsMutexKV.Lock(mutexKey) defer awsMutexKV.Unlock(mutexKey) - _, err = conn.DeleteFargateProfile(input) + log.Printf("[DEBUG] Deleting EKS Fargate Profile: %s", d.Id()) + _, err = conn.DeleteFargateProfile(&eks.DeleteFargateProfileInput{ + ClusterName: aws.String(clusterName), + FargateProfileName: aws.String(fargateProfileName), + }) - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { return nil } if err != nil { - return fmt.Errorf("error deleting EKS Fargate Profile (%s): %s", d.Id(), err) + return fmt.Errorf("error deleting EKS Fargate Profile (%s): %w", d.Id(), err) } - if err := waitForEksFargateProfileDeletion(conn, clusterName, fargateProfileName, d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("error waiting for EKS Fargate Profile (%s) deletion: %s", d.Id(), err) + _, err = waiter.FargateProfileDeleted(conn, clusterName, fargateProfileName, d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return fmt.Errorf("error waiting for EKS Fargate Profile (%s) to delete: %w", d.Id(), err) } return nil @@ -321,56 +310,3 @@ func flattenEksFargateProfileSelectors(fargateProfileSelectors []*eks.FargatePro return l } - -func refreshEksFargateProfileStatus(conn *eks.EKS, clusterName string, fargateProfileName string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - input := &eks.DescribeFargateProfileInput{ - ClusterName: aws.String(clusterName), - FargateProfileName: aws.String(fargateProfileName), - } - - output, err := conn.DescribeFargateProfile(input) - - if err != nil { - return "", "", err - } - - fargateProfile := output.FargateProfile - - if fargateProfile == nil { - return fargateProfile, "", fmt.Errorf("EKS Fargate Profile (%s:%s) missing", clusterName, fargateProfileName) - } - - return fargateProfile, aws.StringValue(fargateProfile.Status), nil - } -} - -func waitForEksFargateProfileDeletion(conn *eks.EKS, clusterName string, fargateProfileName string, timeout time.Duration) error { - stateConf := resource.StateChangeConf{ - Pending: []string{ - eks.FargateProfileStatusActive, - eks.FargateProfileStatusDeleting, - }, - Target: []string{""}, - Timeout: timeout, - Refresh: refreshEksFargateProfileStatus(conn, clusterName, fargateProfileName), - } - - _, err := stateConf.WaitForState() - - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - return nil - } - - return err -} - -func resourceAwsEksFargateProfileParseId(id string) (string, string, error) { - parts := strings.Split(id, ":") - - if len(parts) != 2 || parts[0] == "" || parts[1] == "" { - return "", "", fmt.Errorf("unexpected format of ID (%s), expected cluster-name:fargate-profile-name", id) - } - - return parts[0], parts[1], nil -} diff --git a/aws/resource_aws_eks_fargate_profile_test.go b/aws/resource_aws_eks_fargate_profile_test.go index d01b7b2eed1..fceb1dde40d 100644 --- a/aws/resource_aws_eks_fargate_profile_test.go +++ b/aws/resource_aws_eks_fargate_profile_test.go @@ -5,7 +5,6 @@ import ( "log" "regexp" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/endpoints" @@ -14,6 +13,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func init() { @@ -26,56 +28,63 @@ func init() { func testSweepEksFargateProfiles(region string) error { client, err := sharedClientForRegion(region) if err != nil { - return fmt.Errorf("error getting client: %s", err) + return fmt.Errorf("error getting client: %w", err) } conn := client.(*AWSClient).eksconn - - var errors error input := &eks.ListClustersInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*testSweepResource, 0) + err = conn.ListClustersPages(input, func(page *eks.ListClustersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + for _, cluster := range page.Clusters { - clusterName := aws.StringValue(cluster) input := &eks.ListFargateProfilesInput{ ClusterName: cluster, } + err := conn.ListFargateProfilesPages(input, func(page *eks.ListFargateProfilesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + for _, profile := range page.FargateProfileNames { - profileName := aws.StringValue(profile) - log.Printf("[INFO] Deleting Fargate Profile %q", profileName) - input := &eks.DeleteFargateProfileInput{ - ClusterName: cluster, - FargateProfileName: profile, - } - _, err := conn.DeleteFargateProfile(input) - - if err != nil && !isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - errors = multierror.Append(errors, fmt.Errorf("error deleting EKS Fargate Profile %q: %w", profileName, err)) - continue - } - - if err := waitForEksFargateProfileDeletion(conn, clusterName, profileName, 10*time.Minute); err != nil { - errors = multierror.Append(errors, fmt.Errorf("error waiting for EKS Fargate Profile %q deletion: %w", profileName, err)) - continue - } + r := resourceAwsEksFargateProfile() + d := r.Data(nil) + d.SetId(tfeks.FargateProfileCreateResourceID(aws.StringValue(cluster), aws.StringValue(profile))) + + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) } - return true + + return !lastPage }) + if err != nil { - errors = multierror.Append(errors, fmt.Errorf("error listing Fargate Profiles for EKS Cluster %s: %w", clusterName, err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Fargate Profiles (%s): %w", region, err)) } } - return true + return !lastPage }) + if testSweepSkipSweepError(err) { - log.Printf("[WARN] Skipping EKS Clusters sweep for %s: %s", region, err) - return errors // In case we have completed some pages, but had errors + log.Printf("[WARN] Skipping EKS Fargate Profiles sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Clusters (%s): %w", region, err)) + } + + err = testSweepResourceOrchestrator(sweepResources) + if err != nil { - errors = multierror.Append(errors, fmt.Errorf("error retrieving EKS Clusters: %w", err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping EKS Fargate Profiles (%s): %w", region, err)) } - return errors + return sweeperErrs.ErrorOrNil() } func TestAccAWSEksFargateProfile_basic(t *testing.T) { @@ -129,7 +138,7 @@ func TestAccAWSEksFargateProfile_disappears(t *testing.T) { Config: testAccAWSEksFargateProfileConfigFargateProfileName(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSEksFargateProfileExists(resourceName, &fargateProfile), - testAccCheckAWSEksFargateProfileDisappears(&fargateProfile), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEksFargateProfile(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -242,37 +251,21 @@ func testAccCheckAWSEksFargateProfileExists(resourceName string, fargateProfile return fmt.Errorf("No EKS Fargate Profile ID is set") } - clusterName, fargateProfileName, err := resourceAwsEksFargateProfileParseId(rs.Primary.ID) + clusterName, fargateProfileName, err := tfeks.FargateProfileParseResourceID(rs.Primary.ID) + if err != nil { return err } conn := testAccProvider.Meta().(*AWSClient).eksconn - input := &eks.DescribeFargateProfileInput{ - ClusterName: aws.String(clusterName), - FargateProfileName: aws.String(fargateProfileName), - } - - output, err := conn.DescribeFargateProfile(input) + output, err := finder.FargateProfileByClusterNameAndFargateProfileName(conn, clusterName, fargateProfileName) if err != nil { return err } - if output == nil || output.FargateProfile == nil { - return fmt.Errorf("EKS Fargate Profile (%s) not found", rs.Primary.ID) - } - - if aws.StringValue(output.FargateProfile.FargateProfileName) != fargateProfileName { - return fmt.Errorf("EKS Fargate Profile (%s) not found", rs.Primary.ID) - } - - if got, want := aws.StringValue(output.FargateProfile.Status), eks.FargateProfileStatusActive; got != want { - return fmt.Errorf("EKS Fargate Profile (%s) not in %s status, got: %s", rs.Primary.ID, want, got) - } - - *fargateProfile = *output.FargateProfile + *fargateProfile = *output return nil } @@ -286,51 +279,26 @@ func testAccCheckAWSEksFargateProfileDestroy(s *terraform.State) error { continue } - clusterName, fargateProfileName, err := resourceAwsEksFargateProfileParseId(rs.Primary.ID) + clusterName, fargateProfileName, err := tfeks.FargateProfileParseResourceID(rs.Primary.ID) + if err != nil { return err } - input := &eks.DescribeFargateProfileInput{ - ClusterName: aws.String(clusterName), - FargateProfileName: aws.String(fargateProfileName), - } + _, err = finder.FargateProfileByClusterNameAndFargateProfileName(conn, clusterName, fargateProfileName) - output, err := conn.DescribeFargateProfile(input) - - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { + if tfresource.NotFound(err) { continue } - if output != nil && output.FargateProfile != nil && aws.StringValue(output.FargateProfile.FargateProfileName) == fargateProfileName { - return fmt.Errorf("EKS Fargate Profile (%s) still exists", rs.Primary.ID) - } - } - - return nil -} - -func testAccCheckAWSEksFargateProfileDisappears(fargateProfile *eks.FargateProfile) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).eksconn - - input := &eks.DeleteFargateProfileInput{ - ClusterName: fargateProfile.ClusterName, - FargateProfileName: fargateProfile.FargateProfileName, - } - - _, err := conn.DeleteFargateProfile(input) - - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - return nil - } - if err != nil { return err } - return waitForEksFargateProfileDeletion(conn, aws.StringValue(fargateProfile.ClusterName), aws.StringValue(fargateProfile.FargateProfileName), 10*time.Minute) + return fmt.Errorf("EKS Fargate Profile %s still exists", rs.Primary.ID) } + + return nil } func testAccPreCheckAWSEksFargateProfile(t *testing.T) { @@ -445,13 +413,17 @@ resource "aws_vpc" "test" { enable_dns_support = true tags = { - Name = "tf-acc-test-eks-fargate-profile" + Name = %[1]q "kubernetes.io/cluster/%[1]s" = "shared" } } resource "aws_internet_gateway" "test" { vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_route_table" "public" { @@ -461,6 +433,10 @@ resource "aws_route_table" "public" { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.test.id } + + tags = { + Name = %[1]q + } } resource "aws_main_route_table_association" "test" { @@ -476,7 +452,7 @@ resource "aws_subnet" "private" { vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test-eks-fargate-profile-private" + Name = %[1]q "kubernetes.io/cluster/%[1]s" = "shared" } } @@ -486,6 +462,10 @@ resource "aws_eip" "private" { depends_on = [aws_internet_gateway.test] vpc = true + + tags = { + Name = %[1]q + } } resource "aws_nat_gateway" "private" { @@ -493,6 +473,10 @@ resource "aws_nat_gateway" "private" { allocation_id = aws_eip.private[count.index].id subnet_id = aws_subnet.private[count.index].id + + tags = { + Name = %[1]q + } } resource "aws_route_table" "private" { @@ -504,6 +488,10 @@ resource "aws_route_table" "private" { cidr_block = "0.0.0.0/0" nat_gateway_id = aws_nat_gateway.private[count.index].id } + + tags = { + Name = %[1]q + } } resource "aws_route_table_association" "private" { @@ -521,7 +509,7 @@ resource "aws_subnet" "public" { vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test-eks-fargate-profile-public" + Name = %[1]q "kubernetes.io/cluster/%[1]s" = "shared" } } diff --git a/aws/resource_aws_eks_node_group.go b/aws/resource_aws_eks_node_group.go index f47913cd031..666d1320ba6 100644 --- a/aws/resource_aws_eks_node_group.go +++ b/aws/resource_aws_eks_node_group.go @@ -23,7 +23,6 @@ func resourceAwsEksNodeGroup() *schema.Resource { Read: resourceAwsEksNodeGroupRead, Update: resourceAwsEksNodeGroupUpdate, Delete: resourceAwsEksNodeGroupDelete, - Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, From b3d3054ead5e7b93a4d859e6c38014c95e1952d9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sun, 20 Jun 2021 18:33:13 -0400 Subject: [PATCH 15/20] r/aws_eks_node_group: Use internal waiter package. --- aws/internal/service/eks/finder/finder.go | 59 +++++ aws/internal/service/eks/waiter/status.go | 32 +++ aws/internal/service/eks/waiter/waiter.go | 60 +++++ aws/resource_aws_eks_node_group.go | 257 ++++++---------------- aws/resource_aws_eks_node_group_test.go | 108 +++------ 5 files changed, 250 insertions(+), 266 deletions(-) diff --git a/aws/internal/service/eks/finder/finder.go b/aws/internal/service/eks/finder/finder.go index ccea06c8265..ce9bb7586c6 100644 --- a/aws/internal/service/eks/finder/finder.go +++ b/aws/internal/service/eks/finder/finder.go @@ -66,6 +66,65 @@ func FargateProfileByClusterNameAndFargateProfileName(conn *eks.EKS, clusterName return output.FargateProfile, nil } +func NodegroupByClusterNameAndNodegroupName(conn *eks.EKS, clusterName, nodeGroupName string) (*eks.Nodegroup, error) { + input := &eks.DescribeNodegroupInput{ + ClusterName: aws.String(clusterName), + NodegroupName: aws.String(nodeGroupName), + } + + output, err := conn.DescribeNodegroup(input) + + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Nodegroup == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Nodegroup, nil +} + +func NodegroupUpdateByClusterNameNodegroupNameAndID(conn *eks.EKS, clusterName, nodeGroupName, id string) (*eks.Update, error) { + input := &eks.DescribeUpdateInput{ + Name: aws.String(clusterName), + NodegroupName: aws.String(nodeGroupName), + UpdateId: aws.String(id), + } + + output, err := conn.DescribeUpdate(input) + + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Update == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Update, nil +} + func UpdateByNameAndID(conn *eks.EKS, name, id string) (*eks.Update, error) { input := &eks.DescribeUpdateInput{ Name: aws.String(name), diff --git a/aws/internal/service/eks/waiter/status.go b/aws/internal/service/eks/waiter/status.go index 4552bf78a3a..59b63250314 100644 --- a/aws/internal/service/eks/waiter/status.go +++ b/aws/internal/service/eks/waiter/status.go @@ -44,6 +44,38 @@ func FargateProfileStatus(conn *eks.EKS, clusterName, fargateProfileName string) } } +func NodegroupStatus(conn *eks.EKS, clusterName, nodeGroupName string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.NodegroupByClusterNameAndNodegroupName(conn, clusterName, nodeGroupName) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + +func NodegroupUpdateStatus(conn *eks.EKS, clusterName, nodeGroupName, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.NodegroupUpdateByClusterNameNodegroupNameAndID(conn, clusterName, nodeGroupName, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + func UpdateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { output, err := finder.UpdateByNameAndID(conn, name, id) diff --git a/aws/internal/service/eks/waiter/waiter.go b/aws/internal/service/eks/waiter/waiter.go index d433fc7c607..4056d3cc003 100644 --- a/aws/internal/service/eks/waiter/waiter.go +++ b/aws/internal/service/eks/waiter/waiter.go @@ -88,6 +88,66 @@ func FargateProfileDeleted(conn *eks.EKS, clusterName, fargateProfileName string return nil, err } +func NodegroupCreated(conn *eks.EKS, clusterName, nodeGroupName string, timeout time.Duration) (*eks.Nodegroup, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.NodegroupStatusCreating}, + Target: []string{eks.NodegroupStatusActive}, + Refresh: NodegroupStatus(conn, clusterName, nodeGroupName), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.Nodegroup); ok { + return output, err + } + + return nil, err +} + +func NodegroupDeleted(conn *eks.EKS, clusterName, nodeGroupName string, timeout time.Duration) (*eks.Nodegroup, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.NodegroupStatusActive, eks.NodegroupStatusDeleting}, + Target: []string{}, + Refresh: NodegroupStatus(conn, clusterName, nodeGroupName), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.Nodegroup); ok { + return output, err + } + + return nil, err +} + +func NodegroupUpdateSuccessful(conn *eks.EKS, clusterName, nodeGroupName, id string, timeout time.Duration) (*eks.Update, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.UpdateStatusInProgress}, + Target: []string{eks.UpdateStatusSuccessful}, + Refresh: NodegroupUpdateStatus(conn, clusterName, nodeGroupName, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.Update); ok { + if status := aws.StringValue(output.Status); status == eks.UpdateStatusCancelled || status == eks.UpdateStatusFailed { + var errs *multierror.Error + + for _, e := range output.Errors { + errs = multierror.Append(errs, fmt.Errorf("%s: %s", aws.StringValue(e.ErrorCode), aws.StringValue(e.ErrorMessage))) + } + tfresource.SetLastError(err, errs.ErrorOrNil()) + } + + return output, err + } + + return nil, err +} + func UpdateSuccessful(conn *eks.EKS, name, id string, timeout time.Duration) (*eks.Update, error) { stateConf := &resource.StateChangeConf{ Pending: []string{eks.UpdateStatusInProgress}, diff --git a/aws/resource_aws_eks_node_group.go b/aws/resource_aws_eks_node_group.go index 666d1320ba6..c385f293dee 100644 --- a/aws/resource_aws_eks_node_group.go +++ b/aws/resource_aws_eks_node_group.go @@ -4,17 +4,20 @@ import ( "fmt" "log" "reflect" - "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsEksNodeGroup() *schema.Resource { @@ -259,6 +262,8 @@ func resourceAwsEksNodeGroupCreate(d *schema.ResourceData, meta interface{}) err clusterName := d.Get("cluster_name").(string) nodeGroupName := naming.Generate(d.Get("node_group_name").(string), d.Get("node_group_name_prefix").(string)) + id := tfeks.NodeGroupCreateResourceID(clusterName, nodeGroupName) + input := &eks.CreateNodegroupInput{ ClientRequestToken: aws.String(resource.UniqueId()), ClusterName: aws.String(clusterName), @@ -303,10 +308,6 @@ func resourceAwsEksNodeGroupCreate(d *schema.ResourceData, meta interface{}) err input.ScalingConfig = expandEksNodegroupScalingConfig(v) } - if len(tags) > 0 { - input.Tags = tags.IgnoreAws().EksTags() - } - if v, ok := d.GetOk("taint"); ok && v.(*schema.Set).Len() > 0 { input.Taints = expandEksTaints(v.(*schema.Set).List()) } @@ -315,23 +316,22 @@ func resourceAwsEksNodeGroupCreate(d *schema.ResourceData, meta interface{}) err input.Version = aws.String(v.(string)) } + if len(tags) > 0 { + input.Tags = tags.IgnoreAws().EksTags() + } + _, err := conn.CreateNodegroup(input) if err != nil { - return fmt.Errorf("error creating EKS Node Group (%s/%s): %w", clusterName, nodeGroupName, err) + return fmt.Errorf("error creating EKS Node Group (%s): %w", id, err) } - d.SetId(tfeks.NodeGroupCreateResourceID(clusterName, nodeGroupName)) + d.SetId(id) - stateConf := resource.StateChangeConf{ - Pending: []string{eks.NodegroupStatusCreating}, - Target: []string{eks.NodegroupStatusActive}, - Timeout: d.Timeout(schema.TimeoutCreate), - Refresh: refreshEksNodeGroupStatus(conn, clusterName, nodeGroupName), - } + _, err = waiter.NodegroupCreated(conn, clusterName, nodeGroupName, d.Timeout(schema.TimeoutCreate)) - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("error waiting for EKS Node Group (%s) creation: %w", d.Id(), err) + if err != nil { + return fmt.Errorf("error waiting for EKS Node Groupe (%s) to create: %w", d.Id(), err) } return resourceAwsEksNodeGroupRead(d, meta) @@ -348,14 +348,9 @@ func resourceAwsEksNodeGroupRead(d *schema.ResourceData, meta interface{}) error return err } - input := &eks.DescribeNodegroupInput{ - ClusterName: aws.String(clusterName), - NodegroupName: aws.String(nodeGroupName), - } - - output, err := conn.DescribeNodegroup(input) + nodeGroup, err := finder.NodegroupByClusterNameAndNodegroupName(conn, clusterName, nodeGroupName) - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] EKS Node Group (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -365,13 +360,6 @@ func resourceAwsEksNodeGroupRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("error reading EKS Node Group (%s): %w", d.Id(), err) } - nodeGroup := output.Nodegroup - if nodeGroup == nil { - log.Printf("[WARN] EKS Node Group (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - d.Set("ami_type", nodeGroup.AmiType) d.Set("arn", nodeGroup.NodegroupArn) d.Set("capacity_type", nodeGroup.CapacityType) @@ -413,6 +401,12 @@ func resourceAwsEksNodeGroupRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("error setting subnets: %w", err) } + if err := d.Set("taint", flattenEksTaints(nodeGroup.Taints)); err != nil { + return fmt.Errorf("error setting taint: %w", err) + } + + d.Set("version", nodeGroup.Version) + tags := keyvaluetags.EksKeyValueTags(nodeGroup.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 @@ -424,12 +418,6 @@ func resourceAwsEksNodeGroupRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("error setting tags_all: %w", err) } - if err := d.Set("taint", flattenEksTaints(nodeGroup.Taints)); err != nil { - return fmt.Errorf("error setting taint: %w", err) - } - - d.Set("version", nodeGroup.Version) - return nil } @@ -442,41 +430,7 @@ func resourceAwsEksNodeGroupUpdate(d *schema.ResourceData, meta interface{}) err return err } - if d.HasChanges("labels", "scaling_config", "taint") { - oldLabelsRaw, newLabelsRaw := d.GetChange("labels") - - input := &eks.UpdateNodegroupConfigInput{ - ClientRequestToken: aws.String(resource.UniqueId()), - ClusterName: aws.String(clusterName), - Labels: expandEksUpdateLabelsPayload(oldLabelsRaw, newLabelsRaw), - NodegroupName: aws.String(nodeGroupName), - } - - if v := d.Get("scaling_config").([]interface{}); len(v) > 0 { - input.ScalingConfig = expandEksNodegroupScalingConfig(v) - } - - oldTaintsRaw, newTaintsRaw := d.GetChange("taint") - input.Taints = expandEksUpdateTaintsPayload(oldTaintsRaw.(*schema.Set).List(), newTaintsRaw.(*schema.Set).List()) - - output, err := conn.UpdateNodegroupConfig(input) - - if err != nil { - return fmt.Errorf("error updating EKS Node Group (%s) config: %w", d.Id(), err) - } - - if output == nil || output.Update == nil || output.Update.Id == nil { - return fmt.Errorf("error determining EKS Node Group (%s) config update ID: empty response", d.Id()) - } - - updateID := aws.StringValue(output.Update.Id) - - err = waitForEksNodeGroupUpdate(conn, clusterName, nodeGroupName, updateID, d.Timeout(schema.TimeoutUpdate)) - if err != nil { - return fmt.Errorf("error waiting for EKS Node Group (%s) config update (%s): %w", d.Id(), updateID, err) - } - } - + // Do any version update first. if d.HasChanges("launch_template", "release_version", "version") { input := &eks.UpdateNodegroupVersionInput{ ClientRequestToken: aws.String(resource.UniqueId()), @@ -519,15 +473,44 @@ func resourceAwsEksNodeGroupUpdate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("error updating EKS Node Group (%s) version: %w", d.Id(), err) } - if output == nil || output.Update == nil || output.Update.Id == nil { - return fmt.Errorf("error determining EKS Node Group (%s) version update ID: empty response", d.Id()) + updateID := aws.StringValue(output.Update.Id) + + _, err = waiter.NodegroupUpdateSuccessful(conn, clusterName, nodeGroupName, updateID, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("error waiting for EKS Node Group (%s) version update (%s): %w", d.Id(), updateID, err) + } + } + + if d.HasChanges("labels", "scaling_config", "taint") { + oldLabelsRaw, newLabelsRaw := d.GetChange("labels") + + input := &eks.UpdateNodegroupConfigInput{ + ClientRequestToken: aws.String(resource.UniqueId()), + ClusterName: aws.String(clusterName), + Labels: expandEksUpdateLabelsPayload(oldLabelsRaw, newLabelsRaw), + NodegroupName: aws.String(nodeGroupName), + } + + if v := d.Get("scaling_config").([]interface{}); len(v) > 0 { + input.ScalingConfig = expandEksNodegroupScalingConfig(v) + } + + oldTaintsRaw, newTaintsRaw := d.GetChange("taint") + input.Taints = expandEksUpdateTaintsPayload(oldTaintsRaw.(*schema.Set).List(), newTaintsRaw.(*schema.Set).List()) + + output, err := conn.UpdateNodegroupConfig(input) + + if err != nil { + return fmt.Errorf("error updating EKS Node Group (%s) config: %w", d.Id(), err) } updateID := aws.StringValue(output.Update.Id) - err = waitForEksNodeGroupUpdate(conn, clusterName, nodeGroupName, updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.NodegroupUpdateSuccessful(conn, clusterName, nodeGroupName, updateID, d.Timeout(schema.TimeoutUpdate)) + if err != nil { - return fmt.Errorf("error waiting for EKS Node Group (%s) version update (%s): %w", d.Id(), updateID, err) + return fmt.Errorf("error waiting for EKS Node Group (%s) config update (%s): %w", d.Id(), updateID, err) } } @@ -550,14 +533,13 @@ func resourceAwsEksNodeGroupDelete(d *schema.ResourceData, meta interface{}) err return err } - input := &eks.DeleteNodegroupInput{ + log.Printf("[DEBUG] Deleting EKS Node Group: %s", d.Id()) + _, err = conn.DeleteNodegroup(&eks.DeleteNodegroupInput{ ClusterName: aws.String(clusterName), NodegroupName: aws.String(nodeGroupName), - } + }) - _, err = conn.DeleteNodegroup(input) - - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { return nil } @@ -565,8 +547,10 @@ func resourceAwsEksNodeGroupDelete(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("error deleting EKS Node Group (%s): %w", d.Id(), err) } - if err := waitForEksNodeGroupDeletion(conn, clusterName, nodeGroupName, d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("error waiting for EKS Node Group (%s) deletion: %w", d.Id(), err) + _, err = waiter.NodegroupDeleted(conn, clusterName, nodeGroupName, d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return fmt.Errorf("error waiting for EKS Node Group (%s) to delete: %w", d.Id(), err) } return nil @@ -868,114 +852,3 @@ func flattenEksTaints(taints []*eks.Taint) []interface{} { } return results } - -func refreshEksNodeGroupStatus(conn *eks.EKS, clusterName string, nodeGroupName string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - input := &eks.DescribeNodegroupInput{ - ClusterName: aws.String(clusterName), - NodegroupName: aws.String(nodeGroupName), - } - - output, err := conn.DescribeNodegroup(input) - - if err != nil { - return "", "", err - } - - nodeGroup := output.Nodegroup - - if nodeGroup == nil { - return nodeGroup, "", fmt.Errorf("EKS Node Group (%s) missing", tfeks.NodeGroupCreateResourceID(clusterName, nodeGroupName)) - } - - status := aws.StringValue(nodeGroup.Status) - - // Return enhanced error messaging if available, instead of: - // unexpected state 'CREATE_FAILED', wanted target 'ACTIVE'. last error: %!s() - if status == eks.NodegroupStatusCreateFailed || status == eks.NodegroupStatusDeleteFailed { - if nodeGroup.Health == nil || len(nodeGroup.Health.Issues) == 0 || nodeGroup.Health.Issues[0] == nil { - return nodeGroup, status, fmt.Errorf("unable to find additional information about %s status, check EKS Node Group (%s) health", status, tfeks.NodeGroupCreateResourceID(clusterName, nodeGroupName)) - } - - issue := nodeGroup.Health.Issues[0] - - return nodeGroup, status, fmt.Errorf("%s: %s. Resource IDs: %v", aws.StringValue(issue.Code), aws.StringValue(issue.Message), aws.StringValueSlice(issue.ResourceIds)) - } - - return nodeGroup, status, nil - } -} - -func refreshEksNodeGroupUpdateStatus(conn *eks.EKS, clusterName string, nodeGroupName string, updateID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - input := &eks.DescribeUpdateInput{ - Name: aws.String(clusterName), - NodegroupName: aws.String(nodeGroupName), - UpdateId: aws.String(updateID), - } - - output, err := conn.DescribeUpdate(input) - - if err != nil { - return nil, "", err - } - - if output == nil || output.Update == nil { - return nil, "", fmt.Errorf("EKS Node Group (%s) update (%s) missing", tfeks.NodeGroupCreateResourceID(clusterName, nodeGroupName), updateID) - } - - return output.Update, aws.StringValue(output.Update.Status), nil - } -} - -func waitForEksNodeGroupDeletion(conn *eks.EKS, clusterName string, nodeGroupName string, timeout time.Duration) error { - stateConf := resource.StateChangeConf{ - Pending: []string{ - eks.NodegroupStatusActive, - eks.NodegroupStatusDeleting, - }, - Target: []string{""}, - Timeout: timeout, - Refresh: refreshEksNodeGroupStatus(conn, clusterName, nodeGroupName), - } - - _, err := stateConf.WaitForState() - - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - return nil - } - - return err -} - -func waitForEksNodeGroupUpdate(conn *eks.EKS, clusterName, nodeGroupName string, updateID string, timeout time.Duration) error { - stateConf := resource.StateChangeConf{ - Pending: []string{eks.UpdateStatusInProgress}, - Target: []string{ - eks.UpdateStatusCancelled, - eks.UpdateStatusFailed, - eks.UpdateStatusSuccessful, - }, - Timeout: timeout, - Refresh: refreshEksNodeGroupUpdateStatus(conn, clusterName, nodeGroupName, updateID), - } - - updateRaw, err := stateConf.WaitForState() - - if err != nil { - return err - } - - update := updateRaw.(*eks.Update) - - if aws.StringValue(update.Status) == eks.UpdateStatusSuccessful { - return nil - } - - var detailedErrors []string - for i, updateError := range update.Errors { - detailedErrors = append(detailedErrors, fmt.Sprintf("Error %d: Code: %s / Message: %s", i+1, aws.StringValue(updateError.ErrorCode), aws.StringValue(updateError.ErrorMessage))) - } - - return fmt.Errorf("EKS Node Group (%s) update (%s) status (%s) not successful: Errors:\n%s", clusterName, updateID, aws.StringValue(update.Status), strings.Join(detailedErrors, "\n")) -} diff --git a/aws/resource_aws_eks_node_group_test.go b/aws/resource_aws_eks_node_group_test.go index 927f6302701..90d5c2cc55c 100644 --- a/aws/resource_aws_eks_node_group_test.go +++ b/aws/resource_aws_eks_node_group_test.go @@ -5,7 +5,6 @@ import ( "log" "regexp" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" @@ -15,6 +14,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/terraform-providers/terraform-provider-aws/aws/internal/naming" tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func init() { @@ -28,64 +29,64 @@ func init() { func testSweepEksNodeGroups(region string) error { client, err := sharedClientForRegion(region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.(*AWSClient).eksconn - sweepResources := make([]*testSweepResource, 0) - var errs *multierror.Error - input := &eks.ListClustersInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*testSweepResource, 0) err = conn.ListClustersPages(input, func(page *eks.ListClustersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + for _, cluster := range page.Clusters { - clusterName := aws.StringValue(cluster) input := &eks.ListNodegroupsInput{ ClusterName: cluster, } err := conn.ListNodegroupsPages(input, func(page *eks.ListNodegroupsOutput, lastPage bool) bool { - for _, nodeGroup := range page.Nodegroups { - nodeGroupName := aws.StringValue(nodeGroup) + if page == nil { + return !lastPage + } + for _, nodeGroup := range page.Nodegroups { r := resourceAwsEksNodeGroup() d := r.Data(nil) - - d.Set("cluster_name", clusterName) - d.Set("node_group_name", nodeGroupName) - d.SetId(tfeks.NodeGroupCreateResourceID(clusterName, nodeGroupName)) + d.SetId(tfeks.NodeGroupCreateResourceID(aws.StringValue(cluster), aws.StringValue(nodeGroup))) sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) } + return !lastPage }) if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing EKS Node Groups: %w", err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Node Groups (%s): %w", region, err)) } } return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing EKS Clusters: %w", err)) + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping EKS Node Groups sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors } - if err = testSweepResourceOrchestrator(sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping EKS Node Groups for %s: %w", region, err)) + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Clusters (%s): %w", region, err)) } - // waiting for deletion is not necessary in the sweeper since the resource's delete waits + err = testSweepResourceOrchestrator(sweepResources) - if testSweepSkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping EKS Node Group sweep for %s: %s", region, errs) - return nil + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping EKS Node Groups (%s): %w", region, err)) } - return errs.ErrorOrNil() + return sweeperErrs.ErrorOrNil() } func TestAccAWSEksNodeGroup_basic(t *testing.T) { @@ -210,7 +211,7 @@ func TestAccAWSEksNodeGroup_disappears(t *testing.T) { Config: testAccAWSEksNodeGroupConfigNodeGroupName(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSEksNodeGroupExists(resourceName, &nodeGroup), - testAccCheckAWSEksNodeGroupDisappears(&nodeGroup), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEksNodeGroup(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -986,36 +987,20 @@ func testAccCheckAWSEksNodeGroupExists(resourceName string, nodeGroup *eks.Nodeg } clusterName, nodeGroupName, err := tfeks.NodeGroupParseResourceID(rs.Primary.ID) + if err != nil { return err } conn := testAccProvider.Meta().(*AWSClient).eksconn - input := &eks.DescribeNodegroupInput{ - ClusterName: aws.String(clusterName), - NodegroupName: aws.String(nodeGroupName), - } - - output, err := conn.DescribeNodegroup(input) + output, err := finder.NodegroupByClusterNameAndNodegroupName(conn, clusterName, nodeGroupName) if err != nil { return err } - if output == nil || output.Nodegroup == nil { - return fmt.Errorf("EKS Node Group (%s) not found", rs.Primary.ID) - } - - if aws.StringValue(output.Nodegroup.NodegroupName) != nodeGroupName { - return fmt.Errorf("EKS Node Group (%s) not found", rs.Primary.ID) - } - - if got, want := aws.StringValue(output.Nodegroup.Status), eks.NodegroupStatusActive; got != want { - return fmt.Errorf("EKS Node Group (%s) not in %s status, got: %s", rs.Primary.ID, want, got) - } - - *nodeGroup = *output.Nodegroup + *nodeGroup = *output return nil } @@ -1030,50 +1015,25 @@ func testAccCheckAWSEksNodeGroupDestroy(s *terraform.State) error { } clusterName, nodeGroupName, err := tfeks.NodeGroupParseResourceID(rs.Primary.ID) + if err != nil { return err } - input := &eks.DescribeNodegroupInput{ - ClusterName: aws.String(clusterName), - NodegroupName: aws.String(nodeGroupName), - } - - output, err := conn.DescribeNodegroup(input) + _, err = finder.NodegroupByClusterNameAndNodegroupName(conn, clusterName, nodeGroupName) - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { + if tfresource.NotFound(err) { continue } - if output != nil && output.Nodegroup != nil && aws.StringValue(output.Nodegroup.NodegroupName) == nodeGroupName { - return fmt.Errorf("EKS Node Group (%s) still exists", rs.Primary.ID) - } - } - - return nil -} - -func testAccCheckAWSEksNodeGroupDisappears(nodeGroup *eks.Nodegroup) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).eksconn - - input := &eks.DeleteNodegroupInput{ - ClusterName: nodeGroup.ClusterName, - NodegroupName: nodeGroup.NodegroupName, - } - - _, err := conn.DeleteNodegroup(input) - - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - return nil - } - if err != nil { return err } - return waitForEksNodeGroupDeletion(conn, aws.StringValue(nodeGroup.ClusterName), aws.StringValue(nodeGroup.NodegroupName), 60*time.Minute) + return fmt.Errorf("EKS Node Group %s still exists", rs.Primary.ID) } + + return nil } func testAccCheckAWSEksNodeGroupNotRecreated(i, j *eks.Nodegroup) resource.TestCheckFunc { From 81a9cf6141650bb7a94da65d6de64e7151da651a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sun, 20 Jun 2021 18:36:40 -0400 Subject: [PATCH 16/20] EKS updates are per resource type. --- aws/internal/service/eks/finder/finder.go | 48 ++++++++++----------- aws/internal/service/eks/waiter/status.go | 16 +++---- aws/internal/service/eks/waiter/waiter.go | 52 +++++++++++------------ aws/resource_aws_eks_cluster.go | 8 ++-- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/aws/internal/service/eks/finder/finder.go b/aws/internal/service/eks/finder/finder.go index ce9bb7586c6..2c27bdca3c0 100644 --- a/aws/internal/service/eks/finder/finder.go +++ b/aws/internal/service/eks/finder/finder.go @@ -37,13 +37,13 @@ func ClusterByName(conn *eks.EKS, name string) (*eks.Cluster, error) { return output.Cluster, nil } -func FargateProfileByClusterNameAndFargateProfileName(conn *eks.EKS, clusterName, fargateProfileName string) (*eks.FargateProfile, error) { - input := &eks.DescribeFargateProfileInput{ - ClusterName: aws.String(clusterName), - FargateProfileName: aws.String(fargateProfileName), +func ClusterUpdateByNameAndID(conn *eks.EKS, name, id string) (*eks.Update, error) { + input := &eks.DescribeUpdateInput{ + Name: aws.String(name), + UpdateId: aws.String(id), } - output, err := conn.DescribeFargateProfile(input) + output, err := conn.DescribeUpdate(input) if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { return nil, &resource.NotFoundError{ @@ -56,23 +56,23 @@ func FargateProfileByClusterNameAndFargateProfileName(conn *eks.EKS, clusterName return nil, err } - if output == nil || output.FargateProfile == nil { + if output == nil || output.Update == nil { return nil, &resource.NotFoundError{ Message: "Empty result", LastRequest: input, } } - return output.FargateProfile, nil + return output.Update, nil } -func NodegroupByClusterNameAndNodegroupName(conn *eks.EKS, clusterName, nodeGroupName string) (*eks.Nodegroup, error) { - input := &eks.DescribeNodegroupInput{ - ClusterName: aws.String(clusterName), - NodegroupName: aws.String(nodeGroupName), +func FargateProfileByClusterNameAndFargateProfileName(conn *eks.EKS, clusterName, fargateProfileName string) (*eks.FargateProfile, error) { + input := &eks.DescribeFargateProfileInput{ + ClusterName: aws.String(clusterName), + FargateProfileName: aws.String(fargateProfileName), } - output, err := conn.DescribeNodegroup(input) + output, err := conn.DescribeFargateProfile(input) if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { return nil, &resource.NotFoundError{ @@ -85,24 +85,23 @@ func NodegroupByClusterNameAndNodegroupName(conn *eks.EKS, clusterName, nodeGrou return nil, err } - if output == nil || output.Nodegroup == nil { + if output == nil || output.FargateProfile == nil { return nil, &resource.NotFoundError{ Message: "Empty result", LastRequest: input, } } - return output.Nodegroup, nil + return output.FargateProfile, nil } -func NodegroupUpdateByClusterNameNodegroupNameAndID(conn *eks.EKS, clusterName, nodeGroupName, id string) (*eks.Update, error) { - input := &eks.DescribeUpdateInput{ - Name: aws.String(clusterName), +func NodegroupByClusterNameAndNodegroupName(conn *eks.EKS, clusterName, nodeGroupName string) (*eks.Nodegroup, error) { + input := &eks.DescribeNodegroupInput{ + ClusterName: aws.String(clusterName), NodegroupName: aws.String(nodeGroupName), - UpdateId: aws.String(id), } - output, err := conn.DescribeUpdate(input) + output, err := conn.DescribeNodegroup(input) if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { return nil, &resource.NotFoundError{ @@ -115,20 +114,21 @@ func NodegroupUpdateByClusterNameNodegroupNameAndID(conn *eks.EKS, clusterName, return nil, err } - if output == nil || output.Update == nil { + if output == nil || output.Nodegroup == nil { return nil, &resource.NotFoundError{ Message: "Empty result", LastRequest: input, } } - return output.Update, nil + return output.Nodegroup, nil } -func UpdateByNameAndID(conn *eks.EKS, name, id string) (*eks.Update, error) { +func NodegroupUpdateByClusterNameNodegroupNameAndID(conn *eks.EKS, clusterName, nodeGroupName, id string) (*eks.Update, error) { input := &eks.DescribeUpdateInput{ - Name: aws.String(name), - UpdateId: aws.String(id), + Name: aws.String(clusterName), + NodegroupName: aws.String(nodeGroupName), + UpdateId: aws.String(id), } output, err := conn.DescribeUpdate(input) diff --git a/aws/internal/service/eks/waiter/status.go b/aws/internal/service/eks/waiter/status.go index 59b63250314..572bd1f84e4 100644 --- a/aws/internal/service/eks/waiter/status.go +++ b/aws/internal/service/eks/waiter/status.go @@ -28,9 +28,9 @@ func ClusterStatus(conn *eks.EKS, name string) resource.StateRefreshFunc { } } -func FargateProfileStatus(conn *eks.EKS, clusterName, fargateProfileName string) resource.StateRefreshFunc { +func ClusterUpdateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.FargateProfileByClusterNameAndFargateProfileName(conn, clusterName, fargateProfileName) + output, err := finder.ClusterUpdateByNameAndID(conn, name, id) if tfresource.NotFound(err) { return nil, "", nil @@ -44,9 +44,9 @@ func FargateProfileStatus(conn *eks.EKS, clusterName, fargateProfileName string) } } -func NodegroupStatus(conn *eks.EKS, clusterName, nodeGroupName string) resource.StateRefreshFunc { +func FargateProfileStatus(conn *eks.EKS, clusterName, fargateProfileName string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.NodegroupByClusterNameAndNodegroupName(conn, clusterName, nodeGroupName) + output, err := finder.FargateProfileByClusterNameAndFargateProfileName(conn, clusterName, fargateProfileName) if tfresource.NotFound(err) { return nil, "", nil @@ -60,9 +60,9 @@ func NodegroupStatus(conn *eks.EKS, clusterName, nodeGroupName string) resource. } } -func NodegroupUpdateStatus(conn *eks.EKS, clusterName, nodeGroupName, id string) resource.StateRefreshFunc { +func NodegroupStatus(conn *eks.EKS, clusterName, nodeGroupName string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.NodegroupUpdateByClusterNameNodegroupNameAndID(conn, clusterName, nodeGroupName, id) + output, err := finder.NodegroupByClusterNameAndNodegroupName(conn, clusterName, nodeGroupName) if tfresource.NotFound(err) { return nil, "", nil @@ -76,9 +76,9 @@ func NodegroupUpdateStatus(conn *eks.EKS, clusterName, nodeGroupName, id string) } } -func UpdateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { +func NodegroupUpdateStatus(conn *eks.EKS, clusterName, nodeGroupName, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.UpdateByNameAndID(conn, name, id) + output, err := finder.NodegroupUpdateByClusterNameNodegroupNameAndID(conn, clusterName, nodeGroupName, id) if tfresource.NotFound(err) { return nil, "", nil diff --git a/aws/internal/service/eks/waiter/waiter.go b/aws/internal/service/eks/waiter/waiter.go index 4056d3cc003..d4ae7299ff7 100644 --- a/aws/internal/service/eks/waiter/waiter.go +++ b/aws/internal/service/eks/waiter/waiter.go @@ -54,6 +54,32 @@ func ClusterDeleted(conn *eks.EKS, name string, timeout time.Duration) (*eks.Clu return nil, err } +func ClusterUpdateSuccessful(conn *eks.EKS, name, id string, timeout time.Duration) (*eks.Update, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.UpdateStatusInProgress}, + Target: []string{eks.UpdateStatusSuccessful}, + Refresh: ClusterUpdateStatus(conn, name, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*eks.Update); ok { + if status := aws.StringValue(output.Status); status == eks.UpdateStatusCancelled || status == eks.UpdateStatusFailed { + var errs *multierror.Error + + for _, e := range output.Errors { + errs = multierror.Append(errs, fmt.Errorf("%s: %s", aws.StringValue(e.ErrorCode), aws.StringValue(e.ErrorMessage))) + } + tfresource.SetLastError(err, errs.ErrorOrNil()) + } + + return output, err + } + + return nil, err +} + func FargateProfileCreated(conn *eks.EKS, clusterName, fargateProfileName string, timeout time.Duration) (*eks.FargateProfile, error) { stateConf := &resource.StateChangeConf{ Pending: []string{eks.FargateProfileStatusCreating}, @@ -148,32 +174,6 @@ func NodegroupUpdateSuccessful(conn *eks.EKS, clusterName, nodeGroupName, id str return nil, err } -func UpdateSuccessful(conn *eks.EKS, name, id string, timeout time.Duration) (*eks.Update, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{eks.UpdateStatusInProgress}, - Target: []string{eks.UpdateStatusSuccessful}, - Refresh: UpdateStatus(conn, name, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForState() - - if output, ok := outputRaw.(*eks.Update); ok { - if status := aws.StringValue(output.Status); status == eks.UpdateStatusCancelled || status == eks.UpdateStatusFailed { - var errs *multierror.Error - - for _, e := range output.Errors { - errs = multierror.Append(errs, fmt.Errorf("%s: %s", aws.StringValue(e.ErrorCode), aws.StringValue(e.ErrorMessage))) - } - tfresource.SetLastError(err, errs.ErrorOrNil()) - } - - return output, err - } - - return nil, err -} - // EksAddonCreated waits for a EKS add-on to return status "ACTIVE" or "CREATE_FAILED" func EksAddonCreated(ctx context.Context, conn *eks.EKS, clusterName, addonName string) (*eks.Addon, error) { stateConf := resource.StateChangeConf{ diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index fd0a6e4da6e..83f552a55ae 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -400,7 +400,7 @@ func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error updateID := aws.StringValue(output.Update.Id) - _, err = waiter.UpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.ClusterUpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) if err != nil { return fmt.Errorf("error waiting for EKS Cluster (%s) version update (%s): %w", d.Id(), updateID, err) @@ -422,7 +422,7 @@ func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error updateID := aws.StringValue(output.Update.Id) - _, err = waiter.UpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.ClusterUpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) if err != nil { return fmt.Errorf("error waiting for EKS Cluster (%s) encryption config association (%s): %w", d.Id(), updateID, err) @@ -444,7 +444,7 @@ func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error updateID := aws.StringValue(output.Update.Id) - _, err = waiter.UpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.ClusterUpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) if err != nil { return fmt.Errorf("error waiting for EKS Cluster (%s) logging update (%s): %w", d.Id(), updateID, err) @@ -466,7 +466,7 @@ func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error updateID := aws.StringValue(output.Update.Id) - _, err = waiter.UpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) + _, err = waiter.ClusterUpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate)) if err != nil { return fmt.Errorf("error waiting for EKS Cluster (%s) VPC config update (%s): %w", d.Id(), updateID, err) From 21297ccd2d03357f287b450d29ed7171971e89a4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 21 Jun 2021 09:52:40 -0400 Subject: [PATCH 17/20] r/aws_eks_addon: Use internal waiter package. --- aws/internal/service/eks/finder/finder.go | 61 +++++++ aws/internal/service/eks/id.go | 19 ++ aws/internal/service/eks/waiter/status.go | 62 +++---- aws/internal/service/eks/waiter/waiter.go | 174 ++++++++---------- aws/resource_aws_eks_addon.go | 213 ++++++++++------------ aws/resource_aws_eks_addon_test.go | 130 ++++++------- 6 files changed, 338 insertions(+), 321 deletions(-) diff --git a/aws/internal/service/eks/finder/finder.go b/aws/internal/service/eks/finder/finder.go index 2c27bdca3c0..8aef2b0ba6c 100644 --- a/aws/internal/service/eks/finder/finder.go +++ b/aws/internal/service/eks/finder/finder.go @@ -1,12 +1,73 @@ package finder import ( + "context" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) +func AddonByClusterNameAndAddonName(ctx context.Context, conn *eks.EKS, clusterName, addonName string) (*eks.Addon, error) { + input := &eks.DescribeAddonInput{ + AddonName: aws.String(addonName), + ClusterName: aws.String(clusterName), + } + + output, err := conn.DescribeAddonWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Addon == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Addon, nil +} + +func AddonUpdateByClusterNameAddonNameAndID(ctx context.Context, conn *eks.EKS, clusterName, addonName, id string) (*eks.Update, error) { + input := &eks.DescribeUpdateInput{ + AddonName: aws.String(addonName), + Name: aws.String(clusterName), + UpdateId: aws.String(id), + } + + output, err := conn.DescribeUpdateWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Update == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Update, nil +} + func ClusterByName(conn *eks.EKS, name string) (*eks.Cluster, error) { input := &eks.DescribeClusterInput{ Name: aws.String(name), diff --git a/aws/internal/service/eks/id.go b/aws/internal/service/eks/id.go index 3fe8d2a776c..8aec4cf9a3e 100644 --- a/aws/internal/service/eks/id.go +++ b/aws/internal/service/eks/id.go @@ -5,6 +5,25 @@ import ( "strings" ) +const addonResourceIDSeparator = ":" + +func AddonCreateResourceID(clusterName, addonName string) string { + parts := []string{clusterName, addonName} + id := strings.Join(parts, addonResourceIDSeparator) + + return id +} + +func AddonParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, addonResourceIDSeparator) + + 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]saddon-name", id, addonResourceIDSeparator) +} + const fargateProfileResourceIDSeparator = ":" func FargateProfileCreateResourceID(clusterName, fargateProfileName string) string { diff --git a/aws/internal/service/eks/waiter/status.go b/aws/internal/service/eks/waiter/status.go index 572bd1f84e4..96a1a3daa46 100644 --- a/aws/internal/service/eks/waiter/status.go +++ b/aws/internal/service/eks/waiter/status.go @@ -2,19 +2,17 @@ package waiter import ( "context" - "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) -func ClusterStatus(conn *eks.EKS, name string) resource.StateRefreshFunc { +func AddonStatus(ctx context.Context, conn *eks.EKS, clusterName, addonName string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.ClusterByName(conn, name) + output, err := finder.AddonByClusterNameAndAddonName(ctx, conn, clusterName, addonName) if tfresource.NotFound(err) { return nil, "", nil @@ -28,9 +26,9 @@ func ClusterStatus(conn *eks.EKS, name string) resource.StateRefreshFunc { } } -func ClusterUpdateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { +func AddonUpdateStatus(ctx context.Context, conn *eks.EKS, clusterName, addonName, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.ClusterUpdateByNameAndID(conn, name, id) + output, err := finder.AddonUpdateByClusterNameAddonNameAndID(ctx, conn, clusterName, addonName, id) if tfresource.NotFound(err) { return nil, "", nil @@ -44,9 +42,9 @@ func ClusterUpdateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFu } } -func FargateProfileStatus(conn *eks.EKS, clusterName, fargateProfileName string) resource.StateRefreshFunc { +func ClusterStatus(conn *eks.EKS, name string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.FargateProfileByClusterNameAndFargateProfileName(conn, clusterName, fargateProfileName) + output, err := finder.ClusterByName(conn, name) if tfresource.NotFound(err) { return nil, "", nil @@ -60,9 +58,9 @@ func FargateProfileStatus(conn *eks.EKS, clusterName, fargateProfileName string) } } -func NodegroupStatus(conn *eks.EKS, clusterName, nodeGroupName string) resource.StateRefreshFunc { +func ClusterUpdateStatus(conn *eks.EKS, name, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.NodegroupByClusterNameAndNodegroupName(conn, clusterName, nodeGroupName) + output, err := finder.ClusterUpdateByNameAndID(conn, name, id) if tfresource.NotFound(err) { return nil, "", nil @@ -76,9 +74,9 @@ func NodegroupStatus(conn *eks.EKS, clusterName, nodeGroupName string) resource. } } -func NodegroupUpdateStatus(conn *eks.EKS, clusterName, nodeGroupName, id string) resource.StateRefreshFunc { +func FargateProfileStatus(conn *eks.EKS, clusterName, fargateProfileName string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := finder.NodegroupUpdateByClusterNameNodegroupNameAndID(conn, clusterName, nodeGroupName, id) + output, err := finder.FargateProfileByClusterNameAndFargateProfileName(conn, clusterName, fargateProfileName) if tfresource.NotFound(err) { return nil, "", nil @@ -92,38 +90,34 @@ func NodegroupUpdateStatus(conn *eks.EKS, clusterName, nodeGroupName, id string) } } -func EksAddonStatus(ctx context.Context, conn *eks.EKS, addonName, clusterName string) resource.StateRefreshFunc { +func NodegroupStatus(conn *eks.EKS, clusterName, nodeGroupName string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := conn.DescribeAddonWithContext(ctx, &eks.DescribeAddonInput{ - ClusterName: aws.String(clusterName), - AddonName: aws.String(addonName), - }) - if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { + output, err := finder.NodegroupByClusterNameAndNodegroupName(conn, clusterName, nodeGroupName) + + if tfresource.NotFound(err) { return nil, "", nil } + if err != nil { - return output, "", err - } - if output == nil || output.Addon == nil { - return nil, "", fmt.Errorf("EKS Cluster (%s) add-on (%s) missing", clusterName, addonName) + return nil, "", err } - return output.Addon, aws.StringValue(output.Addon.Status), nil + + return output, aws.StringValue(output.Status), nil } } -func EksAddonUpdateStatus(ctx context.Context, conn *eks.EKS, clusterName, addonName, updateID string) resource.StateRefreshFunc { +func NodegroupUpdateStatus(conn *eks.EKS, clusterName, nodeGroupName, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := conn.DescribeUpdateWithContext(ctx, &eks.DescribeUpdateInput{ - Name: aws.String(clusterName), - AddonName: aws.String(addonName), - UpdateId: aws.String(updateID), - }) - if err != nil { - return output, "", err + output, err := finder.NodegroupUpdateByClusterNameNodegroupNameAndID(conn, clusterName, nodeGroupName, id) + + if tfresource.NotFound(err) { + return nil, "", nil } - if output == nil || output.Update == nil { - return nil, "", fmt.Errorf("EKS Cluster (%s) add-on (%s) update (%s) missing", clusterName, addonName, updateID) + + if err != nil { + return nil, "", err } - return output.Update, aws.StringValue(output.Update.Status), nil + + return output, aws.StringValue(output.Status), nil } } diff --git a/aws/internal/service/eks/waiter/waiter.go b/aws/internal/service/eks/waiter/waiter.go index d4ae7299ff7..171f55c27fe 100644 --- a/aws/internal/service/eks/waiter/waiter.go +++ b/aws/internal/service/eks/waiter/waiter.go @@ -3,23 +3,90 @@ package waiter import ( "context" "fmt" - "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) const ( - EksAddonCreatedTimeout = 20 * time.Minute - EksAddonUpdatedTimeout = 20 * time.Minute - EksAddonDeletedTimeout = 40 * time.Minute + AddonCreatedTimeout = 20 * time.Minute + AddonUpdatedTimeout = 20 * time.Minute + AddonDeletedTimeout = 40 * time.Minute ) +func AddonCreated(ctx context.Context, conn *eks.EKS, clusterName, addonName string) (*eks.Addon, error) { + stateConf := resource.StateChangeConf{ + Pending: []string{eks.AddonStatusCreating}, + Target: []string{eks.AddonStatusActive}, + Refresh: AddonStatus(ctx, conn, clusterName, addonName), + Timeout: AddonCreatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*eks.Addon); ok { + if status, health := aws.StringValue(output.Status), output.Health; status == eks.AddonStatusCreateFailed && health != nil { + var errs *multierror.Error + + for _, issue := range health.Issues { + errs = multierror.Append(errs, fmt.Errorf("%s: %s", aws.StringValue(issue.Code), aws.StringValue(issue.Message))) + } + tfresource.SetLastError(err, errs.ErrorOrNil()) + } + + return output, err + } + + return nil, err +} + +func AddonDeleted(ctx context.Context, conn *eks.EKS, clusterName, addonName string) (*eks.Addon, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{eks.AddonStatusActive, eks.AddonStatusDeleting}, + Target: []string{}, + Refresh: AddonStatus(ctx, conn, clusterName, addonName), + Timeout: AddonDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*eks.Addon); ok { + return output, err + } + + return nil, err +} + +func AddonUpdateSuccessful(ctx context.Context, conn *eks.EKS, clusterName, addonName, id string) (*eks.Update, error) { + stateConf := resource.StateChangeConf{ + Pending: []string{eks.UpdateStatusInProgress}, + Target: []string{eks.UpdateStatusSuccessful}, + Refresh: AddonUpdateStatus(ctx, conn, clusterName, addonName, id), + Timeout: AddonUpdatedTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*eks.Update); ok { + if status := aws.StringValue(output.Status); status == eks.UpdateStatusCancelled || status == eks.UpdateStatusFailed { + var errs *multierror.Error + + for _, e := range output.Errors { + errs = multierror.Append(errs, fmt.Errorf("%s: %s", aws.StringValue(e.ErrorCode), aws.StringValue(e.ErrorMessage))) + } + tfresource.SetLastError(err, errs.ErrorOrNil()) + } + + return output, err + } + + return nil, err +} + func ClusterCreated(conn *eks.EKS, name string, timeout time.Duration) (*eks.Cluster, error) { stateConf := &resource.StateChangeConf{ Pending: []string{eks.ClusterStatusCreating}, @@ -173,100 +240,3 @@ func NodegroupUpdateSuccessful(conn *eks.EKS, clusterName, nodeGroupName, id str return nil, err } - -// EksAddonCreated waits for a EKS add-on to return status "ACTIVE" or "CREATE_FAILED" -func EksAddonCreated(ctx context.Context, conn *eks.EKS, clusterName, addonName string) (*eks.Addon, error) { - stateConf := resource.StateChangeConf{ - Pending: []string{eks.AddonStatusCreating}, - Target: []string{ - eks.AddonStatusActive, - eks.AddonStatusCreateFailed, - }, - Refresh: EksAddonStatus(ctx, conn, addonName, clusterName), - Timeout: EksAddonCreatedTimeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if addon, ok := outputRaw.(*eks.Addon); ok { - // If "CREATE_FAILED" status was returned, gather add-on health issues and return error - if aws.StringValue(addon.Status) == eks.AddonStatusCreateFailed { - var detailedErrors []string - for i, addonIssue := range addon.Health.Issues { - detailedErrors = append(detailedErrors, fmt.Sprintf("Error %d: Code: %s / Message: %s", - i+1, aws.StringValue(addonIssue.Code), aws.StringValue(addonIssue.Message))) - } - - return addon, fmt.Errorf("creation not successful (%s): Errors:\n%s", - aws.StringValue(addon.Status), strings.Join(detailedErrors, "\n")) - } - - return addon, err - } - - return nil, err -} - -// EksAddonDeleted waits for a EKS add-on to be deleted -func EksAddonDeleted(ctx context.Context, conn *eks.EKS, clusterName, addonName string) (*eks.Addon, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{ - eks.AddonStatusActive, - eks.AddonStatusDeleting, - }, - Target: []string{}, - Refresh: EksAddonStatus(ctx, conn, addonName, clusterName), - Timeout: EksAddonDeletedTimeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if err != nil { - // EKS API returns the ResourceNotFound error in this form: - // ResourceNotFoundException: No addon: vpc-cni found in cluster: tf-acc-test-533189557170672934 - if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { - return nil, nil - } - } - if v, ok := outputRaw.(*eks.Addon); ok { - return v, err - } - - return nil, err -} - -// EksAddonUpdateSuccessful waits for a EKS add-on update to return "Successful" -func EksAddonUpdateSuccessful(ctx context.Context, conn *eks.EKS, clusterName, addonName, updateID string) (*eks.Update, error) { - stateConf := resource.StateChangeConf{ - Pending: []string{eks.UpdateStatusInProgress}, - Target: []string{ - eks.UpdateStatusCancelled, - eks.UpdateStatusFailed, - eks.UpdateStatusSuccessful, - }, - Refresh: EksAddonUpdateStatus(ctx, conn, clusterName, addonName, updateID), - Timeout: EksAddonUpdatedTimeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if err != nil { - return nil, err - } - - update, ok := outputRaw.(*eks.Update) - if !ok { - return nil, err - } - - if aws.StringValue(update.Status) == eks.UpdateStatusSuccessful { - return nil, nil - } - - var detailedErrors []string - for i, updateError := range update.Errors { - detailedErrors = append(detailedErrors, fmt.Sprintf("Error %d: Code: %s / Message: %s", - i+1, aws.StringValue(updateError.ErrorCode), aws.StringValue(updateError.ErrorMessage))) - } - - return update, fmt.Errorf("EKS add-on (%s:%s) update (%s) not successful (%s): Errors:\n%s", - clusterName, addonName, updateID, aws.StringValue(update.Status), strings.Join(detailedErrors, "\n")) -} diff --git a/aws/resource_aws_eks_addon.go b/aws/resource_aws_eks_addon.go index cae762dc9a3..fb9b489ab8b 100644 --- a/aws/resource_aws_eks_addon.go +++ b/aws/resource_aws_eks_addon.go @@ -5,17 +5,21 @@ import ( "fmt" "log" "regexp" - "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/waiter" + iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func resourceAwsEksAddon() *schema.Resource { @@ -38,16 +42,6 @@ func resourceAwsEksAddon() *schema.Resource { ForceNew: true, ValidateFunc: validation.NoZeroValues, }, - "cluster_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validateEKSClusterName, - }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, "addon_version": { Type: schema.TypeString, Optional: true, @@ -57,15 +51,15 @@ func resourceAwsEksAddon() *schema.Resource { validation.StringMatch(regexp.MustCompile(`^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`), "must follow semantic version format"), ), }, - "service_account_role_arn": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validateArn, + "arn": { + Type: schema.TypeString, + Computed: true, }, - "resolve_conflicts": { + "cluster_name": { Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(eks.ResolveConflicts_Values(), false), + Required: true, + ForceNew: true, + ValidateFunc: validateEKSClusterName, }, "created_at": { Type: schema.TypeString, @@ -75,6 +69,16 @@ func resourceAwsEksAddon() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "resolve_conflicts": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(eks.ResolveConflicts_Values(), false), + }, + "service_account_role_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), }, @@ -86,23 +90,24 @@ func resourceAwsEksAddonCreate(ctx context.Context, d *schema.ResourceData, meta defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) - clusterName := d.Get("cluster_name").(string) addonName := d.Get("addon_name").(string) + clusterName := d.Get("cluster_name").(string) + id := tfeks.AddonCreateResourceID(clusterName, addonName) input := &eks.CreateAddonInput{ - ClusterName: aws.String(clusterName), AddonName: aws.String(addonName), ClientRequestToken: aws.String(resource.UniqueId()), - } - - if v, ok := d.GetOk("resolve_conflicts"); ok { - input.ResolveConflicts = aws.String(v.(string)) + ClusterName: aws.String(clusterName), } if v, ok := d.GetOk("addon_version"); ok { input.AddonVersion = aws.String(v.(string)) } + if v, ok := d.GetOk("resolve_conflicts"); ok { + input.ResolveConflicts = aws.String(v.(string)) + } + if v, ok := d.GetOk("service_account_role_arn"); ok { input.ServiceAccountRoleArn = aws.String(v.(string)) } @@ -111,29 +116,36 @@ func resourceAwsEksAddonCreate(ctx context.Context, d *schema.ResourceData, meta input.Tags = tags.IgnoreAws().EksTags() } - err := resource.RetryContext(ctx, 1*time.Minute, func() *resource.RetryError { + err := resource.RetryContext(ctx, iamwaiter.PropagationTimeout, func() *resource.RetryError { _, err := conn.CreateAddonWithContext(ctx, input) + + if tfawserr.ErrMessageContains(err, eks.ErrCodeInvalidParameterException, "CREATE_FAILED") { + return resource.RetryableError(err) + } + + if tfawserr.ErrMessageContains(err, eks.ErrCodeInvalidParameterException, "does not exist") { + return resource.RetryableError(err) + } + if err != nil { - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "CREATE_FAILED") { - return resource.RetryableError(err) - } - if isAWSErr(err, eks.ErrCodeInvalidParameterException, "does not exist") { - return resource.RetryableError(err) - } return resource.NonRetryableError(err) } + return nil }) - if isResourceTimeoutError(err) { + + if tfresource.TimedOut(err) { _, err = conn.CreateAddonWithContext(ctx, input) } + if err != nil { - return diag.FromErr(fmt.Errorf("error creating EKS add-on (%s): %w", addonName, err)) + return diag.FromErr(fmt.Errorf("error creating EKS Add-On (%s): %w", id, err)) } - d.SetId(fmt.Sprintf("%s:%s", clusterName, addonName)) + d.SetId(id) + + _, err = waiter.AddonCreated(ctx, conn, clusterName, addonName) - _, err = waiter.EksAddonCreated(ctx, conn, clusterName, addonName) if err != nil { // Creating addon w/o setting resolve_conflicts to "OVERWRITE" // might result in a failed creation, if unmanaged version of addon is already deployed @@ -144,7 +156,7 @@ func resourceAwsEksAddonCreate(ctx context.Context, d *schema.ResourceData, meta // Re-creating like this will resolve the error, but it will also purge any // configurations that were applied by the user (that were conflicting). This might we an unwanted // side effect and should be left for the user to decide how to handle it. - return diag.FromErr(fmt.Errorf("unexpected EKS add-on (%s) state returned during creation: %w\n[WARNING] Running terraform apply again will remove the kubernetes add-on and attempt to create it again effectively purging previous add-on configuration", + return diag.FromErr(fmt.Errorf("unexpected EKS Add-On (%s) state returned during creation: %w\n[WARNING] Running terraform apply again will remove the kubernetes add-on and attempt to create it again effectively purging previous add-on configuration", d.Id(), err)) } @@ -156,41 +168,31 @@ func resourceAwsEksAddonRead(ctx context.Context, d *schema.ResourceData, meta i defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - clusterName, addonName, err := resourceAwsEksAddonParseId(d.Id()) + clusterName, addonName, err := tfeks.AddonParseResourceID(d.Id()) + if err != nil { return diag.FromErr(err) } - input := &eks.DescribeAddonInput{ - ClusterName: aws.String(clusterName), - AddonName: aws.String(addonName), - } - - log.Printf("[DEBUG] Reading EKS add-on: %s", d.Id()) - output, err := conn.DescribeAddonWithContext(ctx, input) - if err != nil { - if isAWSErr(err, eks.ErrCodeResourceNotFoundException, "") { - log.Printf("[WARN] EKS add-on (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - return diag.FromErr(fmt.Errorf("error reading EKS add-on (%s): %w", d.Id(), err)) - } + addon, err := finder.AddonByClusterNameAndAddonName(ctx, conn, clusterName, addonName) - addon := output.Addon - if addon == nil { - log.Printf("[WARN] EKS add-on (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EKS Add-On (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - d.Set("cluster_name", addon.ClusterName) + if err != nil { + return diag.FromErr(fmt.Errorf("error reading EKS Add-On (%s): %w", d.Id(), err)) + } + d.Set("addon_name", addon.AddonName) - d.Set("arn", addon.AddonArn) d.Set("addon_version", addon.AddonVersion) - d.Set("service_account_role_arn", addon.ServiceAccountRoleArn) + d.Set("arn", addon.AddonArn) + d.Set("cluster_name", addon.ClusterName) d.Set("created_at", aws.TimeValue(addon.CreatedAt).Format(time.RFC3339)) d.Set("modified_at", aws.TimeValue(addon.ModifiedAt).Format(time.RFC3339)) + d.Set("service_account_role_arn", addon.ServiceAccountRoleArn) tags := keyvaluetags.EksKeyValueTags(addon.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) @@ -209,66 +211,60 @@ func resourceAwsEksAddonRead(ctx context.Context, d *schema.ResourceData, meta i func resourceAwsEksAddonUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).eksconn - clusterName, addonName, err := resourceAwsEksAddonParseId(d.Id()) + clusterName, addonName, err := tfeks.AddonParseResourceID(d.Id()) + if err != nil { return diag.FromErr(err) } - input := &eks.UpdateAddonInput{ - ClusterName: aws.String(clusterName), - AddonName: aws.String(addonName), - ClientRequestToken: aws.String(resource.UniqueId()), - } - - if d.HasChange("resolve_conflicts") { - if v, ok := d.GetOk("resolve_conflicts"); ok { - d.Set("resolve_conflicts", aws.String(v.(string))) + if d.HasChanges("addon_version", "service_account_role_arn") { + input := &eks.UpdateAddonInput{ + AddonName: aws.String(addonName), + ClientRequestToken: aws.String(resource.UniqueId()), + ClusterName: aws.String(clusterName), } - } - if v, ok := d.GetOk("resolve_conflicts"); ok { - input.ResolveConflicts = aws.String(v.(string)) - } - - if d.HasChange("tags_all") { - o, n := d.GetChange("tags_all") - if err := keyvaluetags.EksUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return diag.FromErr(fmt.Errorf("error updating tags: %w", err)) + if d.HasChange("addon_version") { + input.AddonVersion = aws.String(d.Get("addon_version").(string)) } - } - if d.HasChange("addon_version") { - input.AddonVersion = aws.String(d.Get("addon_version").(string)) - } + if v, ok := d.GetOk("resolve_conflicts"); ok { + input.ResolveConflicts = aws.String(v.(string)) + } - // If service account role ARN is already provided, use it. Otherwise, the add-on uses - // permissions assigned to the node IAM role. - if d.HasChange("service_account_role_arn") || d.Get("service_account_role_arn").(string) != "" { - input.ServiceAccountRoleArn = aws.String(d.Get("service_account_role_arn").(string)) - } + // If service account role ARN is already provided, use it. Otherwise, the add-on uses + // permissions assigned to the node IAM role. + if d.HasChange("service_account_role_arn") || d.Get("service_account_role_arn").(string) != "" { + input.ServiceAccountRoleArn = aws.String(d.Get("service_account_role_arn").(string)) + } - if d.HasChanges("addon_version", "service_account_role_arn") { output, err := conn.UpdateAddonWithContext(ctx, input) - if err != nil { - return diag.FromErr(fmt.Errorf("error updating EKS add-on (%s) version: %w", d.Id(), err)) - } - if output == nil || output.Update == nil || output.Update.Id == nil { - return diag.FromErr(fmt.Errorf("error determining EKS add-on (%s) version update ID: empty response", d.Id())) + if err != nil { + return diag.FromErr(fmt.Errorf("error updating EKS Add-On (%s): %w", d.Id(), err)) } updateID := aws.StringValue(output.Update.Id) - _, err = waiter.EksAddonUpdateSuccessful(ctx, conn, clusterName, addonName, updateID) + _, err = waiter.AddonUpdateSuccessful(ctx, conn, clusterName, addonName, updateID) + if err != nil { if d.Get("resolve_conflicts") != eks.ResolveConflictsOverwrite { // Changing addon version w/o setting resolve_conflicts to "OVERWRITE" // might result in a failed update if there are conflicts: // ConfigurationConflict Apply failed with 1 conflict: conflict with "kubectl"... - return diag.FromErr(fmt.Errorf("error waiting for EKS add-on (%s) update (%s): %w, consider setting attribute %q to %q", + return diag.FromErr(fmt.Errorf("error waiting for EKS Add-On (%s) update (%s): %w, consider setting attribute %q to %q", d.Id(), updateID, err, "resolve_conflicts", eks.ResolveConflictsOverwrite)) } - return diag.FromErr(fmt.Errorf("error waiting for EKS add-on (%s) update (%s): %w", d.Id(), updateID, err)) + + return diag.FromErr(fmt.Errorf("error waiting for EKS Add-On (%s) update (%s): %w", d.Id(), updateID, err)) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := keyvaluetags.EksUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.FromErr(fmt.Errorf("error updating tags: %w", err)) } } @@ -278,34 +274,27 @@ func resourceAwsEksAddonUpdate(ctx context.Context, d *schema.ResourceData, meta func resourceAwsEksAddonDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).eksconn - clusterName, addonName, err := resourceAwsEksAddonParseId(d.Id()) + clusterName, addonName, err := tfeks.AddonParseResourceID(d.Id()) + if err != nil { return diag.FromErr(err) } - input := &eks.DeleteAddonInput{ - ClusterName: aws.String(clusterName), + log.Printf("[DEBUG] Deleting EKS Add-On: %s", d.Id()) + _, err = conn.DeleteAddonWithContext(ctx, &eks.DeleteAddonInput{ AddonName: aws.String(addonName), - } + ClusterName: aws.String(clusterName), + }) - _, err = conn.DeleteAddonWithContext(ctx, input) if err != nil { - return diag.FromErr(fmt.Errorf("error deleting EKS add-on (%s): %w", d.Id(), err)) + return diag.FromErr(fmt.Errorf("error deleting EKS Add-On (%s): %w", d.Id(), err)) } - _, err = waiter.EksAddonDeleted(ctx, conn, clusterName, addonName) + _, err = waiter.AddonDeleted(ctx, conn, clusterName, addonName) + if err != nil { - return diag.FromErr(fmt.Errorf("error waiting for EKS add-on (%s) deletion: %w", d.Id(), err)) + return diag.FromErr(fmt.Errorf("error waiting for EKS Add-On (%s) to delete: %w", d.Id(), err)) } return nil } - -func resourceAwsEksAddonParseId(id string) (string, string, error) { - parts := strings.Split(id, ":") - if len(parts) != 2 { - return "", "", fmt.Errorf("unexpected format of ID (%s), expected cluster-name:addon-name", id) - } - - return parts[0], parts[1], nil -} diff --git a/aws/resource_aws_eks_addon_test.go b/aws/resource_aws_eks_addon_test.go index f79c68c52c7..e436d0eae6d 100644 --- a/aws/resource_aws_eks_addon_test.go +++ b/aws/resource_aws_eks_addon_test.go @@ -6,18 +6,18 @@ import ( "log" "regexp" "testing" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/waiter" + tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) func init() { @@ -32,53 +32,59 @@ func testSweepEksAddon(region string) error { if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.(*AWSClient).eksconn ctx := context.TODO() + conn := client.(*AWSClient).eksconn + input := &eks.ListClustersInput{} var sweeperErrs *multierror.Error + sweepResources := make([]*testSweepResource, 0) - input := &eks.ListClustersInput{MaxResults: aws.Int64(100)} err = conn.ListClustersPagesWithContext(ctx, input, func(page *eks.ListClustersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + for _, cluster := range page.Clusters { - clusterName := aws.StringValue(cluster) input := &eks.ListAddonsInput{ - ClusterName: aws.String(clusterName), + ClusterName: cluster, } + err := conn.ListAddonsPagesWithContext(ctx, input, func(page *eks.ListAddonsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + for _, addon := range page.Addons { - addonName := aws.StringValue(addon) - log.Printf("[INFO] Deleting EKS Addon %s from Cluster %s", addonName, clusterName) - input := &eks.DeleteAddonInput{ - AddonName: aws.String(addonName), - ClusterName: aws.String(clusterName), - } - - _, err := conn.DeleteAddonWithContext(ctx, input) - - if err != nil && !tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error deleting EKS Addon %s from Cluster %s: %w", addonName, clusterName, err)) - continue - } - - if _, err := waiter.EksAddonDeleted(ctx, conn, clusterName, addonName); err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error waiting for EKS Addon %s deletion: %w", addonName, err)) - continue - } + r := resourceAwsEksAddon() + d := r.Data(nil) + d.SetId(tfeks.AddonCreateResourceID(aws.StringValue(cluster), aws.StringValue(addon))) + + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) } - return true + + return !lastPage }) + if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Addons for Cluster %s: %w", clusterName, err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Add-Ons (%s): %w", region, err)) } } - return true + return !lastPage }) + if testSweepSkipSweepError(err) { - log.Print(fmt.Errorf("[WARN] Skipping EKS Addon sweep for %s: %w", region, err)) + log.Print(fmt.Errorf("[WARN] Skipping EKS Add-Ons sweep for %s: %w", region, err)) return sweeperErrs // In case we have completed some pages, but had errors } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing EKS Clusters (%s): %w", region, err)) + } + + err = testSweepResourceOrchestrator(sweepResources) + if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving EKS Clusters: %w", err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping EKS Add-Ons (%s): %w", region, err)) } return sweeperErrs.ErrorOrNil() @@ -717,40 +723,28 @@ func testAccCheckAWSEksAddonExists(ctx context.Context, resourceName string, add return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] if !ok { - return fmt.Errorf("not found: %s", resourceName) + return fmt.Errorf("Not found: %s", resourceName) } if rs.Primary.ID == "" { - return fmt.Errorf("no EKS Addon ID is set") + return fmt.Errorf("no EKS Add-On ID is set") } - clusterName, addonName, err := resourceAwsEksAddonParseId(rs.Primary.ID) - if err != nil { - return err - } + clusterName, addonName, err := tfeks.AddonParseResourceID(rs.Primary.ID) - conn := testAccProvider.Meta().(*AWSClient).eksconn - output, err := conn.DescribeAddonWithContext(ctx, &eks.DescribeAddonInput{ - ClusterName: aws.String(clusterName), - AddonName: aws.String(addonName), - }) if err != nil { return err } - if output == nil || output.Addon == nil { - return fmt.Errorf("EKS Addon (%s) not found", rs.Primary.ID) - } + conn := testAccProvider.Meta().(*AWSClient).eksconn - if aws.StringValue(output.Addon.AddonName) != addonName { - return fmt.Errorf("EKS Addon (%s) not found", rs.Primary.ID) - } + output, err := finder.AddonByClusterNameAndAddonName(ctx, conn, clusterName, addonName) - if aws.StringValue(output.Addon.ClusterName) != clusterName { - return fmt.Errorf("EKS Addon (%s) not found", rs.Primary.ID) + if err != nil { + return err } - *addon = *output.Addon + *addon = *output return nil } @@ -758,40 +752,30 @@ func testAccCheckAWSEksAddonExists(ctx context.Context, resourceName string, add func testAccCheckAWSEksAddonDestroy(s *terraform.State) error { ctx := context.TODO() + conn := testAccProvider.Meta().(*AWSClient).eksconn + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_eks_addon" { continue } - clusterName, addonName, err := resourceAwsEksAddonParseId(rs.Primary.ID) + clusterName, addonName, err := tfeks.AddonParseResourceID(rs.Primary.ID) + if err != nil { return err } - conn := testAccProvider.Meta().(*AWSClient).eksconn - - // Handle eventual consistency - err = resource.RetryContext(ctx, 1*time.Minute, func() *resource.RetryError { - output, err := conn.DescribeAddonWithContext(ctx, &eks.DescribeAddonInput{ - AddonName: aws.String(addonName), - ClusterName: aws.String(clusterName), - }) - - if err != nil { - if tfawserr.ErrCodeEquals(err, eks.ErrCodeResourceNotFoundException) { - return nil - } - return resource.NonRetryableError(err) - } + _, err = finder.AddonByClusterNameAndAddonName(ctx, conn, clusterName, addonName) - if output != nil && output.Addon != nil && aws.StringValue(output.Addon.AddonName) == addonName { - return resource.RetryableError(fmt.Errorf("EKS Addon (%s) still exists", rs.Primary.ID)) - } + if tfresource.NotFound(err) { + continue + } - return nil - }) + if err != nil { + return err + } - return err + return fmt.Errorf("EKS Node Group %s still exists", rs.Primary.ID) } return nil From 5feb7af24c949c2e13fa14d84e9e11b4780774e1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 21 Jun 2021 10:01:35 -0400 Subject: [PATCH 18/20] d/aws_eks_addon: Use internal finder package. --- aws/data_source_aws_eks_addon.go | 45 ++++++++++++++------------------ 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/aws/data_source_aws_eks_addon.go b/aws/data_source_aws_eks_addon.go index 28e5b7b8112..676ee8c89e5 100644 --- a/aws/data_source_aws_eks_addon.go +++ b/aws/data_source_aws_eks_addon.go @@ -6,11 +6,12 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/eks" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + tfeks "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" ) func dataSourceAwsEksAddon() *schema.Resource { @@ -22,23 +23,19 @@ func dataSourceAwsEksAddon() *schema.Resource { Required: true, ValidateFunc: validation.NoZeroValues, }, - "cluster_name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validateEKSClusterName, - }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, "addon_version": { Type: schema.TypeString, Computed: true, }, - "service_account_role_arn": { + "arn": { Type: schema.TypeString, Computed: true, }, + "cluster_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateEKSClusterName, + }, "created_at": { Type: schema.TypeString, Computed: true, @@ -47,6 +44,10 @@ func dataSourceAwsEksAddon() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "service_account_role_arn": { + Type: schema.TypeString, + Computed: true, + }, "tags": tagsSchemaComputed(), }, } @@ -58,31 +59,23 @@ func dataSourceAwsEksAddonRead(ctx context.Context, d *schema.ResourceData, meta addonName := d.Get("addon_name").(string) clusterName := d.Get("cluster_name").(string) + id := tfeks.AddonCreateResourceID(clusterName, addonName) - input := &eks.DescribeAddonInput{ - AddonName: aws.String(addonName), - ClusterName: aws.String(clusterName), - } + addon, err := finder.AddonByClusterNameAndAddonName(ctx, conn, clusterName, addonName) - output, err := conn.DescribeAddonWithContext(ctx, input) if err != nil { - return diag.FromErr(fmt.Errorf("error reading EKS Addon (%s): %w", addonName, err)) + return diag.FromErr(fmt.Errorf("error reading EKS Add-On (%s): %w", id, err)) } - addon := output.Addon - if addon == nil { - return diag.FromErr(fmt.Errorf("EKS Addon (%s) not found", addonName)) - } - - d.SetId(fmt.Sprintf("%s:%s", clusterName, addonName)) - d.Set("arn", addon.AddonArn) + d.SetId(id) d.Set("addon_version", addon.AddonVersion) - d.Set("service_account_role_arn", addon.ServiceAccountRoleArn) + d.Set("arn", addon.AddonArn) d.Set("created_at", aws.TimeValue(addon.CreatedAt).Format(time.RFC3339)) d.Set("modified_at", aws.TimeValue(addon.ModifiedAt).Format(time.RFC3339)) + d.Set("service_account_role_arn", addon.ServiceAccountRoleArn) if err := d.Set("tags", keyvaluetags.EksKeyValueTags(addon.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return diag.FromErr(fmt.Errorf("error setting tags attribute: %w", err)) + return diag.FromErr(fmt.Errorf("error setting tags: %w", err)) } return nil From d0c797c64401e29a69907e42d3246d61b6e39961 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 21 Jun 2021 10:08:52 -0400 Subject: [PATCH 19/20] d/aws_eks_cluster: Use internal finder package. --- aws/data_source_aws_eks_cluster.go | 43 ++++++++++++------------------ 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/aws/data_source_aws_eks_cluster.go b/aws/data_source_aws_eks_cluster.go index f933d2fee3b..6fc5f387762 100644 --- a/aws/data_source_aws_eks_cluster.go +++ b/aws/data_source_aws_eks_cluster.go @@ -2,12 +2,11 @@ package aws import ( "fmt" - "log" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/eks" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/eks/finder" ) func dataSourceAwsEksCluster() *schema.Resource { @@ -95,6 +94,10 @@ func dataSourceAwsEksCluster() *schema.Resource { Computed: true, }, "tags": tagsSchemaComputed(), + "version": { + Type: schema.TypeString, + Computed: true, + }, "vpc_config": { Type: schema.TypeList, Computed: true, @@ -112,17 +115,17 @@ func dataSourceAwsEksCluster() *schema.Resource { Type: schema.TypeBool, Computed: true, }, - "security_group_ids": { + "public_access_cidrs": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "subnet_ids": { + "security_group_ids": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "public_access_cidrs": { + "subnet_ids": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, @@ -134,10 +137,6 @@ func dataSourceAwsEksCluster() *schema.Resource { }, }, }, - "version": { - Type: schema.TypeString, - Computed: true, - }, }, } } @@ -147,22 +146,12 @@ func dataSourceAwsEksClusterRead(d *schema.ResourceData, meta interface{}) error ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig name := d.Get("name").(string) + cluster, err := finder.ClusterByName(conn, name) - input := &eks.DescribeClusterInput{ - Name: aws.String(name), - } - - log.Printf("[DEBUG] Reading EKS Cluster: %s", input) - output, err := conn.DescribeCluster(input) if err != nil { return fmt.Errorf("error reading EKS Cluster (%s): %w", name, err) } - cluster := output.Cluster - if cluster == nil { - return fmt.Errorf("EKS Cluster (%s) not found", name) - } - d.SetId(name) d.Set("arn", cluster.Arn) @@ -171,32 +160,34 @@ func dataSourceAwsEksClusterRead(d *schema.ResourceData, meta interface{}) error } d.Set("created_at", aws.TimeValue(cluster.CreatedAt).String()) + if err := d.Set("enabled_cluster_log_types", flattenEksEnabledLogTypes(cluster.Logging)); err != nil { return fmt.Errorf("error setting enabled_cluster_log_types: %w", err) } + d.Set("endpoint", cluster.Endpoint) if err := d.Set("identity", flattenEksIdentity(cluster.Identity)); err != nil { return fmt.Errorf("error setting identity: %w", err) } + if err := d.Set("kubernetes_network_config", flattenEksNetworkConfig(cluster.KubernetesNetworkConfig)); err != nil { + return fmt.Errorf("error setting kubernetes_network_config: %w", err) + } + d.Set("name", cluster.Name) d.Set("platform_version", cluster.PlatformVersion) d.Set("role_arn", cluster.RoleArn) d.Set("status", cluster.Status) - if err := d.Set("tags", keyvaluetags.EksKeyValueTags(cluster.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) - } - d.Set("version", cluster.Version) if err := d.Set("vpc_config", flattenEksVpcConfigResponse(cluster.ResourcesVpcConfig)); err != nil { return fmt.Errorf("error setting vpc_config: %w", err) } - if err := d.Set("kubernetes_network_config", flattenEksNetworkConfig(cluster.KubernetesNetworkConfig)); err != nil { - return fmt.Errorf("error setting kubernetes_network_config: %w", err) + if err := d.Set("tags", keyvaluetags.EksKeyValueTags(cluster.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) } return nil From 3bebba3b81245f53e723856cbbf1423a354c658f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 22 Jun 2021 09:14:43 -0400 Subject: [PATCH 20/20] Minor fixes after running all acceptance tests. --- aws/data_source_aws_eks_cluster_test.go | 9 +++------ aws/resource_aws_eks_addon_test.go | 2 +- aws/resource_aws_eks_cluster.go | 1 + 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/aws/data_source_aws_eks_cluster_test.go b/aws/data_source_aws_eks_cluster_test.go index e83cb6bb612..94a0e56dad3 100644 --- a/aws/data_source_aws_eks_cluster_test.go +++ b/aws/data_source_aws_eks_cluster_test.go @@ -1,7 +1,6 @@ package aws import ( - "fmt" "regexp" "testing" @@ -11,7 +10,7 @@ import ( ) func TestAccAWSEksClusterDataSource_basic(t *testing.T) { - rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(5)) + rName := acctest.RandomWithPrefix("tf-acc-test") dataSourceResourceName := "data.aws_eks_cluster.test" resourceName := "aws_eks_cluster.test" @@ -57,11 +56,9 @@ func TestAccAWSEksClusterDataSource_basic(t *testing.T) { } func testAccAWSEksClusterDataSourceConfig_Basic(rName string) string { - return fmt.Sprintf(` -%[1]s - + return composeConfig(testAccAWSEksClusterConfig_Logging(rName, []string{"api", "audit"}), ` data "aws_eks_cluster" "test" { name = aws_eks_cluster.test.name } -`, testAccAWSEksClusterConfig_Logging(rName, []string{"api", "audit"})) +`) } diff --git a/aws/resource_aws_eks_addon_test.go b/aws/resource_aws_eks_addon_test.go index e436d0eae6d..29b9cfb91ab 100644 --- a/aws/resource_aws_eks_addon_test.go +++ b/aws/resource_aws_eks_addon_test.go @@ -153,7 +153,7 @@ func TestAccAWSEksAddon_disappears_Cluster(t *testing.T) { var addon eks.Addon rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_eks_addon.test" - clusterResourceName := "aws_eks_cluster" + clusterResourceName := "aws_eks_cluster.test" addonName := "vpc-cni" ctx := context.TODO() diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index 83f552a55ae..57737fdbb75 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -74,6 +74,7 @@ func resourceAwsEksCluster() *schema.Resource { Type: schema.TypeString, ValidateFunc: validation.StringInSlice(eks.LogType_Values(), true), }, + Set: schema.HashString, }, "encryption_config": { Type: schema.TypeList,