provider/aws: Add Placement Constraints to ECS Task Definition

Adds support for applying placement constraints to
aws_ecs_task_definition resource
This commit is contained in:
clint shryock 2017-01-04 09:05:19 -06:00
parent 5a30bce1bf
commit a73f947b8d
3 changed files with 174 additions and 9 deletions

View File

@ -80,6 +80,27 @@ func resourceAwsEcsTaskDefinition() *schema.Resource {
}, },
Set: resourceAwsEcsTaskDefinitionVolumeHash, Set: resourceAwsEcsTaskDefinitionVolumeHash,
}, },
"placement_constraints": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
MaxItems: 10,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Required: true,
},
"expression": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Required: true,
},
},
},
},
}, },
} }
} }
@ -128,6 +149,19 @@ func resourceAwsEcsTaskDefinitionCreate(d *schema.ResourceData, meta interface{}
input.Volumes = volumes input.Volumes = volumes
} }
constraints := d.Get("placement_constraints").(*schema.Set).List()
if len(constraints) > 0 {
var pc []*ecs.TaskDefinitionPlacementConstraint
for _, raw := range constraints {
p := raw.(map[string]interface{})
pc = append(pc, &ecs.TaskDefinitionPlacementConstraint{
Type: aws.String(p["type"].(string)),
Expression: aws.String(p["expression"].(string)),
})
}
input.PlacementConstraints = pc
}
log.Printf("[DEBUG] Registering ECS task definition: %s", input) log.Printf("[DEBUG] Registering ECS task definition: %s", input)
out, err := conn.RegisterTaskDefinition(&input) out, err := conn.RegisterTaskDefinition(&input)
if err != nil { if err != nil {
@ -167,10 +201,27 @@ func resourceAwsEcsTaskDefinitionRead(d *schema.ResourceData, meta interface{})
d.Set("task_role_arn", taskDefinition.TaskRoleArn) d.Set("task_role_arn", taskDefinition.TaskRoleArn)
d.Set("network_mode", taskDefinition.NetworkMode) d.Set("network_mode", taskDefinition.NetworkMode)
d.Set("volumes", flattenEcsVolumes(taskDefinition.Volumes)) d.Set("volumes", flattenEcsVolumes(taskDefinition.Volumes))
if err := d.Set("placement_constraints", flattenPlacementConstraints(taskDefinition.PlacementConstraints)); err != nil {
log.Printf("[ERR] Error setting placement_constraints for (%s): %s", d.Id(), err)
}
return nil return nil
} }
func flattenPlacementConstraints(pcs []*ecs.TaskDefinitionPlacementConstraint) []map[string]interface{} {
if len(pcs) == 0 {
return nil
}
results := make([]map[string]interface{}, 0)
for _, pc := range pcs {
c := make(map[string]interface{})
c["type"] = *pc.Type
c["expression"] = *pc.Expression
results = append(results, c)
}
return results
}
func resourceAwsEcsTaskDefinitionDelete(d *schema.ResourceData, meta interface{}) error { func resourceAwsEcsTaskDefinitionDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ecsconn conn := meta.(*AWSClient).ecsconn

View File

@ -11,6 +11,7 @@ import (
) )
func TestAccAWSEcsTaskDefinition_basic(t *testing.T) { func TestAccAWSEcsTaskDefinition_basic(t *testing.T) {
var def ecs.TaskDefinition
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
@ -19,13 +20,13 @@ func TestAccAWSEcsTaskDefinition_basic(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSEcsTaskDefinition, Config: testAccAWSEcsTaskDefinition,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins"), testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins", &def),
), ),
}, },
resource.TestStep{ resource.TestStep{
Config: testAccAWSEcsTaskDefinitionModified, Config: testAccAWSEcsTaskDefinitionModified,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins"), testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins", &def),
), ),
}, },
}, },
@ -34,6 +35,7 @@ func TestAccAWSEcsTaskDefinition_basic(t *testing.T) {
// Regression for https://github.com/hashicorp/terraform/issues/2370 // Regression for https://github.com/hashicorp/terraform/issues/2370
func TestAccAWSEcsTaskDefinition_withScratchVolume(t *testing.T) { func TestAccAWSEcsTaskDefinition_withScratchVolume(t *testing.T) {
var def ecs.TaskDefinition
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
@ -42,7 +44,7 @@ func TestAccAWSEcsTaskDefinition_withScratchVolume(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSEcsTaskDefinitionWithScratchVolume, Config: testAccAWSEcsTaskDefinitionWithScratchVolume,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep"), testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def),
), ),
}, },
}, },
@ -51,6 +53,7 @@ func TestAccAWSEcsTaskDefinition_withScratchVolume(t *testing.T) {
// Regression for https://github.com/hashicorp/terraform/issues/2694 // Regression for https://github.com/hashicorp/terraform/issues/2694
func TestAccAWSEcsTaskDefinition_withEcsService(t *testing.T) { func TestAccAWSEcsTaskDefinition_withEcsService(t *testing.T) {
var def ecs.TaskDefinition
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
@ -59,14 +62,14 @@ func TestAccAWSEcsTaskDefinition_withEcsService(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSEcsTaskDefinitionWithEcsService, Config: testAccAWSEcsTaskDefinitionWithEcsService,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep"), testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def),
testAccCheckAWSEcsServiceExists("aws_ecs_service.sleep-svc"), testAccCheckAWSEcsServiceExists("aws_ecs_service.sleep-svc"),
), ),
}, },
resource.TestStep{ resource.TestStep{
Config: testAccAWSEcsTaskDefinitionWithEcsServiceModified, Config: testAccAWSEcsTaskDefinitionWithEcsServiceModified,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep"), testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def),
testAccCheckAWSEcsServiceExists("aws_ecs_service.sleep-svc"), testAccCheckAWSEcsServiceExists("aws_ecs_service.sleep-svc"),
), ),
}, },
@ -75,6 +78,7 @@ func TestAccAWSEcsTaskDefinition_withEcsService(t *testing.T) {
} }
func TestAccAWSEcsTaskDefinition_withTaskRoleArn(t *testing.T) { func TestAccAWSEcsTaskDefinition_withTaskRoleArn(t *testing.T) {
var def ecs.TaskDefinition
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
@ -83,7 +87,7 @@ func TestAccAWSEcsTaskDefinition_withTaskRoleArn(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSEcsTaskDefinitionWithTaskRoleArn, Config: testAccAWSEcsTaskDefinitionWithTaskRoleArn,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep"), testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def),
), ),
}, },
}, },
@ -91,6 +95,7 @@ func TestAccAWSEcsTaskDefinition_withTaskRoleArn(t *testing.T) {
} }
func TestAccAWSEcsTaskDefinition_withNetworkMode(t *testing.T) { func TestAccAWSEcsTaskDefinition_withNetworkMode(t *testing.T) {
var def ecs.TaskDefinition
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
@ -99,7 +104,7 @@ func TestAccAWSEcsTaskDefinition_withNetworkMode(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccAWSEcsTaskDefinitionWithNetworkMode, Config: testAccAWSEcsTaskDefinitionWithNetworkMode,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep"), testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"aws_ecs_task_definition.sleep", "network_mode", "bridge"), "aws_ecs_task_definition.sleep", "network_mode", "bridge"),
), ),
@ -108,6 +113,33 @@ func TestAccAWSEcsTaskDefinition_withNetworkMode(t *testing.T) {
}) })
} }
func TestAccAWSEcsTaskDefinition_constraint(t *testing.T) {
var def ecs.TaskDefinition
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEcsTaskDefinitionDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSEcsTaskDefinition_constraint,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins", &def),
resource.TestCheckResourceAttr("aws_ecs_task_definition.jenkins", "placement_constraints.#", "1"),
testAccCheckAWSTaskDefinitionConstraintsAttrs(&def),
),
},
},
})
}
func testAccCheckAWSTaskDefinitionConstraintsAttrs(def *ecs.TaskDefinition) resource.TestCheckFunc {
return func(s *terraform.State) error {
if len(def.PlacementConstraints) != 1 {
return fmt.Errorf("Expected (1) placement_constraints, got (%d)", len(def.PlacementConstraints))
}
return nil
}
}
func TestValidateAwsEcsTaskDefinitionNetworkMode(t *testing.T) { func TestValidateAwsEcsTaskDefinitionNetworkMode(t *testing.T) {
validNames := []string{ validNames := []string{
"bridge", "bridge",
@ -159,17 +191,82 @@ func testAccCheckAWSEcsTaskDefinitionDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccCheckAWSEcsTaskDefinitionExists(name string) resource.TestCheckFunc { func testAccCheckAWSEcsTaskDefinitionExists(name string, def *ecs.TaskDefinition) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
_, ok := s.RootModule().Resources[name] rs, ok := s.RootModule().Resources[name]
if !ok { if !ok {
return fmt.Errorf("Not found: %s", name) return fmt.Errorf("Not found: %s", name)
} }
conn := testAccProvider.Meta().(*AWSClient).ecsconn
out, err := conn.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{
TaskDefinition: aws.String(rs.Primary.Attributes["arn"]),
})
if err != nil {
return err
}
*def = *out.TaskDefinition
return nil return nil
} }
} }
var testAccAWSEcsTaskDefinition_constraint = `
resource "aws_ecs_task_definition" "jenkins" {
family = "terraform-acc-test"
container_definitions = <<TASK_DEFINITION
[
{
"cpu": 10,
"command": ["sleep", "10"],
"entryPoint": ["/"],
"environment": [
{"name": "VARNAME", "value": "VARVAL"}
],
"essential": true,
"image": "jenkins",
"links": ["mongodb"],
"memory": 128,
"name": "jenkins",
"portMappings": [
{
"containerPort": 80,
"hostPort": 8080
}
]
},
{
"cpu": 10,
"command": ["sleep", "10"],
"entryPoint": ["/"],
"essential": true,
"image": "mongodb",
"memory": 128,
"name": "mongodb",
"portMappings": [
{
"containerPort": 28017,
"hostPort": 28017
}
]
}
]
TASK_DEFINITION
volume {
name = "jenkins-home"
host_path = "/ecs/jenkins-home"
}
placement_constraints {
type = "memberOf"
expression = "attribute:ecs.availability-zone in [us-west-2a, us-west-2b]"
}
}
`
var testAccAWSEcsTaskDefinition = ` var testAccAWSEcsTaskDefinition = `
resource "aws_ecs_task_definition" "jenkins" { resource "aws_ecs_task_definition" "jenkins" {
family = "terraform-acc-test" family = "terraform-acc-test"

View File

@ -21,6 +21,11 @@ resource "aws_ecs_task_definition" "service" {
name = "service-storage" name = "service-storage"
host_path = "/ecs/service-storage" host_path = "/ecs/service-storage"
} }
placement_constraints {
type = "memberOf"
expression = "attribute:ecs.availability-zone in [us-west-2a, us-west-2b]"
}
} }
``` ```
@ -74,6 +79,8 @@ official [Developer Guide](https://docs.aws.amazon.com/AmazonECS/latest/develope
* `task_role_arn` - (Optional) The ARN of IAM role that allows your Amazon ECS container task to make calls to other AWS services. * `task_role_arn` - (Optional) The ARN of IAM role that allows your Amazon ECS container task to make calls to other AWS services.
* `network_mode` - (Optional) The Docker networking mode to use for the containers in the task. The valid values are `none`, `bridge`, and `host`. * `network_mode` - (Optional) The Docker networking mode to use for the containers in the task. The valid values are `none`, `bridge`, and `host`.
* `volume` - (Optional) A volume block. See below for details about what arguments are supported. * `volume` - (Optional) A volume block. See below for details about what arguments are supported.
* `placement_constraints` - (Optional) rules that are taken into consideration during task placement. Maximum number of
`placement_constraints` is `10`. Defined below.
Volume block supports the following arguments: Volume block supports the following arguments:
@ -81,6 +88,16 @@ Volume block supports the following arguments:
parameter of container definition in the `mountPoints` section. 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. * `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.
## placement_constraints
`placement_constraints` support the following:
* `expression` - Cluster Query Language expression to apply to the constraint.
For more information, see [Cluster Query Language in the Amazon EC2 Container
Service Developer
Guide](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query-language.html).
* `type` - The type of constraint. The only valid value at this time is `memberOf`
## Attributes Reference ## Attributes Reference
The following attributes are exported: The following attributes are exported: