From 848f612169641a5d76b63755032cad2d7b91d95f Mon Sep 17 00:00:00 2001 From: Ryan Roberts Date: Sun, 21 Aug 2016 13:41:59 -0700 Subject: [PATCH] provider/aws: aws_api_gateway_base_path_mapping resource implementation API Gateway allows users to "claim" a domain name for use as a custom hostname for deployed API endpoints, and then use this base path mapping resource to expose a particular API deployment at a path on such a domain. The acceptance tests use certificates from the aws_api_gateway_domain_name tests which expire in 2026; we'll need to generate some more certificates before we get there. --- builtin/providers/aws/provider.go | 1 + ...ource_aws_api_gateway_base_path_mapping.go | 128 ++++++++++++++++ ..._aws_api_gateway_base_path_mapping_test.go | 138 ++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_api_gateway_base_path_mapping.go create mode 100644 builtin/providers/aws/resource_aws_api_gateway_base_path_mapping_test.go diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 593f4996c..c3049a173 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -164,6 +164,7 @@ func Provider() terraform.ResourceProvider { "aws_api_gateway_account": resourceAwsApiGatewayAccount(), "aws_api_gateway_api_key": resourceAwsApiGatewayApiKey(), "aws_api_gateway_authorizer": resourceAwsApiGatewayAuthorizer(), + "aws_api_gateway_base_path_mapping": resourceAwsApiGatewayBasePathMapping(), "aws_api_gateway_deployment": resourceAwsApiGatewayDeployment(), "aws_api_gateway_domain_name": resourceAwsApiGatewayDomainName(), "aws_api_gateway_integration": resourceAwsApiGatewayIntegration(), diff --git a/builtin/providers/aws/resource_aws_api_gateway_base_path_mapping.go b/builtin/providers/aws/resource_aws_api_gateway_base_path_mapping.go new file mode 100644 index 000000000..964a3b8ed --- /dev/null +++ b/builtin/providers/aws/resource_aws_api_gateway_base_path_mapping.go @@ -0,0 +1,128 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsApiGatewayBasePathMapping() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsApiGatewayBasePathMappingCreate, + Read: resourceAwsApiGatewayBasePathMappingRead, + Delete: resourceAwsApiGatewayBasePathMappingDelete, + + Schema: map[string]*schema.Schema{ + "api_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "base_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "stage_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "domain_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsApiGatewayBasePathMappingCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + err := resource.Retry(30*time.Second, func() *resource.RetryError { + _, err := conn.CreateBasePathMapping(&apigateway.CreateBasePathMappingInput{ + RestApiId: aws.String(d.Get("api_id").(string)), + DomainName: aws.String(d.Get("domain_name").(string)), + BasePath: aws.String(d.Get("base_path").(string)), + Stage: aws.String(d.Get("stage_name").(string)), + }) + + if err != nil { + if err, ok := err.(awserr.Error); ok && err.Code() != "BadRequestException" { + return resource.NonRetryableError(err) + } + + return resource.RetryableError( + fmt.Errorf("Error creating Gateway base path mapping: %s", err), + ) + } + + return nil + }) + + if err != nil { + return fmt.Errorf("Error creating Gateway base path mapping: %s", err) + } + + id := fmt.Sprintf("%s/%s", d.Get("domain_name").(string), d.Get("base_path").(string)) + d.SetId(id) + + return resourceAwsApiGatewayBasePathMappingRead(d, meta) +} + +func resourceAwsApiGatewayBasePathMappingRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + domainName := d.Get("domain_name").(string) + basePath := d.Get("base_path").(string) + + if domainName == "" { + return nil + } + + mapping, err := conn.GetBasePathMapping(&apigateway.GetBasePathMappingInput{ + DomainName: aws.String(domainName), + BasePath: aws.String(basePath), + }) + if err != nil { + if err, ok := err.(awserr.Error); ok && err.Code() == "NotFoundException" { + log.Printf("[WARN] API gateway base path mapping %s has vanished\n", d.Id()) + d.SetId("") + return nil + } + + return fmt.Errorf("Error reading Gateway base path mapping: %s", err) + } + + d.Set("base_path", *mapping.BasePath) + d.Set("api_id", *mapping.RestApiId) + d.Set("stage_name", *mapping.Stage) + + return nil +} + +func resourceAwsApiGatewayBasePathMappingDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + + _, err := conn.DeleteBasePathMapping(&apigateway.DeleteBasePathMappingInput{ + DomainName: aws.String(d.Get("domain_name").(string)), + BasePath: aws.String(d.Get("base_path").(string)), + }) + + if err != nil { + if err, ok := err.(awserr.Error); ok && err.Code() == "NotFoundException" { + return nil + } + + return err + } + + return nil +} diff --git a/builtin/providers/aws/resource_aws_api_gateway_base_path_mapping_test.go b/builtin/providers/aws/resource_aws_api_gateway_base_path_mapping_test.go new file mode 100644 index 000000000..d6fd87bf1 --- /dev/null +++ b/builtin/providers/aws/resource_aws_api_gateway_base_path_mapping_test.go @@ -0,0 +1,138 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSAPIGatewayBasePath_basic(t *testing.T) { + var conf apigateway.BasePathMapping + + // Our test cert is for a wildcard on this domain + name := fmt.Sprintf("%s.tf-acc.invalid", resource.UniqueId()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayBasePathDestroy(name), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSAPIGatewayBasePathConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayBasePathExists("aws_api_gateway_base_path_mapping.test", name, &conf), + ), + }, + }, + }) +} + +func testAccCheckAWSAPIGatewayBasePathExists(n string, name string, res *apigateway.BasePathMapping) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No API Gateway ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apigateway + + req := &apigateway.GetBasePathMappingInput{ + DomainName: aws.String(name), + BasePath: aws.String("tf-acc"), + } + describe, err := conn.GetBasePathMapping(req) + if err != nil { + return err + } + + if *describe.BasePath != "tf-acc" { + return fmt.Errorf("base path mapping not found") + } + + *res = *describe + + return nil + } +} + +func testAccCheckAWSAPIGatewayBasePathDestroy(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigateway + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_api_gateway_rest_api" { + continue + } + + req := &apigateway.GetBasePathMappingsInput{ + DomainName: aws.String(name), + } + _, err := conn.GetBasePathMappings(req) + + if err != nil { + if err, ok := err.(awserr.Error); ok && err.Code() == "NotFoundException" { + return nil + } + return err + } + + return fmt.Errorf("expected error reading deleted base path, but got success") + } + + return nil + } +} + +func testAccAWSAPIGatewayBasePathConfig(name string) string { + return fmt.Sprintf(` +resource "aws_api_gateway_rest_api" "test" { + name = "tf-acc-apigateway-base-path-mapping" + description = "Terraform Acceptance Tests" +} +# API gateway won't let us deploy an empty API +resource "aws_api_gateway_resource" "test" { + rest_api_id = "${aws_api_gateway_rest_api.test.id}" + parent_id = "${aws_api_gateway_rest_api.test.root_resource_id}" + path_part = "tf-acc" +} +resource "aws_api_gateway_method" "test" { + rest_api_id = "${aws_api_gateway_rest_api.test.id}" + resource_id = "${aws_api_gateway_resource.test.id}" + http_method = "GET" + authorization = "NONE" +} +resource "aws_api_gateway_integration" "test" { + rest_api_id = "${aws_api_gateway_rest_api.test.id}" + resource_id = "${aws_api_gateway_resource.test.id}" + http_method = "${aws_api_gateway_method.test.http_method}" + type = "MOCK" +} +resource "aws_api_gateway_deployment" "test" { + rest_api_id = "${aws_api_gateway_rest_api.test.id}" + stage_name = "test" + depends_on = ["aws_api_gateway_integration.test"] +} +resource "aws_api_gateway_base_path_mapping" "test" { + api_id = "${aws_api_gateway_rest_api.test.id}" + base_path = "tf-acc" + stage_name = "${aws_api_gateway_deployment.test.stage_name}" + domain_name = "${aws_api_gateway_domain_name.test.domain_name}" +} +resource "aws_api_gateway_domain_name" "test" { + domain_name = "%s" + certificate_name = "tf-apigateway-base-path-mapping-test" + certificate_body = "%s" + certificate_chain = "%s" + certificate_private_key = "%s" +} +`, name, testAccAWSAPIGatewayCertBody, testAccAWSAPIGatewayCertChain, testAccAWSAPIGatewayCertPrivateKey) +}