Merge pull request #11030 from hashicorp/f-aws-ecs-placement
provider/aws: Add Placement Constraints to ECS Task Definition
This commit is contained in:
commit
21e706cb82
|
@ -80,6 +80,27 @@ func resourceAwsEcsTaskDefinition() *schema.Resource {
|
|||
},
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
out, err := conn.RegisterTaskDefinition(&input)
|
||||
if err != nil {
|
||||
|
@ -167,10 +201,27 @@ func resourceAwsEcsTaskDefinitionRead(d *schema.ResourceData, meta interface{})
|
|||
d.Set("task_role_arn", taskDefinition.TaskRoleArn)
|
||||
d.Set("network_mode", taskDefinition.NetworkMode)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
conn := meta.(*AWSClient).ecsconn
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
)
|
||||
|
||||
func TestAccAWSEcsTaskDefinition_basic(t *testing.T) {
|
||||
var def ecs.TaskDefinition
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
@ -19,13 +20,13 @@ func TestAccAWSEcsTaskDefinition_basic(t *testing.T) {
|
|||
resource.TestStep{
|
||||
Config: testAccAWSEcsTaskDefinition,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins"),
|
||||
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.jenkins", &def),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccAWSEcsTaskDefinitionModified,
|
||||
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
|
||||
func TestAccAWSEcsTaskDefinition_withScratchVolume(t *testing.T) {
|
||||
var def ecs.TaskDefinition
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
@ -42,7 +44,7 @@ func TestAccAWSEcsTaskDefinition_withScratchVolume(t *testing.T) {
|
|||
resource.TestStep{
|
||||
Config: testAccAWSEcsTaskDefinitionWithScratchVolume,
|
||||
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
|
||||
func TestAccAWSEcsTaskDefinition_withEcsService(t *testing.T) {
|
||||
var def ecs.TaskDefinition
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
@ -59,14 +62,14 @@ func TestAccAWSEcsTaskDefinition_withEcsService(t *testing.T) {
|
|||
resource.TestStep{
|
||||
Config: testAccAWSEcsTaskDefinitionWithEcsService,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep"),
|
||||
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def),
|
||||
testAccCheckAWSEcsServiceExists("aws_ecs_service.sleep-svc"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccAWSEcsTaskDefinitionWithEcsServiceModified,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep"),
|
||||
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def),
|
||||
testAccCheckAWSEcsServiceExists("aws_ecs_service.sleep-svc"),
|
||||
),
|
||||
},
|
||||
|
@ -75,6 +78,7 @@ func TestAccAWSEcsTaskDefinition_withEcsService(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAccAWSEcsTaskDefinition_withTaskRoleArn(t *testing.T) {
|
||||
var def ecs.TaskDefinition
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
@ -83,7 +87,7 @@ func TestAccAWSEcsTaskDefinition_withTaskRoleArn(t *testing.T) {
|
|||
resource.TestStep{
|
||||
Config: testAccAWSEcsTaskDefinitionWithTaskRoleArn,
|
||||
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) {
|
||||
var def ecs.TaskDefinition
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
@ -99,7 +104,7 @@ func TestAccAWSEcsTaskDefinition_withNetworkMode(t *testing.T) {
|
|||
resource.TestStep{
|
||||
Config: testAccAWSEcsTaskDefinitionWithNetworkMode,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep"),
|
||||
testAccCheckAWSEcsTaskDefinitionExists("aws_ecs_task_definition.sleep", &def),
|
||||
resource.TestCheckResourceAttr(
|
||||
"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) {
|
||||
validNames := []string{
|
||||
"bridge",
|
||||
|
@ -159,17 +191,82 @@ func testAccCheckAWSEcsTaskDefinitionDestroy(s *terraform.State) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckAWSEcsTaskDefinitionExists(name string) resource.TestCheckFunc {
|
||||
func testAccCheckAWSEcsTaskDefinitionExists(name string, def *ecs.TaskDefinition) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
_, ok := s.RootModule().Resources[name]
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 = `
|
||||
resource "aws_ecs_task_definition" "jenkins" {
|
||||
family = "terraform-acc-test"
|
||||
|
|
|
@ -21,6 +21,11 @@ resource "aws_ecs_task_definition" "service" {
|
|||
name = "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.
|
||||
* `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.
|
||||
* `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:
|
||||
|
||||
|
@ -81,6 +88,16 @@ Volume block supports the following arguments:
|
|||
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.
|
||||
|
||||
## 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
|
||||
|
||||
The following attributes are exported:
|
||||
|
|
Loading…
Reference in New Issue