diff --git a/aws/resource_aws_ecs_task_definition.go b/aws/resource_aws_ecs_task_definition.go index fbf3a329c1e..62250a2fb46 100644 --- a/aws/resource_aws_ecs_task_definition.go +++ b/aws/resource_aws_ecs_task_definition.go @@ -108,6 +108,50 @@ func resourceAwsEcsTaskDefinition() *schema.Resource { Optional: true, ForceNew: true, }, + + "docker_volume_configuration": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "scope": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + ecs.ScopeShared, + ecs.ScopeTask, + }, false), + }, + "autoprovision": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: false, + }, + "driver": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + }, + "driver_opts": { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + ForceNew: true, + Optional: true, + }, + "labels": { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + ForceNew: true, + Optional: true, + }, + }, + }, + }, }, }, Set: resourceAwsEcsTaskDefinitionVolumeHash, @@ -324,6 +368,5 @@ func resourceAwsEcsTaskDefinitionVolumeHash(v interface{}) int { m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["host_path"].(string))) - return hashcode.String(buf.String()) } diff --git a/aws/resource_aws_ecs_task_definition_test.go b/aws/resource_aws_ecs_task_definition_test.go index 4dc286abad1..5beb907a8b4 100644 --- a/aws/resource_aws_ecs_task_definition_test.go +++ b/aws/resource_aws_ecs_task_definition_test.go @@ -60,6 +60,78 @@ func TestAccAWSEcsTaskDefinition_withScratchVolume(t *testing.T) { }) } +func TestAccAWSEcsTaskDefinition_withDockerVolume(t *testing.T) { + var def ecs.TaskDefinition + + rString := acctest.RandString(8) + tdName := fmt.Sprintf("tf_acc_td_with_docker_volume_%s", rString) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsTaskDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcsTaskDefinitionWithDockerVolumes(tdName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.#", "1"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.#", "1"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.scope", "shared"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.autoprovision", "true"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.driver", "local"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.driver_opts.%", "2"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.driver_opts.uid", "1000"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.driver_opts.device", "tmpfs"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.labels.%", "2"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.labels.stack", "april"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.labels.environment", "test"), + ), + }, + }, + }) +} + +func TestAccAWSEcsTaskDefinition_withDockerVolumeMinimalConfig(t *testing.T) { + var def ecs.TaskDefinition + + rString := acctest.RandString(8) + tdName := fmt.Sprintf("tf_acc_td_with_docker_volume_%s", rString) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsTaskDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcsTaskDefinitionWithDockerVolumesMinimalConfig(tdName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.#", "1"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.#", "1"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.scope", "task"), + resource.TestCheckResourceAttr( + "aws_ecs_task_definition.sleep", "volume.584193650.docker_volume_configuration.0.driver", "local"), + ), + }, + }, + }) +} + // Regression for https://github.com/hashicorp/terraform/issues/2694 func TestAccAWSEcsTaskDefinition_withEcsService(t *testing.T) { var def ecs.TaskDefinition @@ -766,6 +838,70 @@ TASK_DEFINITION `, tdName) } +func testAccAWSEcsTaskDefinitionWithDockerVolumes(tdName string) string { + return fmt.Sprintf(` +resource "aws_ecs_task_definition" "sleep" { + family = "%s" + container_definitions = < 0 { + config := configList[0].(map[string]interface{}) + l.DockerVolumeConfiguration = &ecs.DockerVolumeConfiguration{} + + if v, ok := config["scope"].(string); ok && v != "" { + l.DockerVolumeConfiguration.Scope = aws.String(v) + } + + if v, ok := config["autoprovision"]; ok { + l.DockerVolumeConfiguration.Autoprovision = aws.Bool(v.(bool)) + } + + if v, ok := config["driver"].(string); ok && v != "" { + l.DockerVolumeConfiguration.Driver = aws.String(v) + } + + if v, ok := config["driver_opts"].(map[string]interface{}); ok && len(v) > 0 { + l.DockerVolumeConfiguration.DriverOpts = stringMapToPointers(v) + } + + if v, ok := config["labels"].(map[string]interface{}); ok && len(v) > 0 { + l.DockerVolumeConfiguration.Labels = stringMapToPointers(v) + } + } + volumes = append(volumes, l) } @@ -621,15 +647,47 @@ func flattenEcsVolumes(list []*ecs.Volume) []map[string]interface{} { "name": *volume.Name, } - if volume.Host.SourcePath != nil { + if volume.Host != nil && volume.Host.SourcePath != nil { l["host_path"] = *volume.Host.SourcePath } + if volume.DockerVolumeConfiguration != nil { + l["docker_volume_configuration"] = flattenDockerVolumeConfiguration(volume.DockerVolumeConfiguration) + } + result = append(result, l) } return result } +func flattenDockerVolumeConfiguration(config *ecs.DockerVolumeConfiguration) []interface{} { + var items []interface{} + m := make(map[string]interface{}) + + if config.Scope != nil { + m["scope"] = aws.StringValue(config.Scope) + } + + if config.Autoprovision != nil { + m["autoprovision"] = aws.BoolValue(config.Autoprovision) + } + + if config.Driver != nil { + m["driver"] = aws.StringValue(config.Driver) + } + + if config.DriverOpts != nil { + m["driver_opts"] = pointersMapToStringList(config.DriverOpts) + } + + if config.Labels != nil { + m["labels"] = pointersMapToStringList(config.Labels) + } + + items = append(items, m) + return items +} + // Flattens an array of ECS LoadBalancers into a []map[string]interface{} func flattenEcsLoadBalancers(list []*ecs.LoadBalancer) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) diff --git a/website/docs/r/ecs_task_definition.html.markdown b/website/docs/r/ecs_task_definition.html.markdown index 2676d0b7f23..40fb3ec2636 100644 --- a/website/docs/r/ecs_task_definition.html.markdown +++ b/website/docs/r/ecs_task_definition.html.markdown @@ -93,6 +93,33 @@ official [Developer Guide](https://docs.aws.amazon.com/AmazonECS/latest/develope * `name` - (Required) The name of the volume. This name is referenced in the `sourceVolume` parameter of container definition in the `mountPoints` section. * `host_path` - (Optional) The path on the host container instance that is presented to the container. If not set, ECS will create a nonpersistent data volume that starts empty and is deleted after the task has finished. +* `docker_volume_configuration` - (Optional) Used to configure a [docker volume](#docker-volume-configuration-arguments) + +#### Docker Volume Configuration Arguments + +For more information, see [Specifying a Docker volume in your Task Definition Developer Guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-volumes.html#specify-volume-config) + +* `scope` - (Optional) The scope for the Docker volume, which determines its lifecycle, either `task` or `shared`. Docker volumes that are scoped to a `task` are automatically provisioned when the task starts and destroyed when the task stops. Docker volumes that are `scoped` as shared persist after the task stops. +* `autoprovision` - (Optional) If this value is `true`, the Docker volume is created if it does not already exist. *Note*: This field is only used if the scope is `shared`. +* `driver` - (Optional) The Docker volume driver to use. The driver value must match the driver name provided by Docker because it is used for task placement. +* `driver_opts` - (Optional) A map of Docker driver specific options. +* `labels` - (Optional) A map of custom metadata to add to your Docker volume. + +##### Example Usage: +```hcl +resource "aws_ecs_task_definition" "service" { + family = "service" + container_definitions = "${file("task-definitions/service.json")}" + + volume { + name = "service-storage" + docker_volume_configuration { + scope = "shared" + autoprovision = true + } + } +} +``` #### Placement Constraints Arguments