diff --git a/aws/data_source_aws_eks_cluster.go b/aws/data_source_aws_eks_cluster.go index 32a0688d5de..0ba05b81bbf 100644 --- a/aws/data_source_aws_eks_cluster.go +++ b/aws/data_source_aws_eks_cluster.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/service/eks" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) func dataSourceAwsEksCluster() *schema.Resource { @@ -84,6 +85,7 @@ func dataSourceAwsEksCluster() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tags": tagsSchemaComputed(), "vpc_config": { Type: schema.TypeList, MaxItems: 1, @@ -163,6 +165,11 @@ func dataSourceAwsEksClusterRead(d *schema.ResourceData, meta interface{}) error 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().Map()); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + d.Set("version", cluster.Version) if err := d.Set("vpc_config", flattenEksVpcConfigResponse(cluster.ResourcesVpcConfig)); err != nil { diff --git a/aws/data_source_aws_eks_cluster_test.go b/aws/data_source_aws_eks_cluster_test.go index eae49c5a7d3..3ccc769a272 100644 --- a/aws/data_source_aws_eks_cluster_test.go +++ b/aws/data_source_aws_eks_cluster_test.go @@ -36,6 +36,7 @@ func TestAccAWSEksClusterDataSource_basic(t *testing.T) { resource.TestMatchResourceAttr(dataSourceResourceName, "platform_version", regexp.MustCompile(`^eks\.\d+$`)), resource.TestCheckResourceAttrPair(resourceName, "role_arn", dataSourceResourceName, "role_arn"), resource.TestCheckResourceAttrPair(resourceName, "status", dataSourceResourceName, "status"), + resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceResourceName, "tags.%"), resource.TestCheckResourceAttrPair(resourceName, "version", dataSourceResourceName, "version"), resource.TestCheckResourceAttr(dataSourceResourceName, "vpc_config.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "vpc_config.0.endpoint_private_access", dataSourceResourceName, "vpc_config.0.endpoint_private_access"), diff --git a/aws/internal/keyvaluetags/generators/listtags/main.go b/aws/internal/keyvaluetags/generators/listtags/main.go index 778d4815df9..813fbc19194 100644 --- a/aws/internal/keyvaluetags/generators/listtags/main.go +++ b/aws/internal/keyvaluetags/generators/listtags/main.go @@ -43,6 +43,7 @@ var serviceNames = []string{ "ecr", "ecs", "efs", + "eks", "elasticache", "elasticbeanstalk", "elasticsearchservice", diff --git a/aws/internal/keyvaluetags/generators/servicetags/main.go b/aws/internal/keyvaluetags/generators/servicetags/main.go index ad81f7a4b05..327e567bbc9 100644 --- a/aws/internal/keyvaluetags/generators/servicetags/main.go +++ b/aws/internal/keyvaluetags/generators/servicetags/main.go @@ -100,6 +100,7 @@ var mapServiceNames = []string{ "codecommit", "cognitoidentity", "cognitoidentityprovider", + "eks", "glacier", "glue", "guardduty", diff --git a/aws/internal/keyvaluetags/generators/updatetags/main.go b/aws/internal/keyvaluetags/generators/updatetags/main.go index 8ff741e619e..11fc44a7877 100644 --- a/aws/internal/keyvaluetags/generators/updatetags/main.go +++ b/aws/internal/keyvaluetags/generators/updatetags/main.go @@ -46,6 +46,7 @@ var serviceNames = []string{ "ecr", "ecs", "efs", + "eks", "elasticache", "elasticsearchservice", "emr", diff --git a/aws/internal/keyvaluetags/list_tags_gen.go b/aws/internal/keyvaluetags/list_tags_gen.go index 43ba5c20c60..426e545275b 100644 --- a/aws/internal/keyvaluetags/list_tags_gen.go +++ b/aws/internal/keyvaluetags/list_tags_gen.go @@ -30,6 +30,7 @@ import ( "github.com/aws/aws-sdk-go/service/ecr" "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/service/efs" + "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/elasticbeanstalk" "github.com/aws/aws-sdk-go/service/elasticsearchservice" @@ -510,6 +511,23 @@ func EfsListTags(conn *efs.EFS, identifier string) (KeyValueTags, error) { return EfsKeyValueTags(output.Tags), nil } +// EksListTags lists eks service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func EksListTags(conn *eks.EKS, identifier string) (KeyValueTags, error) { + input := &eks.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(input) + + if err != nil { + return New(nil), err + } + + return EksKeyValueTags(output.Tags), nil +} + // ElasticacheListTags lists elasticache service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. diff --git a/aws/internal/keyvaluetags/service_generation_customizations.go b/aws/internal/keyvaluetags/service_generation_customizations.go index 8dee8812376..663cec63448 100644 --- a/aws/internal/keyvaluetags/service_generation_customizations.go +++ b/aws/internal/keyvaluetags/service_generation_customizations.go @@ -37,6 +37,7 @@ import ( "github.com/aws/aws-sdk-go/service/ecr" "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/service/efs" + "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/elasticbeanstalk" "github.com/aws/aws-sdk-go/service/elasticsearchservice" @@ -152,6 +153,8 @@ func ServiceClientType(serviceName string) string { funcType = reflect.TypeOf(ecs.New) case "efs": funcType = reflect.TypeOf(efs.New) + case "eks": + funcType = reflect.TypeOf(eks.New) case "elasticache": funcType = reflect.TypeOf(elasticache.New) case "elasticbeanstalk": diff --git a/aws/internal/keyvaluetags/service_tags_gen.go b/aws/internal/keyvaluetags/service_tags_gen.go index 1fc05de3356..180e5394aae 100644 --- a/aws/internal/keyvaluetags/service_tags_gen.go +++ b/aws/internal/keyvaluetags/service_tags_gen.go @@ -187,6 +187,16 @@ func CognitoidentityproviderKeyValueTags(tags map[string]*string) KeyValueTags { return New(tags) } +// EksTags returns eks service tags. +func (tags KeyValueTags) EksTags() map[string]*string { + return aws.StringMap(tags.Map()) +} + +// EksKeyValueTags creates KeyValueTags from eks service tags. +func EksKeyValueTags(tags map[string]*string) KeyValueTags { + return New(tags) +} + // GlacierTags returns glacier service tags. func (tags KeyValueTags) GlacierTags() map[string]*string { return aws.StringMap(tags.Map()) diff --git a/aws/internal/keyvaluetags/update_tags_gen.go b/aws/internal/keyvaluetags/update_tags_gen.go index 5bcc5a20d23..983534cd5ff 100644 --- a/aws/internal/keyvaluetags/update_tags_gen.go +++ b/aws/internal/keyvaluetags/update_tags_gen.go @@ -35,6 +35,7 @@ import ( "github.com/aws/aws-sdk-go/service/ecr" "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/service/efs" + "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/elasticsearchservice" "github.com/aws/aws-sdk-go/service/emr" @@ -1121,6 +1122,42 @@ func EfsUpdateTags(conn *efs.EFS, identifier string, oldTagsMap interface{}, new return nil } +// EksUpdateTags updates eks service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func EksUpdateTags(conn *eks.EKS, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error { + oldTags := New(oldTagsMap) + newTags := New(newTagsMap) + + if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + input := &eks.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: aws.StringSlice(removedTags.Keys()), + } + + _, err := conn.UntagResource(input) + + if err != nil { + return fmt.Errorf("error untagging resource (%s): %s", identifier, err) + } + } + + if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + input := &eks.TagResourceInput{ + ResourceArn: aws.String(identifier), + Tags: updatedTags.IgnoreAws().EksTags(), + } + + _, err := conn.TagResource(input) + + if err != nil { + return fmt.Errorf("error tagging resource (%s): %s", identifier, err) + } + } + + return nil +} + // ElasticacheUpdateTags updates elasticache service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. diff --git a/aws/resource_aws_eks_cluster.go b/aws/resource_aws_eks_cluster.go index 994249a64bc..2d819e0a213 100644 --- a/aws/resource_aws_eks_cluster.go +++ b/aws/resource_aws_eks_cluster.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) var eksLogTypes = []string{ @@ -104,6 +105,7 @@ func resourceAwsEksCluster() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tags": tagsSchema(), "version": { Type: schema.TypeString, Optional: true, @@ -170,6 +172,10 @@ func resourceAwsEksClusterCreate(d *schema.ResourceData, meta interface{}) error Logging: expandEksLoggingTypes(d.Get("enabled_cluster_log_types").(*schema.Set)), } + if v := d.Get("tags").(map[string]interface{}); len(v) > 0 { + input.Tags = keyvaluetags.New(v).IgnoreAws().EksTags() + } + if v, ok := d.GetOk("version"); ok && v.(string) != "" { input.Version = aws.String(v.(string)) } @@ -262,6 +268,11 @@ func resourceAwsEksClusterRead(d *schema.ResourceData, meta interface{}) error { 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().Map()); err != nil { + return fmt.Errorf("error setting tags: %s", 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) @@ -277,6 +288,13 @@ func resourceAwsEksClusterRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsEksClusterUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).eksconn + if d.HasChange("tags") { + o, n := d.GetChange("tags") + if err := keyvaluetags.EksUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating tags: %s", err) + } + } + if d.HasChange("version") { input := &eks.UpdateClusterVersionInput{ Name: aws.String(d.Id()), diff --git a/aws/resource_aws_eks_cluster_test.go b/aws/resource_aws_eks_cluster_test.go index 53dbd8b6dc0..273d14d9e4d 100644 --- a/aws/resource_aws_eks_cluster_test.go +++ b/aws/resource_aws_eks_cluster_test.go @@ -97,6 +97,7 @@ func TestAccAWSEksCluster_basic(t *testing.T) { resource.TestMatchResourceAttr(resourceName, "platform_version", regexp.MustCompile(`^eks\.\d+$`)), resource.TestMatchResourceAttr(resourceName, "role_arn", regexp.MustCompile(fmt.Sprintf("%s$", rName))), resource.TestCheckResourceAttr(resourceName, "status", eks.ClusterStatusActive), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestMatchResourceAttr(resourceName, "version", regexp.MustCompile(`^\d+\.\d+$`)), resource.TestCheckResourceAttr(resourceName, "vpc_config.#", "1"), resource.TestCheckResourceAttr(resourceName, "vpc_config.0.endpoint_private_access", "false"), @@ -197,6 +198,50 @@ func TestAccAWSEksCluster_Logging(t *testing.T) { }) } +func TestAccAWSEksCluster_Tags(t *testing.T) { + var cluster1, cluster2, cluster3 eks.Cluster + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_eks_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEks(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEksClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEksClusterConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEksClusterExists(resourceName, &cluster1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSEksClusterConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEksClusterExists(resourceName, &cluster2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSEksClusterConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEksClusterExists(resourceName, &cluster3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + func TestAccAWSEksCluster_VpcConfig_SecurityGroupIds(t *testing.T) { var cluster eks.Cluster @@ -520,6 +565,45 @@ resource "aws_eks_cluster" "test" { `, testAccAWSEksClusterConfig_Base(rName), rName, strings.Join(logTypes, "\", \"")) } +func testAccAWSEksClusterConfigTags1(rName, tagKey1, tagValue1 string) string { + return testAccAWSEksClusterConfig_Base(rName) + fmt.Sprintf(` +resource "aws_eks_cluster" "test" { + name = %[1]q + role_arn = "${aws_iam_role.test.arn}" + + tags = { + %[2]q = %[3]q + } + + vpc_config { + subnet_ids = ["${aws_subnet.test.*.id[0]}", "${aws_subnet.test.*.id[1]}"] + } + + depends_on = ["aws_iam_role_policy_attachment.test-AmazonEKSClusterPolicy", "aws_iam_role_policy_attachment.test-AmazonEKSServicePolicy"] +} +`, rName, tagKey1, tagValue1) +} + +func testAccAWSEksClusterConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return testAccAWSEksClusterConfig_Base(rName) + fmt.Sprintf(` +resource "aws_eks_cluster" "test" { + name = %[1]q + role_arn = "${aws_iam_role.test.arn}" + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } + + vpc_config { + subnet_ids = ["${aws_subnet.test.*.id[0]}", "${aws_subnet.test.*.id[1]}"] + } + + depends_on = ["aws_iam_role_policy_attachment.test-AmazonEKSClusterPolicy", "aws_iam_role_policy_attachment.test-AmazonEKSServicePolicy"] +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} + func testAccAWSEksClusterConfig_VpcConfig_SecurityGroupIds(rName string) string { return fmt.Sprintf(` %s diff --git a/website/docs/d/eks_cluster.html.markdown b/website/docs/d/eks_cluster.html.markdown index d593eefaa40..d4570b52850 100644 --- a/website/docs/d/eks_cluster.html.markdown +++ b/website/docs/d/eks_cluster.html.markdown @@ -49,6 +49,7 @@ output "identity-oidc-issuer" { * `platform_version` - The platform version for the cluster. * `role_arn` - The Amazon Resource Name (ARN) of the IAM role that provides permissions for the Kubernetes control plane to make calls to AWS API operations on your behalf. * `status` - The status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`. +* `tags` - Key-value mapping of resource tags. * `version` - The Kubernetes server version for the cluster. * `vpc_config` - Nested attribute containing VPC configuration for the cluster. * `endpoint_private_access` - Indicates whether or not the Amazon EKS private API server endpoint is enabled. diff --git a/website/docs/r/eks_cluster.html.markdown b/website/docs/r/eks_cluster.html.markdown index f266fa2fba7..5989461ba92 100644 --- a/website/docs/r/eks_cluster.html.markdown +++ b/website/docs/r/eks_cluster.html.markdown @@ -112,6 +112,7 @@ The following arguments are supported: * `role_arn` - (Required) The Amazon Resource Name (ARN) of the IAM role that provides permissions for the Kubernetes control plane to make calls to AWS API operations on your behalf. * `vpc_config` - (Required) Nested argument for the VPC associated with your cluster. Amazon EKS VPC resources have specific requirements to work properly with Kubernetes. For more information, see [Cluster VPC Considerations](https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html) and [Cluster Security Group Considerations](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html) in the Amazon EKS User Guide. Configuration detailed below. * `enabled_cluster_log_types` - (Optional) A list of the desired control plane logging to enable. For more information, see [Amazon EKS Control Plane Logging](https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html) +* `tags` - (Optional) Key-value mapping of resource tags. * `version` – (Optional) Desired Kubernetes master version. If you do not specify a value, the latest available version at resource creation is used and no upgrades will occur except those automatically triggered by EKS. The value must be configured and increased to upgrade the version when desired. Downgrades are not supported by EKS. ### vpc_config