diff --git a/aws/provider.go b/aws/provider.go index b40c5d65e0b3..e1cd6df0b976 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -367,6 +367,7 @@ func Provider() terraform.ResourceProvider { "aws_api_gateway_vpc_link": resourceAwsApiGatewayVpcLink(), "aws_apigatewayv2_api": resourceAwsApiGatewayV2Api(), "aws_apigatewayv2_authorizer": resourceAwsApiGatewayV2Authorizer(), + "aws_apigatewayv2_integration": resourceAwsApiGatewayV2Integration(), "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), "aws_appautoscaling_target": resourceAwsAppautoscalingTarget(), "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), diff --git a/aws/resource_aws_apigatewayv2_integration.go b/aws/resource_aws_apigatewayv2_integration.go new file mode 100644 index 000000000000..eed4e89d9ec4 --- /dev/null +++ b/aws/resource_aws_apigatewayv2_integration.go @@ -0,0 +1,314 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apigatewayv2" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceAwsApiGatewayV2Integration() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsApiGatewayV2IntegrationCreate, + Read: resourceAwsApiGatewayV2IntegrationRead, + Update: resourceAwsApiGatewayV2IntegrationUpdate, + Delete: resourceAwsApiGatewayV2IntegrationDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsApiGatewayV2IntegrationImport, + }, + + Schema: map[string]*schema.Schema{ + "api_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "connection_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "connection_type": { + Type: schema.TypeString, + Optional: true, + Default: apigatewayv2.ConnectionTypeInternet, + ValidateFunc: validation.StringInSlice([]string{ + apigatewayv2.ConnectionTypeInternet, + apigatewayv2.ConnectionTypeVpcLink, + }, false), + }, + "content_handling_strategy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + apigatewayv2.ContentHandlingStrategyConvertToBinary, + apigatewayv2.ContentHandlingStrategyConvertToText, + }, false), + }, + "credentials_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "integration_method": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateHTTPMethod(), + }, + "integration_response_selection_expression": { + Type: schema.TypeString, + Computed: true, + }, + "integration_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + apigatewayv2.IntegrationTypeAws, + apigatewayv2.IntegrationTypeAwsProxy, + apigatewayv2.IntegrationTypeHttp, + apigatewayv2.IntegrationTypeHttpProxy, + apigatewayv2.IntegrationTypeMock, + }, false), + }, + "integration_uri": { + Type: schema.TypeString, + Optional: true, + }, + "passthrough_behavior": { + Type: schema.TypeString, + Optional: true, + Default: apigatewayv2.PassthroughBehaviorWhenNoMatch, + ValidateFunc: validation.StringInSlice([]string{ + apigatewayv2.PassthroughBehaviorWhenNoMatch, + apigatewayv2.PassthroughBehaviorNever, + apigatewayv2.PassthroughBehaviorWhenNoTemplates, + }, false), + }, + "payload_format_version": { + Type: schema.TypeString, + Optional: true, + Default: "1.0", + ValidateFunc: validation.StringInSlice([]string{ + "1.0", + "2.0", + }, false), + }, + "request_templates": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "template_selection_expression": { + Type: schema.TypeString, + Optional: true, + }, + "timeout_milliseconds": { + Type: schema.TypeInt, + Optional: true, + Default: 29000, + ValidateFunc: validation.IntBetween(50, 29000), + }, + }, + } +} + +func resourceAwsApiGatewayV2IntegrationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + req := &apigatewayv2.CreateIntegrationInput{ + ApiId: aws.String(d.Get("api_id").(string)), + IntegrationType: aws.String(d.Get("integration_type").(string)), + } + if v, ok := d.GetOk("connection_id"); ok { + req.ConnectionId = aws.String(v.(string)) + } + if v, ok := d.GetOk("connection_type"); ok { + req.ConnectionType = aws.String(v.(string)) + } + if v, ok := d.GetOk("content_handling_strategy"); ok { + req.ContentHandlingStrategy = aws.String(v.(string)) + } + if v, ok := d.GetOk("credentials_arn"); ok { + req.CredentialsArn = aws.String(v.(string)) + } + if v, ok := d.GetOk("description"); ok { + req.Description = aws.String(v.(string)) + } + if v, ok := d.GetOk("integration_method"); ok { + req.IntegrationMethod = aws.String(v.(string)) + } + if v, ok := d.GetOk("integration_uri"); ok { + req.IntegrationUri = aws.String(v.(string)) + } + if v, ok := d.GetOk("passthrough_behavior"); ok { + req.PassthroughBehavior = aws.String(v.(string)) + } + if v, ok := d.GetOk("payload_format_version"); ok { + req.PayloadFormatVersion = aws.String(v.(string)) + } + if v, ok := d.GetOk("request_templates"); ok { + req.RequestTemplates = stringMapToPointers(v.(map[string]interface{})) + } + if v, ok := d.GetOk("template_selection_expression"); ok { + req.TemplateSelectionExpression = aws.String(v.(string)) + } + if v, ok := d.GetOk("timeout_milliseconds"); ok { + req.TimeoutInMillis = aws.Int64(int64(v.(int))) + } + + log.Printf("[DEBUG] Creating API Gateway v2 integration: %s", req) + resp, err := conn.CreateIntegration(req) + if err != nil { + return fmt.Errorf("error creating API Gateway v2 integration: %s", err) + } + + d.SetId(aws.StringValue(resp.IntegrationId)) + + return resourceAwsApiGatewayV2IntegrationRead(d, meta) +} + +func resourceAwsApiGatewayV2IntegrationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + resp, err := conn.GetIntegration(&apigatewayv2.GetIntegrationInput{ + ApiId: aws.String(d.Get("api_id").(string)), + IntegrationId: aws.String(d.Id()), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + log.Printf("[WARN] API Gateway v2 integration (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("error reading API Gateway v2 integration: %s", err) + } + + d.Set("connection_id", resp.ConnectionId) + d.Set("connection_type", resp.ConnectionType) + d.Set("content_handling_strategy", resp.ContentHandlingStrategy) + d.Set("credentials_arn", resp.CredentialsArn) + d.Set("description", resp.Description) + d.Set("integration_method", resp.IntegrationMethod) + d.Set("integration_response_selection_expression", resp.IntegrationResponseSelectionExpression) + d.Set("integration_type", resp.IntegrationType) + d.Set("integration_uri", resp.IntegrationUri) + d.Set("passthrough_behavior", resp.PassthroughBehavior) + d.Set("payload_format_version", resp.PayloadFormatVersion) + err = d.Set("request_templates", pointersMapToStringList(resp.RequestTemplates)) + if err != nil { + return fmt.Errorf("error setting request_templates: %s", err) + } + d.Set("template_selection_expression", resp.TemplateSelectionExpression) + d.Set("timeout_milliseconds", resp.TimeoutInMillis) + + return nil +} + +func resourceAwsApiGatewayV2IntegrationUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + req := &apigatewayv2.UpdateIntegrationInput{ + ApiId: aws.String(d.Get("api_id").(string)), + IntegrationId: aws.String(d.Id()), + } + if d.HasChange("connection_id") { + req.ConnectionId = aws.String(d.Get("connection_id").(string)) + } + if d.HasChange("connection_type") { + req.ConnectionType = aws.String(d.Get("connection_type").(string)) + } + if d.HasChange("content_handling_strategy") { + req.ContentHandlingStrategy = aws.String(d.Get("content_handling_strategy").(string)) + } + if d.HasChange("credentials_arn") { + req.CredentialsArn = aws.String(d.Get("credentials_arn").(string)) + } + if d.HasChange("description") { + req.Description = aws.String(d.Get("description").(string)) + } + if d.HasChange("integration_method") { + req.IntegrationMethod = aws.String(d.Get("integration_method").(string)) + } + if d.HasChange("integration_uri") { + req.IntegrationUri = aws.String(d.Get("integration_uri").(string)) + } + if d.HasChange("passthrough_behavior") { + req.PassthroughBehavior = aws.String(d.Get("passthrough_behavior").(string)) + } + if d.HasChange("payload_format_version") { + req.PayloadFormatVersion = aws.String(d.Get("payload_format_version").(string)) + } + if d.HasChange("request_templates") { + req.RequestTemplates = stringMapToPointers(d.Get("request_templates").(map[string]interface{})) + } + if d.HasChange("template_selection_expression") { + req.TemplateSelectionExpression = aws.String(d.Get("template_selection_expression").(string)) + } + if d.HasChange("timeout_milliseconds") { + req.TimeoutInMillis = aws.Int64(int64(d.Get("timeout_milliseconds").(int))) + } + + log.Printf("[DEBUG] Updating API Gateway v2 integration: %s", req) + _, err := conn.UpdateIntegration(req) + if err != nil { + return fmt.Errorf("error updating API Gateway v2 integration: %s", err) + } + + return resourceAwsApiGatewayV2IntegrationRead(d, meta) +} + +func resourceAwsApiGatewayV2IntegrationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + log.Printf("[DEBUG] Deleting API Gateway v2 integration (%s)", d.Id()) + _, err := conn.DeleteIntegration(&apigatewayv2.DeleteIntegrationInput{ + ApiId: aws.String(d.Get("api_id").(string)), + IntegrationId: aws.String(d.Id()), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + return nil + } + if err != nil { + return fmt.Errorf("error deleting API Gateway v2 integration: %s", err) + } + + return nil +} + +func resourceAwsApiGatewayV2IntegrationImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + if len(parts) != 2 { + return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'api-id/integration-id'", d.Id()) + } + + apiId := parts[0] + integrationId := parts[1] + + conn := meta.(*AWSClient).apigatewayv2conn + + resp, err := conn.GetIntegration(&apigatewayv2.GetIntegrationInput{ + ApiId: aws.String(apiId), + IntegrationId: aws.String(integrationId), + }) + if err != nil { + return nil, err + } + + if aws.BoolValue(resp.ApiGatewayManaged) { + return nil, fmt.Errorf("API Gateway v2 integration (%s) was created via quick create", integrationId) + } + + d.SetId(integrationId) + d.Set("api_id", apiId) + + return []*schema.ResourceData{d}, nil +} diff --git a/aws/resource_aws_apigatewayv2_integration_test.go b/aws/resource_aws_apigatewayv2_integration_test.go new file mode 100644 index 000000000000..38de2e89b1b9 --- /dev/null +++ b/aws/resource_aws_apigatewayv2_integration_test.go @@ -0,0 +1,450 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apigatewayv2" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSAPIGatewayV2Integration_basic(t *testing.T) { + var apiId string + var v apigatewayv2.GetIntegrationOutput + resourceName := "aws_apigatewayv2_integration.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2IntegrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2IntegrationConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2IntegrationExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "connection_id", ""), + resource.TestCheckResourceAttr(resourceName, "connection_type", "INTERNET"), + resource.TestCheckResourceAttr(resourceName, "content_handling_strategy", ""), + resource.TestCheckResourceAttr(resourceName, "credentials_arn", ""), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "integration_method", ""), + resource.TestCheckResourceAttr(resourceName, "integration_response_selection_expression", "${integration.response.statuscode}"), + resource.TestCheckResourceAttr(resourceName, "integration_type", "MOCK"), + resource.TestCheckResourceAttr(resourceName, "integration_uri", ""), + resource.TestCheckResourceAttr(resourceName, "passthrough_behavior", "WHEN_NO_MATCH"), + resource.TestCheckResourceAttr(resourceName, "payload_format_version", "1.0"), + resource.TestCheckResourceAttr(resourceName, "request_templates.%", "0"), + resource.TestCheckResourceAttr(resourceName, "template_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "29000"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2IntegrationImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Integration_disappears(t *testing.T) { + var apiId string + var v apigatewayv2.GetIntegrationOutput + resourceName := "aws_apigatewayv2_integration.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2IntegrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2IntegrationConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2IntegrationExists(resourceName, &apiId, &v), + testAccCheckAWSAPIGatewayV2IntegrationDisappears(&apiId, &v), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Integration_IntegrationTypeHttp(t *testing.T) { + var apiId string + var v apigatewayv2.GetIntegrationOutput + resourceName := "aws_apigatewayv2_integration.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2IntegrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2IntegrationConfig_integrationTypeHttp(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2IntegrationExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "connection_id", ""), + resource.TestCheckResourceAttr(resourceName, "connection_type", "INTERNET"), + resource.TestCheckResourceAttr(resourceName, "content_handling_strategy", "CONVERT_TO_TEXT"), + resource.TestCheckResourceAttr(resourceName, "credentials_arn", ""), + resource.TestCheckResourceAttr(resourceName, "description", "Test HTTP"), + resource.TestCheckResourceAttr(resourceName, "integration_method", "GET"), + resource.TestCheckResourceAttr(resourceName, "integration_response_selection_expression", "${integration.response.statuscode}"), + resource.TestCheckResourceAttr(resourceName, "integration_type", "HTTP"), + resource.TestCheckResourceAttr(resourceName, "integration_uri", "http://www.example.com"), + resource.TestCheckResourceAttr(resourceName, "passthrough_behavior", "WHEN_NO_MATCH"), + resource.TestCheckResourceAttr(resourceName, "payload_format_version", "1.0"), + resource.TestCheckResourceAttr(resourceName, "request_templates.%", "1"), + resource.TestCheckResourceAttr(resourceName, "request_templates.application/json", ""), + resource.TestCheckResourceAttr(resourceName, "template_selection_expression", "$request.body.name"), + resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "28999"), + ), + }, + { + Config: testAccAWSAPIGatewayV2IntegrationConfig_integrationTypeHttpUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2IntegrationExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "connection_id", ""), + resource.TestCheckResourceAttr(resourceName, "connection_type", "INTERNET"), + resource.TestCheckResourceAttr(resourceName, "content_handling_strategy", "CONVERT_TO_BINARY"), + resource.TestCheckResourceAttr(resourceName, "credentials_arn", ""), + resource.TestCheckResourceAttr(resourceName, "description", "Test HTTP updated"), + resource.TestCheckResourceAttr(resourceName, "integration_method", "POST"), + resource.TestCheckResourceAttr(resourceName, "integration_response_selection_expression", "${integration.response.statuscode}"), + resource.TestCheckResourceAttr(resourceName, "integration_type", "HTTP"), + resource.TestCheckResourceAttr(resourceName, "integration_uri", "http://www.example.org"), + resource.TestCheckResourceAttr(resourceName, "passthrough_behavior", "WHEN_NO_TEMPLATES"), + resource.TestCheckResourceAttr(resourceName, "payload_format_version", "1.0"), + resource.TestCheckResourceAttr(resourceName, "request_templates.%", "2"), + resource.TestCheckResourceAttr(resourceName, "request_templates.application/json", "#set($number=42)"), + resource.TestCheckResourceAttr(resourceName, "request_templates.application/xml", "#set($percent=$number/100)"), + resource.TestCheckResourceAttr(resourceName, "template_selection_expression", "$request.body.id"), + resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "51"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2IntegrationImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Integration_Lambda(t *testing.T) { + var apiId string + var v apigatewayv2.GetIntegrationOutput + resourceName := "aws_apigatewayv2_integration.test" + callerIdentityDatasourceName := "data.aws_caller_identity.current" + lambdaResourceName := "aws_lambda_function.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2IntegrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2IntegrationConfig_lambda(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2IntegrationExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "connection_type", "INTERNET"), + resource.TestCheckResourceAttr(resourceName, "content_handling_strategy", "CONVERT_TO_TEXT"), + resource.TestCheckResourceAttrPair(resourceName, "credentials_arn", callerIdentityDatasourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "description", "Test Lambda"), + resource.TestCheckResourceAttr(resourceName, "integration_method", "POST"), + resource.TestCheckResourceAttr(resourceName, "integration_response_selection_expression", "${integration.response.body.errorMessage}"), + resource.TestCheckResourceAttr(resourceName, "integration_type", "AWS"), + resource.TestCheckResourceAttrPair(resourceName, "integration_uri", lambdaResourceName, "invoke_arn"), + resource.TestCheckResourceAttr(resourceName, "passthrough_behavior", "WHEN_NO_MATCH"), + resource.TestCheckResourceAttr(resourceName, "payload_format_version", "1.0"), + resource.TestCheckResourceAttr(resourceName, "request_templates.%", "0"), + resource.TestCheckResourceAttr(resourceName, "template_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "29000"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2IntegrationImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Integration_VpcLink(t *testing.T) { + var apiId string + var v apigatewayv2.GetIntegrationOutput + resourceName := "aws_apigatewayv2_integration.test" + vpcLinkResourceName := "aws_api_gateway_vpc_link.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2IntegrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2IntegrationConfig_vpcLink(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2IntegrationExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttrPair(resourceName, "connection_id", vpcLinkResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "connection_type", "VPC_LINK"), + resource.TestCheckResourceAttr(resourceName, "content_handling_strategy", "CONVERT_TO_TEXT"), + resource.TestCheckResourceAttr(resourceName, "credentials_arn", ""), + resource.TestCheckResourceAttr(resourceName, "description", "Test VPC Link"), + resource.TestCheckResourceAttr(resourceName, "integration_method", "PUT"), + resource.TestCheckResourceAttr(resourceName, "integration_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "integration_type", "HTTP_PROXY"), + resource.TestCheckResourceAttr(resourceName, "integration_uri", "http://www.example.net"), + resource.TestCheckResourceAttr(resourceName, "passthrough_behavior", "NEVER"), + resource.TestCheckResourceAttr(resourceName, "payload_format_version", "1.0"), + resource.TestCheckResourceAttr(resourceName, "request_templates.%", "0"), + resource.TestCheckResourceAttr(resourceName, "template_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "12345"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2IntegrationImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSAPIGatewayV2IntegrationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_apigatewayv2_integration" { + continue + } + + _, err := conn.GetIntegration(&apigatewayv2.GetIntegrationInput{ + ApiId: aws.String(rs.Primary.Attributes["api_id"]), + IntegrationId: aws.String(rs.Primary.ID), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + continue + } + if err != nil { + return err + } + + return fmt.Errorf("API Gateway v2 integration %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAWSAPIGatewayV2IntegrationDisappears(apiId *string, v *apigatewayv2.GetIntegrationOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + _, err := conn.DeleteIntegration(&apigatewayv2.DeleteIntegrationInput{ + ApiId: apiId, + IntegrationId: v.IntegrationId, + }) + + return err + } +} + +func testAccCheckAWSAPIGatewayV2IntegrationExists(n string, vApiId *string, v *apigatewayv2.GetIntegrationOutput) 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 v2 integration ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + apiId := aws.String(rs.Primary.Attributes["api_id"]) + resp, err := conn.GetIntegration(&apigatewayv2.GetIntegrationInput{ + ApiId: apiId, + IntegrationId: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *vApiId = *apiId + *v = *resp + + return nil + } +} + +func testAccAWSAPIGatewayV2IntegrationImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not Found: %s", resourceName) + } + + return fmt.Sprintf("%s/%s", rs.Primary.Attributes["api_id"], rs.Primary.ID), nil + } +} + +func testAccAWSAPIGatewayV2IntegrationConfig_apiWebSocket(rName string) string { + return fmt.Sprintf(` +resource "aws_apigatewayv2_api" "test" { + name = %[1]q + protocol_type = "WEBSOCKET" + route_selection_expression = "$request.body.action" +} +`, rName) +} + +func testAccAWSAPIGatewayV2IntegrationConfig_basic(rName string) string { + return testAccAWSAPIGatewayV2IntegrationConfig_apiWebSocket(rName) + fmt.Sprintf(` +resource "aws_apigatewayv2_integration" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + integration_type = "MOCK" +} +`) +} + +func testAccAWSAPIGatewayV2IntegrationConfig_integrationTypeHttp(rName string) string { + return testAccAWSAPIGatewayV2IntegrationConfig_apiWebSocket(rName) + fmt.Sprintf(` +resource "aws_apigatewayv2_integration" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + integration_type = "HTTP" + + connection_type = "INTERNET" + content_handling_strategy = "CONVERT_TO_TEXT" + description = "Test HTTP" + integration_method = "GET" + integration_uri = "http://www.example.com" + passthrough_behavior = "WHEN_NO_MATCH" + template_selection_expression = "$request.body.name" + timeout_milliseconds = 28999 + + request_templates = { + "application/json" = "" + } +} +`) +} + +func testAccAWSAPIGatewayV2IntegrationConfig_integrationTypeHttpUpdated(rName string) string { + return testAccAWSAPIGatewayV2IntegrationConfig_apiWebSocket(rName) + fmt.Sprintf(` +resource "aws_apigatewayv2_integration" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + integration_type = "HTTP" + + connection_type = "INTERNET" + content_handling_strategy = "CONVERT_TO_BINARY" + description = "Test HTTP updated" + integration_method = "POST" + integration_uri = "http://www.example.org" + passthrough_behavior = "WHEN_NO_TEMPLATES" + template_selection_expression = "$request.body.id" + timeout_milliseconds = 51 + + request_templates = { + "application/json" = "#set($number=42)" + "application/xml" = "#set($percent=$number/100)" + } +} +`) +} + +func testAccAWSAPIGatewayV2IntegrationConfig_lambda(rName string) string { + return testAccAWSAPIGatewayV2IntegrationConfig_apiWebSocket(rName) + baseAccAWSLambdaConfig(rName, rName, rName) + fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_lambda_function" "test" { + filename = "test-fixtures/lambdatest.zip" + function_name = %[1]q + role = "${aws_iam_role.iam_for_lambda.arn}" + handler = "index.handler" + runtime = "nodejs10.x" +} + +resource "aws_apigatewayv2_integration" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + integration_type = "AWS" + + connection_type = "INTERNET" + content_handling_strategy = "CONVERT_TO_TEXT" + credentials_arn = "${data.aws_caller_identity.current.arn}" + description = "Test Lambda" + integration_method = "POST" + integration_uri = "${aws_lambda_function.test.invoke_arn}" + passthrough_behavior = "WHEN_NO_MATCH" +} +`, rName) +} + +func testAccAWSAPIGatewayV2IntegrationConfig_vpcLink(rName string) string { + return testAccAWSAPIGatewayV2IntegrationConfig_apiWebSocket(rName) + fmt.Sprintf(` +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.10.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.10.0.0/24" + availability_zone = "${data.aws_availability_zones.available.names[0]}" + + tags = { + Name = %[1]q + } +} + +resource "aws_lb" "test" { + name = %[1]q + internal = true + load_balancer_type = "network" + subnets = ["${aws_subnet.test.id}"] +} + +resource "aws_api_gateway_vpc_link" "test" { + name = %[1]q + target_arns = ["${aws_lb.test.arn}"] +} + +resource "aws_apigatewayv2_integration" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + integration_type = "HTTP_PROXY" + + connection_id = "${aws_api_gateway_vpc_link.test.id}" + connection_type = "VPC_LINK" + content_handling_strategy = "CONVERT_TO_TEXT" + description = "Test VPC Link" + integration_method = "PUT" + integration_uri = "http://www.example.net" + passthrough_behavior = "NEVER" + timeout_milliseconds = 12345 +} +`, rName) +} diff --git a/website/aws.erb b/website/aws.erb index 62d86b6a06ce..6043c4ac8a36 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -220,6 +220,7 @@
  • aws_apigatewayv2_authorizer + aws_apigatewayv2_integration
  • diff --git a/website/docs/r/apigatewayv2_integration.html.markdown b/website/docs/r/apigatewayv2_integration.html.markdown new file mode 100644 index 000000000000..0596f45650dd --- /dev/null +++ b/website/docs/r/apigatewayv2_integration.html.markdown @@ -0,0 +1,84 @@ +--- +subcategory: "API Gateway v2 (WebSocket and HTTP APIs)" +layout: "aws" +page_title: "AWS: aws_apigatewayv2_integration" +description: |- + Manages an Amazon API Gateway Version 2 integration. +--- + +# Resource: aws_apigatewayv2_integration + +Manages an Amazon API Gateway Version 2 integration. +More information can be found in the [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html). + +## Example Usage + +### Basic + +```hcl +resource "aws_apigatewayv2_integration" "example" { + api_id = "${aws_apigatewayv2_api.example.id}" + integration_type = "MOCK" +} +``` + +### Lambda Integration + +```hcl +resource "aws_lambda_function" "example" { + filename = "example.zip" + function_name = "Example" + role = "${aws_iam_role.example.arn}" + handler = "index.handler" + runtime = "nodejs10.x" +} + +resource "aws_apigatewayv2_integration" "example" { + api_id = "${aws_apigatewayv2_api.example.id}" + integration_type = "AWS" + + connection_type = "INTERNET" + content_handling_strategy = "CONVERT_TO_TEXT" + description = "Lambda example" + integration_method = "POST" + integration_uri = "${aws_lambda_function.example.invoke_arn}" + passthrough_behavior = "WHEN_NO_MATCH" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `api_id` - (Required) The API identifier. +* `integration_type` - (Required) The integration type of an integration. +Valid values: `AWS`, `AWS_PROXY`, `HTTP`, `HTTP_PROXY`, `MOCK`. +* `connection_id` - (Optional) The ID of the VPC link for a private integration. Supported only for HTTP APIs. +* `connection_type` - (Optional) The type of the network connection to the integration endpoint. Valid values: `INTERNET`, `VPC_LINK`. Default is `INTERNET`. +* `content_handling_strategy` - (Optional) How to handle response payload content type conversions. Valid values: `CONVERT_TO_BINARY`, `CONVERT_TO_TEXT`. Supported only for WebSocket APIs. +* `credentials_arn` - (Optional) The credentials required for the integration, if any. +* `description` - (Optional) The description of the integration. +* `integration_method` - (Optional) The integration's HTTP method. Must be specified if `integration_type` is not `MOCK`. +* `integration_uri` - (Optional) The URI of the Lambda function for a Lambda proxy integration, when `integration_type` is `AWS_PROXY`. +For an `HTTP` integration, specify a fully-qualified URL. For an HTTP API private integration, specify the ARN of an Application Load Balancer listener, Network Load Balancer listener, or AWS Cloud Map service. +* `passthrough_behavior` - (Optional) The pass-through behavior for incoming requests based on the Content-Type header in the request, and the available mapping templates specified as the `request_templates` attribute. +Valid values: `WHEN_NO_MATCH`, `WHEN_NO_TEMPLATES`, `NEVER`. Default is `WHEN_NO_MATCH`. Supported only for WebSocket APIs. +* `payload_format_version` - (Optional) The [format of the payload](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format) sent to an integration. Valid values: `1.0`, `2.0`. Default is `1.0`. +* `request_templates` - (Optional) A map of Velocity templates that are applied on the request payload based on the value of the Content-Type header sent by the client. Supported only for WebSocket APIs. +* `template_selection_expression` - (Optional) The [template selection expression](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html#apigateway-websocket-api-template-selection-expressions) for the integration. +* `timeout_milliseconds` - (Optional) Custom timeout between 50 and 29,000 milliseconds. The default value is 29,000 milliseconds or 29 seconds. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The integration identifier. +* `integration_response_selection_expression` - The [integration response selection expression](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html#apigateway-websocket-api-integration-response-selection-expressions) for the integration. + +## Import + +`aws_apigatewayv2_integration` can be imported by using the API identifier and integration identifier, e.g. + +``` +$ terraform import aws_apigatewayv2_integration.example aabbccddee/1122334 +```