Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resource/aws_ecs_task_definition: Add docker volume configuration #5727

Merged
merged 1 commit into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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{})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may also want to add a nil check for configList[0] for this at some point in the future to prevent a potential panic, since things can get a little weird if the nested configuration has all zero-values -- see also: #5852

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

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