Skip to content

Commit

Permalink
Add docker volume configuration to ecs tasks
Browse files Browse the repository at this point in the history
Add support for docker volume configuration to ecs task definitions

Resolves #5523

Signed-off-by: Edward Wilde <[email protected]>
  • Loading branch information
ewilde committed Sep 7, 2018
1 parent 10f781a commit a106e1f
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 2 deletions.
45 changes: 44 additions & 1 deletion aws/resource_aws_ecs_task_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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())
}
136 changes: 136 additions & 0 deletions aws/resource_aws_ecs_task_definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = <<TASK_DEFINITION
[
{
"name": "sleep",
"image": "busybox",
"cpu": 10,
"command": ["sleep","360"],
"memory": 10,
"essential": true
}
]
TASK_DEFINITION
volume {
name = "database_scratch"
docker_volume_configuration {
driver = "local"
scope = "shared"
driver_opts {
device = "tmpfs"
uid = "1000"
}
labels {
environment = "test"
stack = "april"
}
autoprovision = true
}
}
}
`, tdName)
}

func testAccAWSEcsTaskDefinitionWithDockerVolumesMinimalConfig(tdName string) string {
return fmt.Sprintf(`
resource "aws_ecs_task_definition" "sleep" {
family = "%s"
container_definitions = <<TASK_DEFINITION
[
{
"name": "sleep",
"image": "busybox",
"cpu": 10,
"command": ["sleep","360"],
"memory": 10,
"essential": true
}
]
TASK_DEFINITION
volume {
name = "database_scratch"
docker_volume_configuration {
autoprovision = true
}
}
}
`, tdName)
}

func testAccAWSEcsTaskDefinitionWithTaskRoleArn(roleName, policyName, tdName string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "role_test" {
Expand Down
60 changes: 59 additions & 1 deletion aws/structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,32 @@ func expandEcsVolumes(configured []interface{}) ([]*ecs.Volume, error) {
}
}

configList, ok := data["docker_volume_configuration"].([]interface{})
if ok && len(configList) > 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)
}

Expand Down Expand Up @@ -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))
Expand Down
27 changes: 27 additions & 0 deletions website/docs/r/ecs_task_definition.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit a106e1f

Please sign in to comment.