diff --git a/builtin/providers/aws/resource_aws_ecs_service.go b/builtin/providers/aws/resource_aws_ecs_service.go index b8b2168e1..e3030581e 100644 --- a/builtin/providers/aws/resource_aws_ecs_service.go +++ b/builtin/providers/aws/resource_aws_ecs_service.go @@ -26,73 +26,73 @@ func resourceAwsEcsService() *schema.Resource { Delete: resourceAwsEcsServiceDelete, Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "cluster": &schema.Schema{ + "cluster": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "task_definition": &schema.Schema{ + "task_definition": { Type: schema.TypeString, Required: true, }, - "desired_count": &schema.Schema{ + "desired_count": { Type: schema.TypeInt, Optional: true, }, - "iam_role": &schema.Schema{ + "iam_role": { Type: schema.TypeString, ForceNew: true, Optional: true, }, - "deployment_maximum_percent": &schema.Schema{ + "deployment_maximum_percent": { Type: schema.TypeInt, Optional: true, Default: 200, }, - "deployment_minimum_healthy_percent": &schema.Schema{ + "deployment_minimum_healthy_percent": { Type: schema.TypeInt, Optional: true, Default: 100, }, - "load_balancer": &schema.Schema{ + "load_balancer": { Type: schema.TypeSet, Optional: true, ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "elb_name": &schema.Schema{ + "elb_name": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "target_group_arn": &schema.Schema{ + "target_group_arn": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - "container_name": &schema.Schema{ + "container_name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "container_port": &schema.Schema{ + "container_port": { Type: schema.TypeInt, Required: true, ForceNew: true, @@ -102,19 +102,39 @@ func resourceAwsEcsService() *schema.Resource { Set: resourceAwsEcsLoadBalancerHash, }, - "placement_strategy": &schema.Schema{ + "placement_strategy": { Type: schema.TypeSet, Optional: true, ForceNew: true, MaxItems: 5, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "type": &schema.Schema{ + "type": { Type: schema.TypeString, ForceNew: true, Required: true, }, - "field": &schema.Schema{ + "field": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + }, + }, + }, + "placement_constraints": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + MaxItems: 10, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { //TODO: Add a Validation for the types + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "expression": { Type: schema.TypeString, ForceNew: true, Required: true, @@ -166,6 +186,19 @@ func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error input.PlacementStrategy = ps } + constraints := d.Get("placement_constraints").(*schema.Set).List() + if len(constraints) > 0 { + var pc []*ecs.PlacementConstraint + for _, raw := range constraints { + p := raw.(map[string]interface{}) + pc = append(pc, &ecs.PlacementConstraint{ + Type: aws.String(p["type"].(string)), + Expression: aws.String(p["expression"].(string)), + }) + } + input.PlacementConstraints = pc + } + log.Printf("[DEBUG] Creating ECS service: %s", input) // Retry due to AWS IAM policy eventual consistency @@ -277,10 +310,27 @@ func resourceAwsEcsServiceRead(d *schema.ResourceData, meta interface{}) error { if err := d.Set("placement_strategy", flattenPlacementStrategy(service.PlacementStrategy)); err != nil { log.Printf("[ERR] Error setting placement_strategy for (%s): %s", d.Id(), err) } + if err := d.Set("placement_constraints", flattenServicePlacementConstraints(service.PlacementConstraints)); err != nil { + log.Printf("[ERR] Error setting placement_constraints for (%s): %s", d.Id(), err) + } return nil } +func flattenServicePlacementConstraints(pcs []*ecs.PlacementConstraint) []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 flattenPlacementStrategy(pss []*ecs.PlacementStrategy) []map[string]interface{} { if len(pss) == 0 { return nil diff --git a/builtin/providers/aws/resource_aws_ecs_service_test.go b/builtin/providers/aws/resource_aws_ecs_service_test.go index 3f3fd6f60..458768ebe 100644 --- a/builtin/providers/aws/resource_aws_ecs_service_test.go +++ b/builtin/providers/aws/resource_aws_ecs_service_test.go @@ -13,52 +13,52 @@ import ( func TestParseTaskDefinition(t *testing.T) { cases := map[string]map[string]interface{}{ - "invalid": map[string]interface{}{ + "invalid": { "family": "", "revision": "", "isValid": false, }, - "invalidWithColon:": map[string]interface{}{ + "invalidWithColon:": { "family": "", "revision": "", "isValid": false, }, - "1234": map[string]interface{}{ + "1234": { "family": "", "revision": "", "isValid": false, }, - "invalid:aaa": map[string]interface{}{ + "invalid:aaa": { "family": "", "revision": "", "isValid": false, }, - "invalid=family:1": map[string]interface{}{ + "invalid=family:1": { "family": "", "revision": "", "isValid": false, }, - "invalid:name:1": map[string]interface{}{ + "invalid:name:1": { "family": "", "revision": "", "isValid": false, }, - "valid:1": map[string]interface{}{ + "valid:1": { "family": "valid", "revision": "1", "isValid": true, }, - "abc12-def:54": map[string]interface{}{ + "abc12-def:54": { "family": "abc12-def", "revision": "54", "isValid": true, }, - "lorem_ip-sum:123": map[string]interface{}{ + "lorem_ip-sum:123": { "family": "lorem_ip-sum", "revision": "123", "isValid": true, }, - "lorem-ipsum:1": map[string]interface{}{ + "lorem-ipsum:1": { "family": "lorem-ipsum", "revision": "1", "isValid": true, @@ -89,14 +89,14 @@ func TestAccAWSEcsServiceWithARN(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsService, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"), ), }, - resource.TestStep{ + { Config: testAccAWSEcsServiceModified, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"), @@ -112,14 +112,14 @@ func TestAccAWSEcsServiceWithFamilyAndRevision(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsServiceWithFamilyAndRevision, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.jenkins"), ), }, - resource.TestStep{ + { Config: testAccAWSEcsServiceWithFamilyAndRevisionModified, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.jenkins"), @@ -141,7 +141,7 @@ func TestAccAWSEcsServiceWithRenamedCluster(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsServiceWithRenamedCluster, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.ghost"), @@ -150,7 +150,7 @@ func TestAccAWSEcsServiceWithRenamedCluster(t *testing.T) { ), }, - resource.TestStep{ + { Config: testAccAWSEcsServiceWithRenamedClusterModified, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.ghost"), @@ -168,7 +168,7 @@ func TestAccAWSEcsService_withIamRole(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsService_withIamRole, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.ghost"), @@ -184,7 +184,7 @@ func TestAccAWSEcsService_withDeploymentValues(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsServiceWithDeploymentValues, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"), @@ -205,13 +205,13 @@ func TestAccAWSEcsService_withLbChanges(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsService_withLbChanges, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.with_lb_changes"), ), }, - resource.TestStep{ + { Config: testAccAWSEcsService_withLbChanges_modified, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.with_lb_changes"), @@ -229,7 +229,7 @@ func TestAccAWSEcsService_withEcsClusterName(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsServiceWithEcsClusterName, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.jenkins"), @@ -247,7 +247,7 @@ func TestAccAWSEcsService_withAlb(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsServiceWithAlb, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.with_alb"), @@ -263,14 +263,14 @@ func TestAccAWSEcsServiceWithPlacementStrategy(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSEcsServiceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSEcsService, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"), resource.TestCheckResourceAttr("aws_ecs_service.mongo", "placement_strategy.#", "0"), ), }, - resource.TestStep{ + { Config: testAccAWSEcsServiceWithPlacementStrategy, Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"), @@ -281,6 +281,23 @@ func TestAccAWSEcsServiceWithPlacementStrategy(t *testing.T) { }) } +func TestAccAWSEcsServiceWithPlacementConstraints(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcsServiceWithPlacementConstraint, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo"), + resource.TestCheckResourceAttr("aws_ecs_service.mongo", "placement_constraints.#", "1"), + ), + }, + }, + }) +} + func testAccCheckAWSEcsServiceDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ecsconn @@ -416,6 +433,38 @@ resource "aws_ecs_service" "mongo" { } ` +var testAccAWSEcsServiceWithPlacementConstraint = ` +resource "aws_ecs_cluster" "default" { + name = "terraformecstest21" +} + +resource "aws_ecs_task_definition" "mongo" { + family = "mongodb" + container_definitions = < **Note:** As a result of an AWS limitation, a single `load_balancer` can be attached to the ECS service at most. See [related docs](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-load-balancing.html#load-balancing-concepts). @@ -64,6 +71,16 @@ Load balancers support the following: * `container_name` - (Required) The name of the container to associate with the load balancer (as it appears in a container definition). * `container_port` - (Required) The port on the container to associate with the load balancer. +## 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 values at this time are `memberOf` and `distinctInstance`. + ## Attributes Reference