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,
|
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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue