diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0f7be99..93a7f25b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 0.7.2 (Unreleased) +FEATURES: + * **New Resources:** `aws_api_gateway_domain_name` and `aws_api_gateway_base_path_mapping`, for using custom domains in AWS API Gateway [GH-8353] + BUG FIXES: * core: fix crash case when malformed JSON given [GH-8295] * core: when asking for input, spaces are allowed [GH-8394] diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 35c082c84..c3049a173 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -164,7 +164,9 @@ 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(), "aws_api_gateway_integration_response": resourceAwsApiGatewayIntegrationResponse(), "aws_api_gateway_method": resourceAwsApiGatewayMethod(), 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) +} diff --git a/builtin/providers/aws/resource_aws_api_gateway_domain_name.go b/builtin/providers/aws/resource_aws_api_gateway_domain_name.go new file mode 100644 index 000000000..0d4be86dc --- /dev/null +++ b/builtin/providers/aws/resource_aws_api_gateway_domain_name.go @@ -0,0 +1,182 @@ +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 resourceAwsApiGatewayDomainName() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsApiGatewayDomainNameCreate, + Read: resourceAwsApiGatewayDomainNameRead, + Update: resourceAwsApiGatewayDomainNameUpdate, + Delete: resourceAwsApiGatewayDomainNameDelete, + + Schema: map[string]*schema.Schema{ + + "certificate_body": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "certificate_chain": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "certificate_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "certificate_private_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "domain_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "cloudfront_domain_name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "cloudfront_zone_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsApiGatewayDomainNameCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + log.Printf("[DEBUG] Creating API Gateway Domain Name") + + domainName, err := conn.CreateDomainName(&apigateway.CreateDomainNameInput{ + CertificateBody: aws.String(d.Get("certificate_body").(string)), + CertificateChain: aws.String(d.Get("certificate_chain").(string)), + CertificateName: aws.String(d.Get("certificate_name").(string)), + CertificatePrivateKey: aws.String(d.Get("certificate_private_key").(string)), + DomainName: aws.String(d.Get("domain_name").(string)), + }) + if err != nil { + return fmt.Errorf("Error creating API Gateway Domain Name: %s", err) + } + + d.SetId(*domainName.DomainName) + d.Set("cloudfront_domain_name", *domainName.DistributionDomainName) + d.Set("cloudfront_zone_id", cloudFrontRoute53ZoneID) + + return resourceAwsApiGatewayDomainNameRead(d, meta) +} + +func resourceAwsApiGatewayDomainNameRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + log.Printf("[DEBUG] Reading API Gateway Domain Name %s", d.Id()) + + domainName, err := conn.GetDomainName(&apigateway.GetDomainNameInput{ + DomainName: aws.String(d.Id()), + }) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NotFoundException" { + log.Printf("[WARN] API gateway domain name %s has vanished\n", d.Id()) + d.SetId("") + return nil + } + + return err + } + + d.Set("certificate_name", domainName.CertificateName) + d.Set("certificate_upload_date", domainName.CertificateUploadDate) + d.Set("cloudfront_domain_name", domainName.DistributionDomainName) + d.Set("domain_name", domainName.DomainName) + + return nil +} + +func resourceAwsApiGatewayDomainNameUpdateOperations(d *schema.ResourceData) []*apigateway.PatchOperation { + operations := make([]*apigateway.PatchOperation, 0) + + if d.HasChange("certificate_body") { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/certificate_body"), + Value: aws.String(d.Get("certificate_body").(string)), + }) + } + + if d.HasChange("certificate_chain") { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/certificate_chain"), + Value: aws.String(d.Get("certificate_chain").(string)), + }) + } + + if d.HasChange("certificate_name") { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/certificate_name"), + Value: aws.String(d.Get("certificate_name").(string)), + }) + } + + if d.HasChange("certificate_private_key") { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/certificate_private_key"), + Value: aws.String(d.Get("certificate_private_key").(string)), + }) + } + + return operations +} + +func resourceAwsApiGatewayDomainNameUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + log.Printf("[DEBUG] Updating API Gateway Domain Name %s", d.Id()) + + _, err := conn.UpdateDomainName(&apigateway.UpdateDomainNameInput{ + DomainName: aws.String(d.Id()), + PatchOperations: resourceAwsApiGatewayDomainNameUpdateOperations(d), + }) + if err != nil { + return err + } + + return resourceAwsApiGatewayDomainNameRead(d, meta) +} + +func resourceAwsApiGatewayDomainNameDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigateway + log.Printf("[DEBUG] Deleting API Gateway Domain Name: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() *resource.RetryError { + _, err := conn.DeleteDomainName(&apigateway.DeleteDomainNameInput{ + DomainName: aws.String(d.Id()), + }) + + if err == nil { + return nil + } + + if apigatewayErr, ok := err.(awserr.Error); ok && apigatewayErr.Code() == "NotFoundException" { + return nil + } + + return resource.NonRetryableError(err) + }) +} diff --git a/builtin/providers/aws/resource_aws_api_gateway_domain_name_test.go b/builtin/providers/aws/resource_aws_api_gateway_domain_name_test.go new file mode 100644 index 000000000..76fc3c108 --- /dev/null +++ b/builtin/providers/aws/resource_aws_api_gateway_domain_name_test.go @@ -0,0 +1,211 @@ +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 TestAccAWSAPIGatewayDomainName_basic(t *testing.T) { + var conf apigateway.DomainName + + // 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: testAccCheckAWSAPIGatewayDomainNameDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSAPIGatewayDomainNameConfigCreate(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayDomainNameExists("aws_api_gateway_domain_name.test", &conf), + resource.TestCheckResourceAttr( + "aws_api_gateway_domain_name.test", "certificate_body", testAccAWSAPIGatewayCertBody, + ), + resource.TestCheckResourceAttr( + "aws_api_gateway_domain_name.test", "certificate_chain", testAccAWSAPIGatewayCertChain, + ), + resource.TestCheckResourceAttr( + "aws_api_gateway_domain_name.test", "certificate_name", "tf-acc-apigateway-domain-name", + ), + resource.TestCheckResourceAttr( + "aws_api_gateway_domain_name.test", "certificate_private_key", testAccAWSAPIGatewayCertPrivateKey, + ), + resource.TestCheckResourceAttr( + "aws_api_gateway_domain_name.test", "domain_name", name, + ), + ), + }, + }, + }) +} + +func testAccCheckAWSAPIGatewayDomainNameExists(n string, res *apigateway.DomainName) 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 DomainName ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apigateway + + req := &apigateway.GetDomainNameInput{ + DomainName: aws.String(rs.Primary.ID), + } + describe, err := conn.GetDomainName(req) + if err != nil { + return err + } + + if *describe.DomainName != rs.Primary.ID { + return fmt.Errorf("APIGateway DomainName not found") + } + + *res = *describe + + return nil + } +} + +func testAccCheckAWSAPIGatewayDomainNameDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigateway + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_api_gateway_api_key" { + continue + } + + describe, err := conn.GetDomainNames(&apigateway.GetDomainNamesInput{}) + + if err == nil { + if len(describe.Items) != 0 && + *describe.Items[0].DomainName == rs.Primary.ID { + return fmt.Errorf("API Gateway DomainName still exists") + } + } + + aws2err, ok := err.(awserr.Error) + if !ok { + return err + } + if aws2err.Code() != "NotFoundException" { + return err + } + + return nil + } + + return nil +} + +// Expires on August 20, 2026 +const testAccAWSAPIGatewayCertBody = `-----BEGIN CERTIFICATE----- +MIIEfjCCA2agAwIBAgIRAPHiUbRWC/Ff/reiyzqh3wAwDQYJKoZIhvcNAQELBQAw +gcsxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNUGlyYXRlIEhh +cmJvcjEZMBcGA1UECRMQNTg3OSBDb3R0b24gTGluazETMBEGA1UEERMKOTU1NTkt +MTIyNzEVMBMGA1UEChMMRXhhbXBsZSwgSW5jMSgwJgYDVQQLEx9EZXBhcnRtZW50 +IG9mIFRlcnJhZm9ybSBUZXN0aW5nMRowGAYDVQQDExF0Zi1hY2MtY2EuaW52YWxp +ZDEKMAgGA1UEBRMBMjAeFw0xNjA4MjIxNjQ1NTZaFw0yNjA4MjAxNjQ1NTZaMIHK +MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVBpcmF0ZSBIYXJi +b3IxGTAXBgNVBAkTEDU4NzkgQ290dG9uIExpbmsxEzARBgNVBBETCjk1NTU5LTEy +MjcxFTATBgNVBAoTDEV4YW1wbGUsIEluYzEoMCYGA1UECxMfRGVwYXJ0bWVudCBv +ZiBUZXJyYWZvcm0gVGVzdGluZzEZMBcGA1UEAxMQKi50Zi1hY2MuaW52YWxpZDEK +MAgGA1UEBRMBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL/xTPoz +3tnvkLjthqsMKwWVtE1yi210UnseBYkc2w6iQUXMCsXkw2PZXW6QIW7o1GNjnrE5 +9jkeEJ2ABEYyor+ooaNZmjJ0dvY1tFerMhsOkQudDBz+eaR13UQvMFc42nFWrS5/ +XCInm4HjWblEfw2b/wbGwTPzIN3+zWdt2XTsSzIRUYgjXy/93aLIVhWaYDsP45RM +C48qhJKSsYOVH/PSkgw4AfohDXzn2iltTUBFdGZqBufMW4sPp8G8DIOvt01NsXB2 +QLLVgcMJVegx9xxg50+cf6ILck5Ap/SzkCN7XYk8zWC36/QIkiQ6bMMy0nJtgE0B +jwmwnFF+zF/A4oMCAwEAAaNcMFowDgYDVR0PAQH/BAQDAgWgMBkGA1UdJQQSMBAG +CCsGAQUFBwMBBgRVHSUAMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU/hWQ+UUh +7EiLd0fCsgy1jvSgxkswDQYJKoZIhvcNAQELBQADggEBABnTdSEOqj2+7FTeiSR9 +iPGQ3swhrmk59j8XUszrlm6MvZX9AeV9gLcE9J0PfFaAcZM0pR+XZBmyZLoBxUgU +8Y17yWwAxZo13EwgzTOmebDxm4W+Og4vBxsKS3x/L8D8BKdzBcmVJvggJqTs5Qau +ePetAQcD/Tfbq7vY9fNQJ4XQTlF5sN625miijTQcNg/s/H2Fj3FENR3UaFiQDtLQ +eatV0KGF/ik8XFHPbFm7XUZfUcDxvoaljglJfXyVttU956j892ZakvAHzhylFRyy +/wJnKV7K3zQVCEV0n/dJpXhf7w9PuWHrCp+HZJLMPMtm1IPblVxPNwHhGBG40I+Z +FSg= +-----END CERTIFICATE----- +` + +// Expires on August 20, 2026 +const testAccAWSAPIGatewayCertChain = `-----BEGIN CERTIFICATE----- +MIIEoDCCA4igAwIBAgIQRR1EBw7bN+mMYtXrrvzLdzANBgkqhkiG9w0BAQsFADCB +yzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1QaXJhdGUgSGFy +Ym9yMRkwFwYDVQQJExA1ODc5IENvdHRvbiBMaW5rMRMwEQYDVQQREwo5NTU1OS0x +MjI3MRUwEwYDVQQKEwxFeGFtcGxlLCBJbmMxKDAmBgNVBAsTH0RlcGFydG1lbnQg +b2YgVGVycmFmb3JtIFRlc3RpbmcxGjAYBgNVBAMTEXRmLWFjYy1jYS5pbnZhbGlk +MQowCAYDVQQFEwEyMB4XDTE2MDgyMjE2NDU1NloXDTI2MDgyMDE2NDU1Nlowgcsx +CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNUGlyYXRlIEhhcmJv +cjEZMBcGA1UECRMQNTg3OSBDb3R0b24gTGluazETMBEGA1UEERMKOTU1NTktMTIy +NzEVMBMGA1UEChMMRXhhbXBsZSwgSW5jMSgwJgYDVQQLEx9EZXBhcnRtZW50IG9m +IFRlcnJhZm9ybSBUZXN0aW5nMRowGAYDVQQDExF0Zi1hY2MtY2EuaW52YWxpZDEK +MAgGA1UEBRMBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7k1xdX +HdA0HTajtSPvCwIeNE4HO3IsYn9jnUBvhQ52EJ72GPzlg91AhdVufIncJRjk51iP +n86TO/bg/dx4d+G45aUFw7WmZGTTZjurhnVWCDPeeetItV3PmsWHkwTiBj4GJYW4 +1mJk0ACjmmGEyz24T46Cn87Tljk+ivdCQL/oYTjpC7jc2i5b7ziiwsToWMEljWV1 +cf/4qQNFmFnETbaQW2qcFdjUoO8N1gRNkC9yvOjo6Fc+lETRHn3e926jDSaqfbwJ +hKKBoXciYnddNXgA83GBTdnsRiRoD4zFEhNThmgvy5MmkEEdwKl5QIz6ajo4Xxtd +54XZpt0y/ZVy6BECAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgIEMBkGA1UdJQQSMBAG +CCsGAQUFBwMBBgRVHSUAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP4VkPlF +IexIi3dHwrIMtY70oMZLMB8GA1UdIwQYMBaAFP4VkPlFIexIi3dHwrIMtY70oMZL +MA0GCSqGSIb3DQEBCwUAA4IBAQBlXSOkvE/c8pcI6vijInnAWY+eX2W7njCzEfmP +gkDeW/2A1/6eSM6C/ym+9zEgZ9YLxPfiQKzN8Ej+EziFiZW3ZBHnWcW9rZt36wFA +swF2nIqWa5nkVnAX2aPVfULN1u1BQiv3XZqNNlBk6m9AsgxiZ9jtOY8oQQU3uDYe +bs9ttLMKJApbe08sKmPawVlXHqj6qb85Pg4bzzknA+euRee9KIi87eTlWc/Ht+WN +Huh16WqokePii7e/PAaiTAe7ubsPav29lzmHq2NooXrs+FljXveQJpBqu6wKKTXe +6/5BBsp2474RiZgTbuO2Mbr2nwcoJ9V6iu1M9siCGbbt1nQr +-----END CERTIFICATE----- +` + +const testAccAWSAPIGatewayCertPrivateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAv/FM+jPe2e+QuO2GqwwrBZW0TXKLbXRSex4FiRzbDqJBRcwK +xeTDY9ldbpAhbujUY2OesTn2OR4QnYAERjKiv6iho1maMnR29jW0V6syGw6RC50M +HP55pHXdRC8wVzjacVatLn9cIiebgeNZuUR/DZv/BsbBM/Mg3f7NZ23ZdOxLMhFR +iCNfL/3doshWFZpgOw/jlEwLjyqEkpKxg5Uf89KSDDgB+iENfOfaKW1NQEV0ZmoG +58xbiw+nwbwMg6+3TU2xcHZAstWBwwlV6DH3HGDnT5x/ogtyTkCn9LOQI3tdiTzN +YLfr9AiSJDpswzLScm2ATQGPCbCcUX7MX8DigwIDAQABAoIBAFSRcXQPpJFrDt2b +sajtTItCYVV6MVpBVRHvsUqvDwkMjiu9ccWtPDVjENpk4IYoSWOdAc9eFVEnIPTz +8W4oYzKEjusU0G6Ih92E3fd+cy4epeNzB2JC8L94OswO6oKThxNGuDjzXlmiD88T +p3WMa1pIr/2BVqCX75Q/7qoyaQwtR/anvOes154gRGY70ucsUpEzo3fyfXdCKO/o +y1quQ4mmCS50lPCJ3i8JZH2YmGY+xThnXzk7Q9ipjGrHTjinpX8f9tQB2RRwqlA/ +uQ+/q6cOKUAehLtriXyr2QAoCzdz1Lfm8L5KQ3EdrxUUe9dhs/a7bKw4N5eHjw/x +eBUagsECgYEAyoaIqZ0ur5NQEHuY9Givs6a+ObWLjM18EdfQ7zX7NJRPL3q0jdkm +KW+pn2OzoRxhwgaD6WuJJmj3rd3ledkwxoO6Efwo8x2z1u/T43FsknW/X3wJLLga +fPU2T9lSHRufBQUAhPYkf4dSqUklHJ4YSMafbCkvSXKGZqGVLQfu1iECgYEA8p9s +/5+d0Pcnykau7Hp8a9z9ehHEIF+ce/wb6/6p3fgCP04FLvKqs74p9SrEnyl9uiFx +ZC9P2XhmEwNFGA+qKPaRBo4RoGeAYgCKD0BfdaGfmp6WLhO0pSnl4c06bXnvwqbD +DhhT6UvMs6m/uSufwtfCGO48yYiY1z6DqBjuHCMCgYBWJyrltHbSu8D4cgucFRiB +PPJ5DDCkIhmgYYWA7R7CvEB/OxyppvFj+RtYMYqNg8xWRH1DA7rhOw/5x4ZB8lGc +cRbrZbBp033YdkdV3r9IAoz5aoNgoaSq+Yk0KIeU2FYqRXl2FltqYL+aQgJmjR5Z +fxz8Xvy9qtlfuWcDM/e24QKBgQDGtH8mk+lCfUkfRuh4UJCaHnGSif5grS2R9ZZA +n18rpbThd9qS6reXYgUm/5Hs8KRBzqX5cS4qY4rlw2XRIPMxfU6lWbFh96KToPFx +MD1+L5JxpbRFpGnsYvYdCmHxy03r03wojRAcH7JU6o9U7j936hDTLjqmq7LRhid5 +goFwlQKBgQCGxTWqQu9FJjtmjdM/ARi7CcwWT40NI8ybyebjDtElekILFMUwO+/w +M78Me3d5g/oyeS6qCfyK1f7vBb/WhxLBBegaqMGUWbRoKYD0LH5U3zkqZ9ehnDFB +vhcEnoxPD9HmdFkRduJscyzVNJ50lMuQ3uk7zwiYuS/Z7shH81xzMQ== +-----END RSA PRIVATE KEY----- +` + +func testAccAWSAPIGatewayDomainNameConfigCreate(name string) string { + return fmt.Sprintf(` +resource "aws_api_gateway_domain_name" "test" { + domain_name = "%s" + certificate_body = "%v" + certificate_chain = "%v" + certificate_name = "tf-acc-apigateway-domain-name" + certificate_private_key = "%v" +} +`, name, testAccAWSAPIGatewayCertBody, testAccAWSAPIGatewayCertChain, testAccAWSAPIGatewayCertPrivateKey) +} diff --git a/website/source/docs/providers/aws/r/api_gateway_base_path_mapping.html.markdown b/website/source/docs/providers/aws/r/api_gateway_base_path_mapping.html.markdown new file mode 100644 index 000000000..e56e5cd64 --- /dev/null +++ b/website/source/docs/providers/aws/r/api_gateway_base_path_mapping.html.markdown @@ -0,0 +1,47 @@ +--- +layout: "aws" +page_title: "AWS: aws_api_gateway_base_path_mapping" +sidebar_current: "docs-aws-resource-api-gateway-base-path-mapping" +description: |- + Connects a custom domain with a deployed API +--- + +# aws\_api\_gateway\_base\_path\_mapping + +Connects a custom domain name registered via `aws_api_gateway_domain_name` +with a deployed API so that its methods can be called via the +custom domain name. + +## Example Usage + +``` +resource "aws_api_gateway_deployment" "example" { + # See aws_api_gateway_rest_api_docs for how to create this + rest_api_id = "${aws_api_gateway_rest_api.MyDemoAPI.id}" + stage_name = "live" +} + +resource "aws_api_gateway_domain_name" "example" { + domain_name = "example.com" + + certificate_name = "example-api" + certificate_body = "${file("${path.module}/example.com/example.crt")}" + certificate_chain = "${file("${path.module}/example.com/ca.crt")}" + certificate_private_key = "${file("${path.module}/example.com/example.key")}" +} + +resource "aws_api_gateway_base_path_mapping" "test" { + api_id = "${aws_api_gateway_rest_api.MyDemoAPI.id}" + stage = "${aws_api_gateway_deployment.example.stage_name}" + domain_name = "${aws_api_gateway_domain_name.example.domain_name}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `domain_name` - (Required) The already-registered domain name to connect the API to. +* `api_id` - (Required) The id of the API to connect. +* `stage` - (Optional) The name of a specific deployment stage to expose at the given path. If omitted, callers may select any stage by including its name as a path element after the base path. +* `base_path` - (Optional) Path segment that must be prepended to the path when accessing the API via this mapping. If omitted, the API is exposed at the root of the given domain. diff --git a/website/source/docs/providers/aws/r/api_gateway_domain_name.html.markdown b/website/source/docs/providers/aws/r/api_gateway_domain_name.html.markdown new file mode 100644 index 000000000..27f1354a4 --- /dev/null +++ b/website/source/docs/providers/aws/r/api_gateway_domain_name.html.markdown @@ -0,0 +1,75 @@ +--- +layout: "aws" +page_title: "AWS: aws_api_gateway_domain_name" +sidebar_current: "docs-aws-resource-api-gateway-domain-name" +description: |- + Registers a custom domain name for use with AWS API Gateway. +--- + +# aws\_api\_gateway\_domain\_name + +Registers a custom domain name for use with AWS API Gateway. + +This resource just establishes ownership of and the TLS settings for +a particular domain name. An API can be attached to a particular path +under the registered domain name using +[the `aws_api_gateway_base_path_mapping` resource](api_gateway_base_path_mapping.html). + +Internally API Gateway creates a CloudFront distribution to +route requests on the given hostname. In addition to this resource +it's necessary to create a DNS record corresponding to the +given domain name which is an alias (either Route53 alias or +traditional CNAME) to the Cloudfront domain name exported in the +`cloudfront_domain_name` attribute. + +## Example Usage + +``` +resource "aws_api_gateway_domain_name" "example" { + domain_name = "api.example.com" + + certificate_name = "example-api" + certificate_body = "${file("${path.module}/example.com/example.crt")}" + certificate_chain = "${file("${path.module}/example.com/ca.crt")}" + certificate_private_key = "${file("${path.module}/example.com/example.key")}" +} + +# Example DNS record using Route53. +# Route53 is not specifically required; any DNS host can be used. +resource "aws_route53_record" "example" { + zone_id = "${aws_route53_zone.example.id}" # See aws_route53_zone for how to create this + + name = "${aws_api_gateway_domain_name.example.domain_name}" + type = "A" + + alias { + name = "${aws_api_gateway_domain_name.example.cloudfront_domain_name}" + zone_id = "${aws_api_gateway_domain_name.example.cloudfront_zone_id}" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `domain_name` - (Required) The fully-qualified domain name to register +* `certificate_name` - (Required) The unique name to use when registering this + cert as an IAM server certificate +* `certificate_body` - (Required) The certificate issued for the domain name + being registered, in PEM format +* `certificate_chain` - (Required) The certificate for the CA that issued the + certificate, along with any intermediate CA certificates required to + create an unbroken chain to a certificate trusted by the intended API clients. +* `certificate_private_key` - (Required) The private key associated with the + domain certificate given in `certificate_body`. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The internal id assigned to this domain name by API Gateway. +* `cloudfront_domain_name` - The hostname created by Cloudfront to represent + the distribution that implements this domain name mapping. +* `cloudfront_zone_id` - For convenience, the hosted zone id (`Z2FDTNDATAQYW2`) + that can be used to create a Route53 alias record for the distribution. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 728d652ef..aa7a34a71 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -56,9 +56,15 @@ > aws_api_gateway_authorizer + > + aws_api_gateway_base_path_mapping + > aws_api_gateway_deployment + > + aws_api_gateway_domain_name + > aws_api_gateway_integration