diff --git a/aws/provider.go b/aws/provider.go index 703266ed4c3..2a2a7fa8ba0 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -268,6 +268,7 @@ func Provider() terraform.ResourceProvider { "aws_api_gateway_stage": resourceAwsApiGatewayStage(), "aws_api_gateway_usage_plan": resourceAwsApiGatewayUsagePlan(), "aws_api_gateway_usage_plan_key": resourceAwsApiGatewayUsagePlanKey(), + "aws_api_gateway_vpc_link": resourceAwsApiGatewayVpcLink(), "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), "aws_appautoscaling_target": resourceAwsAppautoscalingTarget(), "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), diff --git a/aws/resource_aws_api_gateway_vpc_link.go b/aws/resource_aws_api_gateway_vpc_link.go new file mode 100644 index 00000000000..8911e5dcbba --- /dev/null +++ b/aws/resource_aws_api_gateway_vpc_link.go @@ -0,0 +1,205 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsApiGatewayVpcLink() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsApiGatewayVpcLinkCreate, + Read: resourceAwsApiGatewayVpcLinkRead, + Update: resourceAwsApiGatewayVpcLinkUpdate, + Delete: resourceAwsApiGatewayVpcLinkDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "target_arns": { + Type: schema.TypeSet, + MaxItems: 1, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceAwsApiGatewayVpcLinkCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + input := &apigateway.CreateVpcLinkInput{ + Name: aws.String(d.Get("name").(string)), + TargetArns: expandStringList(d.Get("target_arns").(*schema.Set).List()), + } + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + resp, err := conn.CreateVpcLink(input) + if err != nil { + return err + } + + d.SetId(*resp.Id) + + stateConf := &resource.StateChangeConf{ + Pending: []string{apigateway.VpcLinkStatusPending}, + Target: []string{apigateway.VpcLinkStatusAvailable}, + Refresh: apigatewayVpcLinkRefreshStatusFunc(conn, *resp.Id), + Timeout: 8 * time.Minute, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + d.SetId("") + return fmt.Errorf("[WARN] Error waiting for APIGateway Vpc Link status to be \"%s\": %s", apigateway.VpcLinkStatusAvailable, err) + } + + return nil +} + +func resourceAwsApiGatewayVpcLinkRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + input := &apigateway.GetVpcLinkInput{ + VpcLinkId: aws.String(d.Id()), + } + + resp, err := conn.GetVpcLink(input) + if err != nil { + if isAWSErr(err, apigateway.ErrCodeNotFoundException, "") { + log.Printf("[WARN] VPC Link %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + return err + } + + d.Set("name", resp.Name) + d.Set("description", resp.Description) + d.Set("target_arn", flattenStringList(resp.TargetArns)) + return nil +} + +func resourceAwsApiGatewayVpcLinkUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + operations := make([]*apigateway.PatchOperation, 0) + + if d.HasChange("name") { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/name"), + Value: aws.String(d.Get("name").(string)), + }) + } + + if d.HasChange("description") { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/description"), + Value: aws.String(d.Get("description").(string)), + }) + } + + input := &apigateway.UpdateVpcLinkInput{ + VpcLinkId: aws.String(d.Id()), + PatchOperations: operations, + } + + _, err := conn.UpdateVpcLink(input) + if err != nil { + if isAWSErr(err, apigateway.ErrCodeNotFoundException, "") { + log.Printf("[WARN] VPC Link %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{apigateway.VpcLinkStatusPending}, + Target: []string{apigateway.VpcLinkStatusAvailable}, + Refresh: apigatewayVpcLinkRefreshStatusFunc(conn, d.Id()), + Timeout: 8 * time.Minute, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("[WARN] Error waiting for APIGateway Vpc Link status to be \"%s\": %s", apigateway.VpcLinkStatusAvailable, err) + } + + return nil +} + +func resourceAwsApiGatewayVpcLinkDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + input := &apigateway.DeleteVpcLinkInput{ + VpcLinkId: aws.String(d.Id()), + } + + _, err := conn.DeleteVpcLink(input) + if err != nil { + if isAWSErr(err, apigateway.ErrCodeNotFoundException, "") { + return nil + } + return err + } + + stateConf := resource.StateChangeConf{ + Pending: []string{apigateway.VpcLinkStatusPending, + apigateway.VpcLinkStatusAvailable, + apigateway.VpcLinkStatusDeleting}, + Target: []string{""}, + Timeout: 5 * time.Minute, + MinTimeout: 1 * time.Second, + Refresh: func() (interface{}, string, error) { + resp, err := conn.GetVpcLink(&apigateway.GetVpcLinkInput{ + VpcLinkId: aws.String(d.Id()), + }) + if err != nil { + if isAWSErr(err, apigateway.ErrCodeNotFoundException, "") { + return 1, "", nil + } + return nil, "failed", err + } + return resp, *resp.Status, nil + }, + } + + if _, err := stateConf.WaitForState(); err != nil { + return err + } + + return nil +} + +func apigatewayVpcLinkRefreshStatusFunc(conn *apigateway.APIGateway, vl string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &apigateway.GetVpcLinkInput{ + VpcLinkId: aws.String(vl), + } + resp, err := conn.GetVpcLink(input) + if err != nil { + return nil, "failed", err + } + return resp, *resp.Status, nil + } +} diff --git a/aws/resource_aws_api_gateway_vpc_link_test.go b/aws/resource_aws_api_gateway_vpc_link_test.go new file mode 100644 index 00000000000..d1a03805b62 --- /dev/null +++ b/aws/resource_aws_api_gateway_vpc_link_test.go @@ -0,0 +1,132 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAwsAPIGatewayVpcLink_basic(t *testing.T) { + rName := acctest.RandString(5) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAPIGatewayVpcLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAPIGatewayVpcLinkConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAPIGatewayVpcLinkExists("aws_api_gateway_vpc_link.test"), + resource.TestCheckResourceAttr("aws_api_gateway_vpc_link.test", "name", fmt.Sprintf("tf-apigateway-%s", rName)), + resource.TestCheckResourceAttr("aws_api_gateway_vpc_link.test", "description", "test"), + resource.TestCheckResourceAttr("aws_api_gateway_vpc_link.test", "target_arns.#", "1"), + ), + }, + { + Config: testAccAPIGatewayVpcLinkConfig_Update(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAPIGatewayVpcLinkExists("aws_api_gateway_vpc_link.test"), + resource.TestCheckResourceAttr("aws_api_gateway_vpc_link.test", "name", fmt.Sprintf("tf-apigateway-update-%s", rName)), + resource.TestCheckResourceAttr("aws_api_gateway_vpc_link.test", "description", "test update"), + resource.TestCheckResourceAttr("aws_api_gateway_vpc_link.test", "target_arns.#", "1"), + ), + }, + }, + }) +} + +func testAccCheckAwsAPIGatewayVpcLinkDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigateway + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_api_gateway_vpc_link" { + continue + } + + input := &apigateway.GetVpcLinkInput{ + VpcLinkId: aws.String(rs.Primary.ID), + } + + _, err := conn.GetVpcLink(input) + if err != nil { + if isAWSErr(err, apigateway.ErrCodeNotFoundException, "") { + return nil + } + return err + } + + return fmt.Errorf("Expected VPC Link to be destroyed, %s found", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAwsAPIGatewayVpcLinkExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + conn := testAccProvider.Meta().(*AWSClient).apigateway + + input := &apigateway.GetVpcLinkInput{ + VpcLinkId: aws.String(rs.Primary.ID), + } + + _, err := conn.GetVpcLink(input) + if err != nil { + return err + } + + return nil + } +} + +func testAccAPIGatewayVpcLinkConfig_basis(rName string) string { + return fmt.Sprintf(` +resource "aws_lb" "test_a" { + name = "tf-lb-%s" + internal = true + load_balancer_type = "network" + subnets = ["${aws_subnet.test.id}"] +} + +resource "aws_vpc" "test" { + cidr_block = "10.10.0.0/16" +} + +data "aws_availability_zones" "test" {} + +resource "aws_subnet" "test" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.10.0.0/21" + availability_zone = "${data.aws_availability_zones.test.names[0]}" +} +`, rName) +} + +func testAccAPIGatewayVpcLinkConfig(rName string) string { + return testAccAPIGatewayVpcLinkConfig_basis(rName) + fmt.Sprintf(` +resource "aws_api_gateway_vpc_link" "test" { + name = "tf-apigateway-%s" + description = "test" + target_arns = ["${aws_lb.test_a.arn}"] +} +`, rName) +} + +func testAccAPIGatewayVpcLinkConfig_Update(rName string) string { + return testAccAPIGatewayVpcLinkConfig_basis(rName) + fmt.Sprintf(` +resource "aws_api_gateway_vpc_link" "test" { + name = "tf-apigateway-update-%s" + description = "test update" + target_arns = ["${aws_lb.test_a.arn}"] +} +`, rName) +} diff --git a/website/aws.erb b/website/aws.erb index 11370ca5695..baed247e9be 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -331,6 +331,9 @@