From 3514e3ffefcab881796686f3cba221350a50469f Mon Sep 17 00:00:00 2001 From: Travis Rutledge Date: Fri, 2 Aug 2024 11:56:23 -0400 Subject: [PATCH 1/9] * ecs_service: Added support for tag_specifications under volume_configuration. --- internal/service/ecs/service.go | 82 ++++++++++++++++++++++++++++ internal/service/ecs/service_test.go | 50 +++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index 3753afd229c..2cf138b0ce1 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -1046,6 +1046,30 @@ func resourceService() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "tag_specifications": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_type": { + Type: schema.TypeString, + Required: true, + }, + names.AttrPropagateTags: { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if awstypes.PropagateTags(old) == awstypes.PropagateTagsNone && new == "" { + return true + } + return false + }, + ValidateDiagFunc: enum.Validate[awstypes.PropagateTags](), + }, + names.AttrTags: tftags.TagsSchema(), + }, + }, + }, }, }, }, @@ -2308,10 +2332,68 @@ func expandManagedEBSVolume(ebs []interface{}) *awstypes.ServiceManagedEBSVolume if v, ok := raw[names.AttrVolumeType].(string); ok && v != "" { config.VolumeType = aws.String(v) } + if v, ok := raw["tag_specifications"].([]interface{}); ok && len(v) > 0 { + config.TagSpecifications = expandTagSpecifications(v) + } return config } +func expandTagSpecifications(ts []interface{}) []awstypes.EBSTagSpecification { + if len(ts) == 0 { + return []awstypes.EBSTagSpecification{} + } + + var s []awstypes.EBSTagSpecification + for _, item := range ts { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + var config awstypes.EBSTagSpecification + if v, ok := raw["resource_type"].(string); ok && v != "" { + config.ResourceType = awstypes.EBSResourceType(v) + } + if v, ok := raw[names.AttrPropagateTags].(string); ok && v != "" { + config.PropagateTags = awstypes.PropagateTags(v) + } + if v, ok := raw[names.AttrTags].([]interface{}); ok && len(v) > 0 { + config.Tags = expandECSTags(v) + } + + s = append(s, config) + } + + return s +} + +func expandECSTags(tfList []interface{}) []awstypes.Tag { + if len(tfList) == 0 { + return nil + } + + var ecsTags []awstypes.Tag + + for _, tfMapRaw := range tfList { + tfMap, _ := tfMapRaw.(map[string]interface{}) + + ecsTag := awstypes.Tag{} + + if v, ok := tfMap[names.AttrKey].(string); ok && v != "" { + ecsTag.Key = aws.String(v) + } + + if v, ok := tfMap[names.AttrValue].(string); ok && v != "" { + ecsTag.Value = aws.String(v) + } + + ecsTags = append(ecsTags, ecsTag) + } + + return ecsTags +} + func expandServices(srv []interface{}) []awstypes.ServiceConnectService { if len(srv) == 0 { return nil diff --git a/internal/service/ecs/service_test.go b/internal/service/ecs/service_test.go index 33c0265211a..83cc5ae2ff4 100644 --- a/internal/service/ecs/service_test.go +++ b/internal/service/ecs/service_test.go @@ -328,6 +328,28 @@ func TestAccECSService_VolumeConfiguration_basic(t *testing.T) { }) } +func TestAccECSService_VolumeConfiguration_TagSpecifications(t *testing.T) { + ctx := acctest.Context(t) + var service awstypes.Service + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ecs_service.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckServiceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccServiceConfig_volumeConfiguration_TagSpecifications(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service), + ), + }, + }, + }) +} + func TestAccECSService_VolumeConfiguration_update(t *testing.T) { ctx := acctest.Context(t) var service awstypes.Service @@ -2289,6 +2311,34 @@ resource "aws_ecs_service" "test" { `, rName)) } +func testAccServiceConfig_volumeConfiguration_TagSpecifications(rName string) string { + return acctest.ConfigCompose(testAccServiceConfig_baseVolumeConfiguration(rName), fmt.Sprintf(` +resource "aws_ecs_service" "test" { + name = %[1]q + cluster = aws_ecs_cluster.test.id + task_definition = aws_ecs_task_definition.test.arn + desired_count = 1 + + volume_configuration { + name = "vol1" + managed_ebs_volume { + role_arn = aws_iam_role.ecs_service.arn + size_in_gb = "8" + tag_specifications { + resource_type = "volume" + propagate_tags = "SERVICE" + tags = { + Name = %[1]q + } + } + } + } + + depends_on = [aws_iam_role_policy.ecs_service] +} +`, rName)) +} + func testAccServiceConfig_volumeConfiguration_update(rName, volumeType string, size int) string { return acctest.ConfigCompose(testAccServiceConfig_baseVolumeConfiguration(rName), fmt.Sprintf(` resource "aws_ecs_service" "test" { From dfe53e5bd66aeccfe3b47cc7c8f0e15aa2107746 Mon Sep 17 00:00:00 2001 From: Travis Rutledge Date: Fri, 2 Aug 2024 12:16:19 -0400 Subject: [PATCH 2/9] * Updated ecs_service.html.markdown with tag_specifications --- website/docs/r/ecs_service.html.markdown | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/docs/r/ecs_service.html.markdown b/website/docs/r/ecs_service.html.markdown index 563e16aecfd..f1c782e371d 100644 --- a/website/docs/r/ecs_service.html.markdown +++ b/website/docs/r/ecs_service.html.markdown @@ -183,6 +183,7 @@ The `managed_ebs_volume` configuration block supports the following: * `snapshot_id` - (Optional) Snapshot that Amazon ECS uses to create the volume. You must specify either a `size_in_gb` or a `snapshot_id`. * `throughput` - (Optional) Throughput to provision for a volume, in MiB/s, with a maximum of 1,000 MiB/s. * `volume_type` - (Optional) Volume type. +* `tag_specifications` - (Optional) The tags to apply to the volume. [See below](#tag_specifications). ### capacity_provider_strategy @@ -317,6 +318,14 @@ For more information, see [Task Networking](https://docs.aws.amazon.com/AmazonEC * `dns_name` - (Optional) Name that you use in the applications of client tasks to connect to this service. * `port` - (Required) Listening port number for the Service Connect proxy. This port is available inside of all of the tasks within the same namespace. +### tag_specifications + +`tag_specifications` supports the following: + +* `resource_type` - (Required) The type of volume resource. Valid values, `volume`. +* `propagate_tags` - (Optional) Determines whether to propagate the tags from the task definition to the Amazon EBS volume. +* `tags` - (Optional) The tags applied to this Amazon EBS volume. `AmazonECSCreated` and `AmazonECSManaged` are reserved tags that can't be used. + ## Attribute Reference This resource exports the following attributes in addition to the arguments above: From ee70d5f0ae789be632f6a034b774584aa31842ab Mon Sep 17 00:00:00 2001 From: Travis Rutledge Date: Fri, 2 Aug 2024 13:03:40 -0400 Subject: [PATCH 3/9] making changes from check errors --- internal/service/ecs/service.go | 6 +++--- internal/service/ecs/service_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index 2cf138b0ce1..ccb858efbc9 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -1051,7 +1051,7 @@ func resourceService() *schema.Resource { Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "resource_type": { + names.AttrResourceType: { Type: schema.TypeString, Required: true, }, @@ -2359,7 +2359,7 @@ func expandTagSpecifications(ts []interface{}) []awstypes.EBSTagSpecification { config.PropagateTags = awstypes.PropagateTags(v) } if v, ok := raw[names.AttrTags].([]interface{}); ok && len(v) > 0 { - config.Tags = expandECSTags(v) + config.Tags = expandTags(v) } s = append(s, config) @@ -2368,7 +2368,7 @@ func expandTagSpecifications(ts []interface{}) []awstypes.EBSTagSpecification { return s } -func expandECSTags(tfList []interface{}) []awstypes.Tag { +func expandTags(tfList []interface{}) []awstypes.Tag { if len(tfList) == 0 { return nil } diff --git a/internal/service/ecs/service_test.go b/internal/service/ecs/service_test.go index 83cc5ae2ff4..73a902b0744 100644 --- a/internal/service/ecs/service_test.go +++ b/internal/service/ecs/service_test.go @@ -2325,7 +2325,7 @@ resource "aws_ecs_service" "test" { role_arn = aws_iam_role.ecs_service.arn size_in_gb = "8" tag_specifications { - resource_type = "volume" + resource_type = "volume" propagate_tags = "SERVICE" tags = { Name = %[1]q From d1ae18ee8e7bd13170d61fd7bfde6c25f27474f3 Mon Sep 17 00:00:00 2001 From: Travis Rutledge Date: Fri, 2 Aug 2024 13:33:34 -0400 Subject: [PATCH 4/9] updating static reference for resource_type --- internal/service/ecs/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index ccb858efbc9..474c451850c 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -2352,7 +2352,7 @@ func expandTagSpecifications(ts []interface{}) []awstypes.EBSTagSpecification { } var config awstypes.EBSTagSpecification - if v, ok := raw["resource_type"].(string); ok && v != "" { + if v, ok := raw[names.AttrResourceType].(string); ok && v != "" { config.ResourceType = awstypes.EBSResourceType(v) } if v, ok := raw[names.AttrPropagateTags].(string); ok && v != "" { From cd104988c2fe2f2e330f87e1e9793add87eb6925 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Fri, 20 Sep 2024 15:38:30 -0500 Subject: [PATCH 5/9] aws_ecs_volume: use tags type --- internal/service/ecs/service.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index 520d48037cb..ad778c7db42 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -1056,8 +1056,9 @@ func resourceService() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ names.AttrResourceType: { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.EBSResourceType](), }, names.AttrPropagateTags: { Type: schema.TypeString, @@ -1226,7 +1227,7 @@ func resourceServiceCreate(ctx context.Context, d *schema.ResourceData, meta int } if v, ok := d.GetOk("volume_configuration"); ok && len(v.([]interface{})) > 0 { - input.VolumeConfigurations = expandVolumeConfigurations(v.([]interface{})) + input.VolumeConfigurations = expandVolumeConfigurations(ctx, v.([]interface{})) } output, err := retryServiceCreate(ctx, conn, input) @@ -1510,7 +1511,7 @@ func resourceServiceUpdate(ctx context.Context, d *schema.ResourceData, meta int } if d.HasChange("volume_configuration") { - input.VolumeConfigurations = expandVolumeConfigurations(d.Get("volume_configuration").([]interface{})) + input.VolumeConfigurations = expandVolumeConfigurations(ctx, d.Get("volume_configuration").([]interface{})) } // Retry due to IAM eventual consistency. @@ -2282,7 +2283,7 @@ func expandSecretOptions(sop []interface{}) []awstypes.Secret { return out } -func expandVolumeConfigurations(vc []interface{}) []awstypes.ServiceVolumeConfiguration { +func expandVolumeConfigurations(ctx context.Context, vc []interface{}) []awstypes.ServiceVolumeConfiguration { if len(vc) == 0 { return nil } @@ -2297,7 +2298,7 @@ func expandVolumeConfigurations(vc []interface{}) []awstypes.ServiceVolumeConfig } if v, ok := p["managed_ebs_volume"].([]interface{}); ok && len(v) > 0 { - config.ManagedEBSVolume = expandManagedEBSVolume(v) + config.ManagedEBSVolume = expandManagedEBSVolume(ctx, v) } vcs = append(vcs, config) } @@ -2305,7 +2306,7 @@ func expandVolumeConfigurations(vc []interface{}) []awstypes.ServiceVolumeConfig return vcs } -func expandManagedEBSVolume(ebs []interface{}) *awstypes.ServiceManagedEBSVolumeConfiguration { +func expandManagedEBSVolume(ctx context.Context, ebs []interface{}) *awstypes.ServiceManagedEBSVolumeConfiguration { if len(ebs) == 0 { return &awstypes.ServiceManagedEBSVolumeConfiguration{} } @@ -2340,13 +2341,13 @@ func expandManagedEBSVolume(ebs []interface{}) *awstypes.ServiceManagedEBSVolume config.VolumeType = aws.String(v) } if v, ok := raw["tag_specifications"].([]interface{}); ok && len(v) > 0 { - config.TagSpecifications = expandTagSpecifications(v) + config.TagSpecifications = expandTagSpecifications(ctx, v) } return config } -func expandTagSpecifications(ts []interface{}) []awstypes.EBSTagSpecification { +func expandTagSpecifications(ctx context.Context, ts []interface{}) []awstypes.EBSTagSpecification { if len(ts) == 0 { return []awstypes.EBSTagSpecification{} } @@ -2365,10 +2366,11 @@ func expandTagSpecifications(ts []interface{}) []awstypes.EBSTagSpecification { if v, ok := raw[names.AttrPropagateTags].(string); ok && v != "" { config.PropagateTags = awstypes.PropagateTags(v) } - if v, ok := raw[names.AttrTags].([]interface{}); ok && len(v) > 0 { - config.Tags = expandTags(v) + if v, ok := raw[names.AttrTags].(map[string]any); ok && len(v) > 0 { + if v := tftags.New(ctx, v).IgnoreAWS(); len(v) > 0 { + config.Tags = Tags(v) + } } - s = append(s, config) } From 77cad9391e8cf2d05b39e6690ec00f6aa9b8e441 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Fri, 20 Sep 2024 15:46:46 -0500 Subject: [PATCH 6/9] chore: cleanup unused functions --- internal/service/ecs/service.go | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index ad778c7db42..43a817364fe 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -2377,32 +2377,6 @@ func expandTagSpecifications(ctx context.Context, ts []interface{}) []awstypes.E return s } -func expandTags(tfList []interface{}) []awstypes.Tag { - if len(tfList) == 0 { - return nil - } - - var ecsTags []awstypes.Tag - - for _, tfMapRaw := range tfList { - tfMap, _ := tfMapRaw.(map[string]interface{}) - - ecsTag := awstypes.Tag{} - - if v, ok := tfMap[names.AttrKey].(string); ok && v != "" { - ecsTag.Key = aws.String(v) - } - - if v, ok := tfMap[names.AttrValue].(string); ok && v != "" { - ecsTag.Value = aws.String(v) - } - - ecsTags = append(ecsTags, ecsTag) - } - - return ecsTags -} - func expandServices(srv []interface{}) []awstypes.ServiceConnectService { if len(srv) == 0 { return nil From 579453a42f40d29141db82100dc70297fcf74e92 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Fri, 20 Sep 2024 15:48:22 -0500 Subject: [PATCH 7/9] add CHANGELOG entry --- .changelog/38662.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/38662.txt diff --git a/.changelog/38662.txt b/.changelog/38662.txt new file mode 100644 index 00000000000..8df1d2f648a --- /dev/null +++ b/.changelog/38662.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_ecs_service: Add `tag_specifications` attribute +``` \ No newline at end of file From 2aaf7be41dde49e210108bc44ce98b147bd7fa17 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Fri, 20 Sep 2024 15:56:57 -0500 Subject: [PATCH 8/9] rename test to match similar test casing --- internal/service/ecs/service_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/service/ecs/service_test.go b/internal/service/ecs/service_test.go index ef2f2723283..0365521f973 100644 --- a/internal/service/ecs/service_test.go +++ b/internal/service/ecs/service_test.go @@ -325,7 +325,7 @@ func TestAccECSService_VolumeConfiguration_basic(t *testing.T) { }) } -func TestAccECSService_VolumeConfiguration_TagSpecifications(t *testing.T) { +func TestAccECSService_VolumeConfiguration_tagSpecifications(t *testing.T) { ctx := acctest.Context(t) var service awstypes.Service rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -338,7 +338,7 @@ func TestAccECSService_VolumeConfiguration_TagSpecifications(t *testing.T) { CheckDestroy: testAccCheckServiceDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccServiceConfig_volumeConfiguration_TagSpecifications(rName), + Config: testAccServiceConfig_volumeConfiguration_tagSpecifications(rName), Check: resource.ComposeTestCheckFunc( testAccCheckServiceExists(ctx, resourceName, &service), ), @@ -2320,7 +2320,7 @@ resource "aws_ecs_service" "test" { `, rName)) } -func testAccServiceConfig_volumeConfiguration_TagSpecifications(rName string) string { +func testAccServiceConfig_volumeConfiguration_tagSpecifications(rName string) string { return acctest.ConfigCompose(testAccServiceConfig_baseVolumeConfiguration(rName), fmt.Sprintf(` resource "aws_ecs_service" "test" { name = %[1]q From 053293aa6bb9bf225f9672b81765cc6a4ddc7b3e Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Fri, 20 Sep 2024 16:30:01 -0500 Subject: [PATCH 9/9] tweak CHANGELOG entry --- .changelog/38662.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/38662.txt b/.changelog/38662.txt index 8df1d2f648a..3cfd1caa714 100644 --- a/.changelog/38662.txt +++ b/.changelog/38662.txt @@ -1,3 +1,3 @@ ```release-note:enhancement -resource/aws_ecs_service: Add `tag_specifications` attribute +resource/aws_ecs_service: Add `volume_configuration.managed_ebs_volume.tag_specifications` attribute ``` \ No newline at end of file