From cd54da5495fcec8d72859bf8c89d8c8ad185a360 Mon Sep 17 00:00:00 2001 From: Atsushi Ishibashi Date: Sat, 24 Mar 2018 17:20:05 +0900 Subject: [PATCH 1/3] resource/ecs_service: Support ServiceRegistries --- aws/resource_aws_ecs_service.go | 57 ++++++++++++ aws/resource_aws_ecs_service_test.go | 105 +++++++++++++++++++++++ website/docs/r/ecs_service.html.markdown | 10 ++- 3 files changed, 171 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_ecs_service.go b/aws/resource_aws_ecs_service.go index 7d8b74aba70..d4f3c7ddaca 100644 --- a/aws/resource_aws_ecs_service.go +++ b/aws/resource_aws_ecs_service.go @@ -204,6 +204,24 @@ func resourceAwsEcsService() *schema.Resource { }, }, }, + + "service_registries": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "port": { + Type: schema.TypeInt, + Optional: true, + }, + "registry_arn": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, }, } } @@ -305,6 +323,23 @@ func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error input.PlacementConstraints = pc } + serviceRegistries := d.Get("service_registries").(*schema.Set).List() + if len(serviceRegistries) > 0 { + srs := make([]*ecs.ServiceRegistry, 0, len(serviceRegistries)) + for _, v := range serviceRegistries { + raw := v.(map[string]interface{}) + sr := &ecs.ServiceRegistry{ + RegistryArn: aws.String(raw["registry_arn"].(string)), + } + if port, ok := raw["port"].(int); ok { + sr.Port = aws.Int64(int64(port)) + } + + srs = append(srs, sr) + } + input.ServiceRegistries = srs + } + log.Printf("[DEBUG] Creating ECS service: %s", input) // Retry due to AWS IAM & ECS eventual consistency @@ -445,6 +480,10 @@ func resourceAwsEcsServiceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("[ERR] Error setting network_configuration for (%s): %s", d.Id(), err) } + if err := d.Set("service_registries", flattenServiceRegistries(service.ServiceRegistries)); err != nil { + return fmt.Errorf("[ERR] Error setting service_registries for (%s): %s", d.Id(), err) + } + return nil } @@ -521,6 +560,24 @@ func flattenPlacementStrategy(pss []*ecs.PlacementStrategy) []map[string]interfa return results } +func flattenServiceRegistries(srs []*ecs.ServiceRegistry) []map[string]interface{} { + if len(srs) == 0 { + return nil + } + results := make([]map[string]interface{}, 0) + for _, sr := range srs { + c := make(map[string]interface{}) + if sr.Port != nil { + c["port"] = *sr.Port + } + if sr.RegistryArn != nil { + c["registry_arn"] = *sr.RegistryArn + } + results = append(results, c) + } + return results +} + func resourceAwsEcsServiceUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ecsconn diff --git a/aws/resource_aws_ecs_service_test.go b/aws/resource_aws_ecs_service_test.go index 5d12bcc5088..a54dc51bdb7 100644 --- a/aws/resource_aws_ecs_service_test.go +++ b/aws/resource_aws_ecs_service_test.go @@ -611,6 +611,30 @@ func TestAccAWSEcsService_withLaunchTypeEC2AndNetworkConfiguration(t *testing.T) }) } +func TestAccAWSEcsService_withServiceRegistries(t *testing.T) { + var service ecs.Service + rString := acctest.RandString(8) + + clusterName := fmt.Sprintf("tf-acc-cluster-svc-w-ups-%s", rString) + tdName := fmt.Sprintf("tf-acc-td-svc-w-ups-%s", rString) + svcName := fmt.Sprintf("tf-acc-svc-w-ups-%s", rString) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcsServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcsService_withServiceRegistries(rString, clusterName, tdName, svcName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcsServiceExists("aws_ecs_service.test", &service), + resource.TestCheckResourceAttr("aws_ecs_service.test", "service_registries.#", "1"), + ), + }, + }, + }) +} + func testAccCheckAWSEcsServiceDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ecsconn @@ -1688,3 +1712,84 @@ resource "aws_ecs_service" "main" { } `, sg1Name, sg2Name, clusterName, tdName, svcName, securityGroups) } + +func testAccAWSEcsService_withServiceRegistries(rName, clusterName, tdName, svcName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "test" {} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "test" { + count = 2 + cidr_block = "${cidrsubnet(aws_vpc.test.cidr_block, 8, count.index)}" + availability_zone = "${data.aws_availability_zones.test.names[count.index]}" + vpc_id = "${aws_vpc.test.id}" +} + +resource "aws_security_group" "test" { + name = "tf-acc-sg-%s" + vpc_id = "${aws_vpc.test.id}" + + ingress { + protocol = "-1" + from_port = 0 + to_port = 0 + cidr_blocks = ["${aws_vpc.test.cidr_block}"] + } +} + +resource "aws_service_discovery_private_dns_namespace" "test" { + name = "tf-acc-sd-%s.terraform.local" + description = "test" + vpc = "${aws_vpc.test.id}" +} + +resource "aws_service_discovery_service" "test" { + name = "tf-acc-sd-%s" + dns_config { + namespace_id = "${aws_service_discovery_private_dns_namespace.test.id}" + dns_records { + ttl = 5 + type = "SRV" + } + } +} + +resource "aws_ecs_cluster" "test" { + name = "%s" +} + +resource "aws_ecs_task_definition" "test" { + family = "%s" + network_mode = "awsvpc" + 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](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-load-balancing.html#load-balancing-concepts). @@ -106,6 +107,13 @@ Guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query For more information, see [Task Networking](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html) +## service_registries + +`service_registries` support the following: + +* `registry_arn` - (Required) The ARN of the Service Registry. The currently supported service registry is Amazon Route 53 Auto Naming Service(`aws_service_discovery_service`). For more information, see [Service](https://docs.aws.amazon.com/Route53/latest/APIReference/API_autonaming_Service.html) +* `port` - (Optional) The port value used if your Service Discovery service specified an SRV record. + ## Attributes Reference The following attributes are exported: @@ -122,4 +130,4 @@ ECS services can be imported using the `name` together with ecs cluster `name`, ``` $ terraform import aws_ecs_service.imported cluster-name/service-name -``` \ No newline at end of file +``` From f0a434799d10e692d85a33fd9b15c45f8ec73985 Mon Sep 17 00:00:00 2001 From: Atsushi Ishibashi Date: Wed, 4 Apr 2018 15:33:57 +0900 Subject: [PATCH 2/3] Reflect reviews --- aws/resource_aws_ecs_service.go | 3 ++- website/docs/r/ecs_service.html.markdown | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_ecs_service.go b/aws/resource_aws_ecs_service.go index d4f3c7ddaca..380dc04a7bf 100644 --- a/aws/resource_aws_ecs_service.go +++ b/aws/resource_aws_ecs_service.go @@ -209,6 +209,7 @@ func resourceAwsEcsService() *schema.Resource { Type: schema.TypeSet, Optional: true, ForceNew: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "port": { @@ -331,7 +332,7 @@ func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error sr := &ecs.ServiceRegistry{ RegistryArn: aws.String(raw["registry_arn"].(string)), } - if port, ok := raw["port"].(int); ok { + if port, ok := raw["port"].(int); ok && port != 0 { sr.Port = aws.Int64(int64(port)) } diff --git a/website/docs/r/ecs_service.html.markdown b/website/docs/r/ecs_service.html.markdown index cc967dc0aeb..f7b43acedda 100644 --- a/website/docs/r/ecs_service.html.markdown +++ b/website/docs/r/ecs_service.html.markdown @@ -63,7 +63,7 @@ into consideration during task placement. The maximum number of * `placement_constraints` - (Optional) rules that are taken into consideration during task placement. Maximum number of `placement_constraints` is `10`. Defined below. * `network_configuration` - (Optional) The network configuration for the service. This parameter is required for task definitions that use the awsvpc network mode to receive their own Elastic Network Interface, and it is not supported for other network modes. -* `service_registries` - (Optional) The service discovery registries for the service. +* `service_registries` - (Optional) The service discovery registries for the service. The maximum number of `service_registries` blocks is `1`. -> **Note:** As a result of an AWS limitation, a single `load_balancer` can be attached to the ECS service at most. See [related docs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-load-balancing.html#load-balancing-concepts). From 3f41ea6972fb44390416d4991ad3239c36b6aa1b Mon Sep 17 00:00:00 2001 From: Atsushi Ishibashi Date: Thu, 5 Apr 2018 12:14:52 +0900 Subject: [PATCH 3/3] review --- aws/resource_aws_ecs_service.go | 20 +++++++++++--------- aws/resource_aws_ecs_service_test.go | 12 +++++++----- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/aws/resource_aws_ecs_service.go b/aws/resource_aws_ecs_service.go index 380dc04a7bf..7de62c2c5d5 100644 --- a/aws/resource_aws_ecs_service.go +++ b/aws/resource_aws_ecs_service.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" ) var taskDefinitionRE = regexp.MustCompile("^([a-zA-Z0-9_-]+):([0-9]+)$") @@ -213,12 +214,14 @@ func resourceAwsEcsService() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "port": { - Type: schema.TypeInt, - Optional: true, + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 65536), }, "registry_arn": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, }, }, }, @@ -567,12 +570,11 @@ func flattenServiceRegistries(srs []*ecs.ServiceRegistry) []map[string]interface } results := make([]map[string]interface{}, 0) for _, sr := range srs { - c := make(map[string]interface{}) - if sr.Port != nil { - c["port"] = *sr.Port + c := map[string]interface{}{ + "registry_arn": aws.StringValue(sr.RegistryArn), } - if sr.RegistryArn != nil { - c["registry_arn"] = *sr.RegistryArn + if sr.Port != nil { + c["port"] = int(aws.Int64Value(sr.Port)) } results = append(results, c) } diff --git a/aws/resource_aws_ecs_service_test.go b/aws/resource_aws_ecs_service_test.go index a54dc51bdb7..27d4c87a7ec 100644 --- a/aws/resource_aws_ecs_service_test.go +++ b/aws/resource_aws_ecs_service_test.go @@ -102,6 +102,7 @@ func TestAccAWSEcsService_withARN(t *testing.T) { Config: testAccAWSEcsService(clusterName, tdName, svcName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo", &service), + resource.TestCheckResourceAttr("aws_ecs_service.mongo", "service_registries.#", "0"), ), }, @@ -109,6 +110,7 @@ func TestAccAWSEcsService_withARN(t *testing.T) { Config: testAccAWSEcsServiceModified(clusterName, tdName, svcName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSEcsServiceExists("aws_ecs_service.mongo", &service), + resource.TestCheckResourceAttr("aws_ecs_service.mongo", "service_registries.#", "0"), ), }, }, @@ -1750,7 +1752,7 @@ resource "aws_service_discovery_service" "test" { name = "tf-acc-sd-%s" dns_config { namespace_id = "${aws_service_discovery_private_dns_namespace.test.id}" - dns_records { + dns_records { ttl = 5 type = "SRV" } @@ -1763,7 +1765,7 @@ resource "aws_ecs_cluster" "test" { resource "aws_ecs_task_definition" "test" { family = "%s" - network_mode = "awsvpc" + network_mode = "awsvpc" container_definitions = <