diff --git a/.changelog/19119.txt b/.changelog/19119.txt new file mode 100644 index 00000000000..0849343e624 --- /dev/null +++ b/.changelog/19119.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_elasticache_subnet_group: Add `tags` argument +``` \ No newline at end of file diff --git a/aws/resource_aws_elasticache_subnet_group.go b/aws/resource_aws_elasticache_subnet_group.go index bbce175e125..e236ef3fc1a 100644 --- a/aws/resource_aws_elasticache_subnet_group.go +++ b/aws/resource_aws_elasticache_subnet_group.go @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/service/elasticache" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) func resourceAwsElasticacheSubnetGroup() *schema.Resource { @@ -46,12 +47,22 @@ func resourceAwsElasticacheSubnetGroup() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), }, + + CustomizeDiff: SetTagsDiff, } } func resourceAwsElasticacheSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).elasticacheconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) // Get the group properties name := d.Get("name").(string) @@ -66,11 +77,12 @@ func resourceAwsElasticacheSubnetGroupCreate(d *schema.ResourceData, meta interf CacheSubnetGroupDescription: aws.String(desc), CacheSubnetGroupName: aws.String(name), SubnetIds: subnetIds, + Tags: tags.IgnoreAws().ElasticacheTags(), } _, err := conn.CreateCacheSubnetGroup(req) if err != nil { - return fmt.Errorf("Error creating CacheSubnetGroup: %s", err) + return fmt.Errorf("error creating ElastiCache Subnet Group (%s): %w", name, err) } // Assign the group name as the resource ID @@ -79,11 +91,14 @@ func resourceAwsElasticacheSubnetGroupCreate(d *schema.ResourceData, meta interf // name contained uppercase characters. d.SetId(strings.ToLower(name)) - return nil + return resourceAwsElasticacheSubnetGroupRead(d, meta) } func resourceAwsElasticacheSubnetGroupRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).elasticacheconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + req := &elasticache.DescribeCacheSubnetGroupsInput{ CacheSubnetGroupName: aws.String(d.Get("name").(string)), } @@ -118,15 +133,34 @@ func resourceAwsElasticacheSubnetGroupRead(d *schema.ResourceData, meta interfac ids[i] = *s.SubnetIdentifier } + d.Set("arn", group.ARN) d.Set("name", group.CacheSubnetGroupName) d.Set("description", group.CacheSubnetGroupDescription) d.Set("subnet_ids", ids) + tags, err := keyvaluetags.ElasticacheListTags(conn, d.Get("arn").(string)) + + if err != nil && !isAWSErr(err, "UnknownOperationException", "") { + return fmt.Errorf("error listing tags for ElastiCache SubnetGroup (%s): %w", d.Id(), err) + } + + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + return nil } func resourceAwsElasticacheSubnetGroupUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).elasticacheconn + if d.HasChanges("subnet_ids", "description") { var subnets []*string if v := d.Get("subnet_ids"); v != nil { @@ -141,8 +175,16 @@ func resourceAwsElasticacheSubnetGroupUpdate(d *schema.ResourceData, meta interf CacheSubnetGroupDescription: aws.String(d.Get("description").(string)), SubnetIds: subnets, }) + if err != nil { - return err + return fmt.Errorf("error updating ElastiCache Subnet Group (%s): %w", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := keyvaluetags.ElasticacheUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating tags: %w", err) } } @@ -184,7 +226,7 @@ func resourceAwsElasticacheSubnetGroupDelete(d *schema.ResourceData, meta interf } if err != nil { - return fmt.Errorf("error deleting Elasticache Subnet Group (%s): %s", d.Id(), err) + return fmt.Errorf("error deleting ElastiCache Subnet Group (%s): %w", d.Id(), err) } return nil diff --git a/aws/resource_aws_elasticache_subnet_group_test.go b/aws/resource_aws_elasticache_subnet_group_test.go index 491139b3cb2..3570aac77d8 100644 --- a/aws/resource_aws_elasticache_subnet_group_test.go +++ b/aws/resource_aws_elasticache_subnet_group_test.go @@ -125,6 +125,53 @@ func TestAccAWSElasticacheSubnetGroup_update(t *testing.T) { }) } +func TestAccAWSElasticacheSubnetGroup_tags(t *testing.T) { + var csg elasticache.CacheSubnetGroup + resourceName := "aws_elasticache_subnet_group.test" + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, elasticache.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheSubnetGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSElasticacheSubnetGroupTags1(rInt, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheSubnetGroupExists(resourceName, &csg), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "description"}, + }, + { + Config: testAccAWSElasticacheSubnetGroupTags2(rInt, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheSubnetGroupExists(resourceName, &csg), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSElasticacheSubnetGroupTags1(rInt, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheSubnetGroupExists(resourceName, &csg), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + func testAccCheckAWSElasticacheSubnetGroupDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).elasticacheconn @@ -299,3 +346,72 @@ resource "aws_elasticache_subnet_group" "test" { } `, rInt)) } + +func testAccAWSElasticacheSubnetGroupTags1(rInt int, tag1Key, tag1Value string) string { + return composeConfig(testAccAvailableAZsNoOptInConfig(), fmt.Sprintf(` +resource "aws_vpc" "foo" { + cidr_block = "192.168.0.0/16" + + tags = { + Name = "terraform-testacc-elasticache-subnet-group" + } +} + +resource "aws_subnet" "foo" { + vpc_id = aws_vpc.foo.id + cidr_block = "192.168.0.0/20" + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = "tf-acc-elasticache-subnet-group" + } +} + +resource "aws_elasticache_subnet_group" "test" { + # Including uppercase letters in this name to ensure + # that we correctly handle the fact that the API + # normalizes names to lowercase. + name = "tf-TEST-cache-subnet-%03d" + subnet_ids = [aws_subnet.foo.id] + + tags = { + %q = %q + } +} +`, rInt, tag1Key, tag1Value)) +} + +func testAccAWSElasticacheSubnetGroupTags2(rInt int, tag1Key, tag1Value, tag2Key, tag2Value string) string { + return composeConfig(testAccAvailableAZsNoOptInConfig(), fmt.Sprintf(` +resource "aws_vpc" "foo" { + cidr_block = "192.168.0.0/16" + + tags = { + Name = "terraform-testacc-elasticache-subnet-group" + } +} + +resource "aws_subnet" "foo" { + vpc_id = aws_vpc.foo.id + cidr_block = "192.168.0.0/20" + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = "tf-acc-elasticache-subnet-group" + } +} + +resource "aws_elasticache_subnet_group" "test" { + # Including uppercase letters in this name to ensure + # that we correctly handle the fact that the API + # normalizes names to lowercase. + name = "tf-TEST-cache-subnet-%03d" + subnet_ids = [aws_subnet.foo.id] + + tags = { + %q = %q + %q = %q + } +} +`, rInt, tag1Key, tag1Value, tag2Key, tag2Value)) +} diff --git a/website/docs/r/elasticache_subnet_group.html.markdown b/website/docs/r/elasticache_subnet_group.html.markdown index 4f36a0065a9..3b4abf287e4 100644 --- a/website/docs/r/elasticache_subnet_group.html.markdown +++ b/website/docs/r/elasticache_subnet_group.html.markdown @@ -48,14 +48,16 @@ The following arguments are supported: * `name` – (Required) Name for the cache subnet group. Elasticache converts this name to lowercase. * `description` – (Optional) Description for the cache subnet group. Defaults to "Managed by Terraform". * `subnet_ids` – (Required) List of VPC Subnet IDs for the cache subnet group +* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `description` -* `name` -* `subnet_ids` +* `description` - The Description of the ElastiCache Subnet Group. +* `name` - The Name of the ElastiCache Subnet Group. +* `subnet_ids` - The Subnet IDs of the ElastiCache Subnet Group. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). ## Import