From a73f947b8d9dcfb93f03768570d6a76ad563f26a Mon Sep 17 00:00:00 2001 From: clint shryock Date: Wed, 4 Jan 2017 09:05:19 -0600 Subject: [PATCH] provider/aws: Add Placement Constraints to ECS Task Definition Adds support for applying placement constraints to aws_ecs_task_definition resource --- .../aws/resource_aws_ecs_task_definition.go | 51 ++++++++ .../resource_aws_ecs_task_definition_test.go | 115 ++++++++++++++++-- .../aws/r/ecs_task_definition.html.markdown | 17 +++ 3 files changed, 174 insertions(+), 9 deletions(-) diff --git a/builtin/providers/aws/resource_aws_ecs_task_definition.go b/builtin/providers/aws/resource_aws_ecs_task_definition.go index 787df3044..2c844638e 100644 --- a/builtin/providers/aws/resource_aws_ecs_task_definition.go +++ b/builtin/providers/aws/resource_aws_ecs_task_definition.go @@ -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 diff --git a/builtin/providers/aws/resource_aws_ecs_task_definition_test.go b/builtin/providers/aws/resource_aws_ecs_task_definition_test.go index b2de53fb6..b167c72e9 100644 --- a/builtin/providers/aws/resource_aws_ecs_task_definition_test.go +++ b/builtin/providers/aws/resource_aws_ecs_task_definition_test.go @@ -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 = <