diff --git a/.changelog/37019.txt b/.changelog/37019.txt new file mode 100644 index 000000000000..80562c6857cb --- /dev/null +++ b/.changelog/37019.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_ecs_service: Add `volume_configuration` argument +``` + +```release-note:enhancement +resource/aws_ecs_task_definition: Add `configure_at_launch` parameter in `volume` argument +``` \ No newline at end of file diff --git a/GNUmakefile b/GNUmakefile index 517c7032e692..c1c051874db1 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -503,6 +503,7 @@ semgrep-code-quality: semgrep-validate ## [CI] Semgrep Checks / Code Quality Sca semgrep-constants: semgrep-validate ## Fix constants with Semgrep --autofix @echo "make: Fix constants with Semgrep --autofix" @semgrep $(SEMGREP_ARGS) --autofix \ + $(if $(filter-out $(origin PKG), undefined),--include $(PKG_NAME),) \ --config .ci/.semgrep-constants.yml \ --config .ci/.semgrep-test-constants.yml diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index 37d562f3a068..075efa7cb702 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -518,6 +518,69 @@ func ResourceService() *schema.Resource { Optional: true, Default: false, }, + "volume_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + names.AttrName: { + Type: schema.TypeString, + Required: true, + }, + "managed_ebs_volume": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + names.AttrRoleARN: { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + names.AttrEncrypted: { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "file_system_type": { + Type: schema.TypeString, + Optional: true, + Default: ecs.TaskFilesystemTypeXfs, + ValidateFunc: validation.StringInSlice(ecs.TaskFilesystemType_Values(), false), + }, + names.AttrIOPS: { + Type: schema.TypeInt, + Optional: true, + }, + names.AttrKMSKeyID: { + Type: schema.TypeString, + Optional: true, + }, + "size_in_gb": { + Type: schema.TypeInt, + Optional: true, + }, + names.AttrSnapshotID: { + Type: schema.TypeString, + Optional: true, + }, + "throughput": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IntBetween(0, 1000), + }, + names.AttrVolumeType: { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, }, CustomizeDiff: customdiff.Sequence( @@ -625,6 +688,10 @@ func resourceServiceCreate(ctx context.Context, d *schema.ResourceData, meta int input.ServiceConnectConfiguration = expandServiceConnectConfiguration(v.([]interface{})) } + if v, ok := d.GetOk("volume_configuration"); ok && len(v.([]interface{})) > 0 { + input.VolumeConfigurations = expandVolumeConfigurations(v.([]interface{})) + } + serviceRegistries := d.Get("service_registries").([]interface{}) if len(serviceRegistries) > 0 { srs := make([]*ecs.ServiceRegistry, 0, len(serviceRegistries)) @@ -956,6 +1023,10 @@ func resourceServiceUpdate(ctx context.Context, d *schema.ResourceData, meta int input.ServiceConnectConfiguration = expandServiceConnectConfiguration(d.Get("service_connect_configuration").([]interface{})) } + if d.HasChange("volume_configuration") { + input.VolumeConfigurations = expandVolumeConfigurations(d.Get("volume_configuration").([]interface{})) + } + if d.HasChange("service_registries") { input.ServiceRegistries = expandServiceRegistries(d.Get("service_registries").([]interface{})) } @@ -1466,6 +1537,67 @@ func expandSecretOptions(sop []interface{}) []*ecs.Secret { return out } +func expandVolumeConfigurations(vc []interface{}) []*ecs.ServiceVolumeConfiguration { + if len(vc) == 0 { + return nil + } + + vcs := make([]*ecs.ServiceVolumeConfiguration, 0) + + for _, raw := range vc { + p := raw.(map[string]interface{}) + + config := &ecs.ServiceVolumeConfiguration{ + Name: aws.String(p[names.AttrName].(string)), + } + + if v, ok := p["managed_ebs_volume"].([]interface{}); ok && len(v) > 0 { + config.ManagedEBSVolume = expandManagedEBSVolume(v) + } + vcs = append(vcs, config) + } + + return vcs +} + +func expandManagedEBSVolume(ebs []interface{}) *ecs.ServiceManagedEBSVolumeConfiguration { + if len(ebs) == 0 { + return &ecs.ServiceManagedEBSVolumeConfiguration{} + } + raw := ebs[0].(map[string]interface{}) + + config := &ecs.ServiceManagedEBSVolumeConfiguration{} + if v, ok := raw[names.AttrRoleARN].(string); ok && v != "" { + config.RoleArn = aws.String(v) + } + if v, ok := raw[names.AttrEncrypted].(bool); ok { + config.Encrypted = aws.Bool(v) + } + if v, ok := raw["file_system_type"].(string); ok && v != "" { + config.FilesystemType = aws.String(v) + } + if v, ok := raw[names.AttrIOPS].(int); ok && v != 0 { + config.Iops = aws.Int64(int64(v)) + } + if v, ok := raw[names.AttrKMSKeyID].(string); ok && v != "" { + config.KmsKeyId = aws.String(v) + } + if v, ok := raw["size_in_gb"].(int); ok && v != 0 { + config.SizeInGiB = aws.Int64(int64(v)) + } + if v, ok := raw[names.AttrSnapshotID].(string); ok && v != "" { + config.SnapshotId = aws.String(v) + } + if v, ok := raw["throughput"].(int); ok && v != 0 { + config.Throughput = aws.Int64(int64(v)) + } + if v, ok := raw[names.AttrVolumeType].(string); ok && v != "" { + config.VolumeType = aws.String(v) + } + + return config +} + func expandServices(srv []interface{}) []*ecs.ServiceConnectService { if len(srv) == 0 { return nil diff --git a/internal/service/ecs/service_test.go b/internal/service/ecs/service_test.go index c12af96e28f6..b53a26912c4a 100644 --- a/internal/service/ecs/service_test.go +++ b/internal/service/ecs/service_test.go @@ -307,6 +307,62 @@ func TestAccECSService_CapacityProviderStrategy_update(t *testing.T) { }) } +func TestAccECSService_VolumeConfiguration_basic(t *testing.T) { + ctx := acctest.Context(t) + var service ecs.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_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service), + ), + }, + }, + }) +} + +func TestAccECSService_VolumeConfiguration_update(t *testing.T) { + ctx := acctest.Context(t) + var service ecs.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_update(rName, "gp2", 8), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service), + ), + }, + { + Config: testAccServiceConfig_volumeConfiguration_update(rName, "gp3", 8), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service), + ), + }, + { + Config: testAccServiceConfig_volumeConfiguration_update(rName, "gp3", 16), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service), + ), + }, + }, + }) +} + func TestAccECSService_familyAndRevision(t *testing.T) { ctx := acctest.Context(t) var service ecs.Service @@ -2088,6 +2144,195 @@ resource "aws_ecs_service" "test" { `, rName)) } +func testAccServiceConfig_volumeConfiguration_basic(rName string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} +data "aws_partition" "current" {} + +resource "aws_ecs_cluster" "test" { + name = %[1]q +} + +resource "aws_ecs_task_definition" "test" { + family = %[1]q + + container_definitions = < 0 { l.DockerVolumeConfiguration = expandVolumesDockerVolume(v) } @@ -1056,6 +1066,10 @@ func flattenVolumes(list []*ecs.Volume) []map[string]interface{} { l["host_path"] = aws.StringValue(volume.Host.SourcePath) } + if volume.ConfiguredAtLaunch != nil { + l["configure_at_launch"] = aws.BoolValue(volume.ConfiguredAtLaunch) + } + if volume.DockerVolumeConfiguration != nil { l["docker_volume_configuration"] = flattenDockerVolumeConfiguration(volume.DockerVolumeConfiguration) } diff --git a/internal/service/ecs/task_definition_test.go b/internal/service/ecs/task_definition_test.go index 563bedf2dfa5..3d81ecde66c5 100644 --- a/internal/service/ecs/task_definition_test.go +++ b/internal/service/ecs/task_definition_test.go @@ -137,6 +137,37 @@ func TestAccECSTaskDefinition_scratchVolume(t *testing.T) { }) } +func TestAccECSTaskDefinition_configuredAtLaunch(t *testing.T) { + ctx := acctest.Context(t) + var def ecs.TaskDefinition + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ecs_task_definition.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTaskDefinitionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTaskDefinitionConfig_configuredAtLaunch(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTaskDefinitionExists(ctx, resourceName, &def), + resource.TestCheckResourceAttr(resourceName, "volume.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "volume.0.configure_at_launch", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccTaskDefinitionImportStateIdFunc(resourceName), + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{names.AttrSkipDestroy, "track_latest"}, + }, + }, + }) +} + func TestAccECSTaskDefinition_DockerVolume_basic(t *testing.T) { ctx := acctest.Context(t) var def ecs.TaskDefinition @@ -1793,6 +1824,35 @@ TASK_DEFINITION `, rName) } +func testAccTaskDefinitionConfig_configuredAtLaunch(rName string) string { + return fmt.Sprintf(` +resource "aws_ecs_task_definition" "test" { + family = %[1]q + + container_definitions = <