Skip to content

Commit

Permalink
Merge pull request #3240 from terraform-providers/f-aws_ecs_service-a…
Browse files Browse the repository at this point in the history
…ssign_public_ip

resource/aws_ecs_service: Add public_assign_ip attribute and fix InvalidParameterException handling
  • Loading branch information
bflad authored Feb 9, 2018
2 parents f0fc3f0 + 7551f92 commit ac4c5c1
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 62 deletions.
108 changes: 58 additions & 50 deletions aws/resource_aws_ecs_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
Expand Down Expand Up @@ -136,6 +135,11 @@ func resourceAwsEcsService() *schema.Resource {
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"assign_public_ip": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
},
},
Expand Down Expand Up @@ -259,7 +263,7 @@ func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error
input.Role = aws.String(v.(string))
}

input.NetworkConfiguration = expandEcsNetworkConfigration(d.Get("network_configuration").([]interface{}))
input.NetworkConfiguration = expandEcsNetworkConfiguration(d.Get("network_configuration").([]interface{}))

strategies := d.Get("placement_strategy").(*schema.Set).List()
if len(strategies) > 0 {
Expand Down Expand Up @@ -310,21 +314,12 @@ func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error
out, err = conn.CreateService(&input)

if err != nil {
awsErr, ok := err.(awserr.Error)
if !ok {
return resource.NonRetryableError(err)
}
if awsErr.Code() == "InvalidParameterException" {
log.Printf("[DEBUG] Trying to create ECS service again: %q",
awsErr.Message())
if isAWSErr(err, ecs.ErrCodeClusterNotFoundException, "") {
return resource.RetryableError(err)
}
if awsErr.Code() == "ClusterNotFoundException" {
log.Printf("[DEBUG] Trying to create ECS service again: %q",
awsErr.Message())
if isAWSErr(err, ecs.ErrCodeInvalidParameterException, "Please verify that the ECS service role being passed has the proper permissions.") {
return resource.RetryableError(err)
}

return resource.NonRetryableError(err)
}

Expand All @@ -339,7 +334,7 @@ func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error
log.Printf("[DEBUG] ECS service created: %s", *service.ServiceArn)
d.SetId(*service.ServiceArn)

return resourceAwsEcsServiceUpdate(d, meta)
return resourceAwsEcsServiceRead(d, meta)
}

func resourceAwsEcsServiceRead(d *schema.ResourceData, meta interface{}) error {
Expand All @@ -351,7 +346,18 @@ func resourceAwsEcsServiceRead(d *schema.ResourceData, meta interface{}) error {
Cluster: aws.String(d.Get("cluster").(string)),
}

out, err := conn.DescribeServices(&input)
var out *ecs.DescribeServicesOutput
err := resource.Retry(2*time.Minute, func() *resource.RetryError {
var err error
out, err = conn.DescribeServices(&input)
if err != nil {
if d.IsNewResource() && isAWSErr(err, ecs.ErrCodeServiceNotFoundException, "") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})
if err != nil {
return err
}
Expand Down Expand Up @@ -422,24 +428,30 @@ func resourceAwsEcsServiceRead(d *schema.ResourceData, meta interface{}) error {
log.Printf("[ERR] Error setting placement_constraints for (%s): %s", d.Id(), err)
}

if err := d.Set("network_configuration", flattenEcsNetworkConfigration(service.NetworkConfiguration)); err != nil {
if err := d.Set("network_configuration", flattenEcsNetworkConfiguration(service.NetworkConfiguration)); err != nil {
return fmt.Errorf("[ERR] Error setting network_configuration for (%s): %s", d.Id(), err)
}

return nil
}

func flattenEcsNetworkConfigration(nc *ecs.NetworkConfiguration) []interface{} {
func flattenEcsNetworkConfiguration(nc *ecs.NetworkConfiguration) []interface{} {
if nc == nil {
return nil
}

result := make(map[string]interface{})
result["security_groups"] = schema.NewSet(schema.HashString, flattenStringList(nc.AwsvpcConfiguration.SecurityGroups))
result["subnets"] = schema.NewSet(schema.HashString, flattenStringList(nc.AwsvpcConfiguration.Subnets))

if nc.AwsvpcConfiguration.AssignPublicIp != nil {
result["assign_public_ip"] = *nc.AwsvpcConfiguration.AssignPublicIp == ecs.AssignPublicIpEnabled
}

return []interface{}{result}
}

func expandEcsNetworkConfigration(nc []interface{}) *ecs.NetworkConfiguration {
func expandEcsNetworkConfiguration(nc []interface{}) *ecs.NetworkConfiguration {
if len(nc) == 0 {
return nil
}
Expand All @@ -449,6 +461,13 @@ func expandEcsNetworkConfigration(nc []interface{}) *ecs.NetworkConfiguration {
awsVpcConfig.SecurityGroups = expandStringSet(val.(*schema.Set))
}
awsVpcConfig.Subnets = expandStringSet(raw["subnets"].(*schema.Set))
if val, ok := raw["assign_public_ip"].(bool); ok {
awsVpcConfig.AssignPublicIp = aws.String(ecs.AssignPublicIpDisabled)
if val {
awsVpcConfig.AssignPublicIp = aws.String(ecs.AssignPublicIpEnabled)
}
}

return &ecs.NetworkConfiguration{AwsvpcConfiguration: awsVpcConfig}
}

Expand Down Expand Up @@ -518,24 +537,17 @@ func resourceAwsEcsServiceUpdate(d *schema.ResourceData, meta interface{}) error
}
}

if d.HasChange("network_configration") {
input.NetworkConfiguration = expandEcsNetworkConfigration(d.Get("network_configuration").([]interface{}))
if d.HasChange("network_configuration") {
input.NetworkConfiguration = expandEcsNetworkConfiguration(d.Get("network_configuration").([]interface{}))
}

// Retry due to IAM & ECS eventual consistency
// Retry due to IAM eventual consistency
err := resource.Retry(2*time.Minute, func() *resource.RetryError {
out, err := conn.UpdateService(&input)
if err != nil {
awsErr, ok := err.(awserr.Error)
if ok && awsErr.Code() == "InvalidParameterException" {
log.Printf("[DEBUG] Trying to update ECS service again: %#v", err)
return resource.RetryableError(err)
}
if ok && awsErr.Code() == "ServiceNotFoundException" {
log.Printf("[DEBUG] Trying to update ECS service again: %#v", err)
if isAWSErr(err, ecs.ErrCodeInvalidParameterException, "Please verify that the ECS service role being passed has the proper permissions.") {
return resource.RetryableError(err)
}

return resource.NonRetryableError(err)
}

Expand All @@ -558,11 +570,17 @@ func resourceAwsEcsServiceDelete(d *schema.ResourceData, meta interface{}) error
Cluster: aws.String(d.Get("cluster").(string)),
})
if err != nil {
if isAWSErr(err, ecs.ErrCodeServiceNotFoundException, "") {
log.Printf("[DEBUG] Removing ECS Service from state, %q is already gone", d.Id())
d.SetId("")
return nil
}
return err
}

if len(resp.Services) == 0 {
log.Printf("[DEBUG] ECS Service %q is already gone", d.Id())
log.Printf("[DEBUG] Removing ECS Service from state, %q is already gone", d.Id())
d.SetId("")
return nil
}

Expand All @@ -585,33 +603,23 @@ func resourceAwsEcsServiceDelete(d *schema.ResourceData, meta interface{}) error
}
}

input := ecs.DeleteServiceInput{
Service: aws.String(d.Id()),
Cluster: aws.String(d.Get("cluster").(string)),
}
// Wait until the ECS service is drained
err = resource.Retry(5*time.Minute, func() *resource.RetryError {
input := ecs.DeleteServiceInput{
Service: aws.String(d.Id()),
Cluster: aws.String(d.Get("cluster").(string)),
}

log.Printf("[DEBUG] Trying to delete ECS service %s", input)
_, err := conn.DeleteService(&input)
if err == nil {
return nil
}

ec2err, ok := err.(awserr.Error)
if !ok {
if err != nil {
if isAWSErr(err, ecs.ErrCodeInvalidParameterException, "The service cannot be stopped while deployments are active.") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
if ec2err.Code() == "InvalidParameterException" {
// Prevent "The service cannot be stopped while deployments are active."
log.Printf("[DEBUG] Trying to delete ECS service again: %q",
ec2err.Message())
return resource.RetryableError(err)
}

return resource.NonRetryableError(err)

return nil
})

if err != nil {
return err
}
Expand Down
73 changes: 65 additions & 8 deletions aws/resource_aws_ecs_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,17 +504,48 @@ func TestAccAWSEcsService_withLaunchTypeFargate(t *testing.T) {
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsServiceWithLaunchTypeFargate(sg1Name, sg2Name, clusterName, tdName, svcName),
Config: testAccAWSEcsServiceWithLaunchTypeFargate(sg1Name, sg2Name, clusterName, tdName, svcName, "false"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.main"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "launch_type", "FARGATE"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.assign_public_ip", "false"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.security_groups.#", "2"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.subnets.#", "2"),
),
},
},
})
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsServiceWithLaunchTypeFargate(sg1Name, sg2Name, clusterName, tdName, svcName, "true"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.main"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.assign_public_ip", "true"),
),
},
},
})
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsServiceWithLaunchTypeFargate(sg1Name, sg2Name, clusterName, tdName, svcName, "false"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.main"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.assign_public_ip", "false"),
),
},
},
})
}

func TestAccAWSEcsService_withNetworkConfiguration(t *testing.T) {
func TestAccAWSEcsService_withLaunchTypeEC2AndNetworkConfiguration(t *testing.T) {
rString := acctest.RandString(8)

sg1Name := fmt.Sprintf("tf-acc-sg-1-svc-w-nc-%s", rString)
Expand All @@ -529,9 +560,21 @@ func TestAccAWSEcsService_withNetworkConfiguration(t *testing.T) {
CheckDestroy: testAccCheckAWSEcsServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEcsServiceWithNetworkConfigration(sg1Name, sg2Name, clusterName, tdName, svcName),
Config: testAccAWSEcsServiceWithNetworkConfiguration(sg1Name, sg2Name, clusterName, tdName, svcName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.main"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.assign_public_ip", "false"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.security_groups.#", "2"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.subnets.#", "2"),
),
},
{
Config: testAccAWSEcsServiceWithNetworkConfiguration_modified(sg1Name, sg2Name, clusterName, tdName, svcName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEcsServiceExists("aws_ecs_service.main"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.assign_public_ip", "false"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.security_groups.#", "1"),
resource.TestCheckResourceAttr("aws_ecs_service.main", "network_configuration.0.subnets.#", "2"),
),
},
},
Expand Down Expand Up @@ -778,7 +821,7 @@ resource "aws_ecs_service" "mongo" {
`, clusterName, tdName, svcName)
}

func testAccAWSEcsServiceWithLaunchTypeFargate(sg1Name, sg2Name, clusterName, tdName, svcName string) string {
func testAccAWSEcsServiceWithLaunchTypeFargate(sg1Name, sg2Name, clusterName, tdName, svcName, assignPublicIP string) string {
return fmt.Sprintf(`
provider "aws" {
region = "us-east-1"
Expand Down Expand Up @@ -856,9 +899,10 @@ resource "aws_ecs_service" "main" {
network_configuration {
security_groups = ["${aws_security_group.allow_all_a.id}", "${aws_security_group.allow_all_b.id}"]
subnets = ["${aws_subnet.main.*.id}"]
assign_public_ip = %s
}
}
`, sg1Name, sg2Name, clusterName, tdName, svcName)
`, sg1Name, sg2Name, clusterName, tdName, svcName, assignPublicIP)
}

func testAccAWSEcsService_healthCheckGracePeriodSeconds(vpcNameTag, clusterName, tdName, roleName, policyName,
Expand Down Expand Up @@ -1476,7 +1520,20 @@ resource "aws_ecs_service" "with_alb" {
`, clusterName, tdName, roleName, policyName, tgName, lbName, svcName)
}

func testAccAWSEcsServiceWithNetworkConfigration(sg1Name, sg2Name, clusterName, tdName, svcName string) string {
func testAccAWSEcsServiceWithNetworkConfiguration(sg1Name, sg2Name, clusterName, tdName, svcName string) string {
return tpl_testAccAWSEcsServiceWithNetworkConfiguration(
sg1Name, sg2Name, clusterName, tdName, svcName,
`"${aws_security_group.allow_all_a.id}", "${aws_security_group.allow_all_b.id}"`,
)
}
func testAccAWSEcsServiceWithNetworkConfiguration_modified(sg1Name, sg2Name, clusterName, tdName, svcName string) string {
return tpl_testAccAWSEcsServiceWithNetworkConfiguration(
sg1Name, sg2Name, clusterName, tdName, svcName,
`"${aws_security_group.allow_all_a.id}"`,
)
}

func tpl_testAccAWSEcsServiceWithNetworkConfiguration(sg1Name, sg2Name, clusterName, tdName, svcName string, securityGroups string) string {
return fmt.Sprintf(`
data "aws_availability_zones" "available" {}
Expand Down Expand Up @@ -1543,9 +1600,9 @@ resource "aws_ecs_service" "main" {
task_definition = "${aws_ecs_task_definition.mongo.arn}"
desired_count = 1
network_configuration {
security_groups = ["${aws_security_group.allow_all_a.id}", "${aws_security_group.allow_all_b.id}"]
security_groups = [%s]
subnets = ["${aws_subnet.main.*.id}"]
}
}
`, sg1Name, sg2Name, clusterName, tdName, svcName)
`, sg1Name, sg2Name, clusterName, tdName, svcName, securityGroups)
}
4 changes: 2 additions & 2 deletions aws/resource_aws_security_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ func TestAccAWSSecurityGroup_forceRevokeRules_true(t *testing.T) {
testAddCycle,
),
},
// Verify the DependencyViolation error by using a configration with the
// Verify the DependencyViolation error by using a configuration with the
// groups removed. Terraform tries to destroy them but cannot. Expect a
// DependencyViolation error
{
Expand Down Expand Up @@ -579,7 +579,7 @@ func TestAccAWSSecurityGroup_forceRevokeRules_false(t *testing.T) {
testAddCycle,
),
},
// Verify the DependencyViolation error by using a configration with the
// Verify the DependencyViolation error by using a configuration with the
// groups removed, and the Groups not configured to revoke their ruls.
// Terraform tries to destroy them but cannot. Expect a
// DependencyViolation error
Expand Down
1 change: 0 additions & 1 deletion examples/networking/region/numbering.tf

This file was deleted.

1 change: 0 additions & 1 deletion examples/networking/subnet/numbering.tf

This file was deleted.

2 changes: 2 additions & 0 deletions website/docs/r/ecs_service.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ Guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query

* `subnets` - (Required) The subnets associated with the task or service.
* `security_groups` - (Optional) The security groups associated with the task or service. If you do not specify a security group, the default security group for the VPC is used.
* `assign_public_ip` - (Optional) Assign a public IP address to the ENI (Fargate launch type only). Valid values are `true` or `false`. Default `false`.

For more information, see [Task Networking](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html)

## Attributes Reference
Expand Down

0 comments on commit ac4c5c1

Please sign in to comment.