diff --git a/aws/provider.go b/aws/provider.go index 11d9d824c9c..8cdc8336818 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -372,6 +372,7 @@ func Provider() terraform.ResourceProvider { "aws_apigatewayv2_integration_response": resourceAwsApiGatewayV2IntegrationResponse(), "aws_apigatewayv2_model": resourceAwsApiGatewayV2Model(), "aws_apigatewayv2_route": resourceAwsApiGatewayV2Route(), + "aws_apigatewayv2_route_response": resourceAwsApiGatewayV2RouteResponse(), "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), "aws_appautoscaling_target": resourceAwsAppautoscalingTarget(), "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), diff --git a/aws/resource_aws_apigatewayv2_route_response.go b/aws/resource_aws_apigatewayv2_route_response.go new file mode 100644 index 00000000000..ed8f895aad3 --- /dev/null +++ b/aws/resource_aws_apigatewayv2_route_response.go @@ -0,0 +1,160 @@ +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" +) + +func resourceAwsApiGatewayV2RouteResponse() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsApiGatewayV2RouteResponseCreate, + Read: resourceAwsApiGatewayV2RouteResponseRead, + Update: resourceAwsApiGatewayV2RouteResponseUpdate, + Delete: resourceAwsApiGatewayV2RouteResponseDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsApiGatewayV2RouteResponseImport, + }, + + Schema: map[string]*schema.Schema{ + "api_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "model_selection_expression": { + Type: schema.TypeString, + Optional: true, + }, + "response_models": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "route_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "route_response_key": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceAwsApiGatewayV2RouteResponseCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + req := &apigatewayv2.CreateRouteResponseInput{ + ApiId: aws.String(d.Get("api_id").(string)), + RouteId: aws.String(d.Get("route_id").(string)), + RouteResponseKey: aws.String(d.Get("route_response_key").(string)), + } + if v, ok := d.GetOk("model_selection_expression"); ok { + req.ModelSelectionExpression = aws.String(v.(string)) + } + if v, ok := d.GetOk("response_models"); ok { + req.ResponseModels = stringMapToPointers(v.(map[string]interface{})) + } + + log.Printf("[DEBUG] Creating API Gateway v2 route response: %s", req) + resp, err := conn.CreateRouteResponse(req) + if err != nil { + return fmt.Errorf("error creating API Gateway v2 route response: %s", err) + } + + d.SetId(aws.StringValue(resp.RouteResponseId)) + + return resourceAwsApiGatewayV2RouteResponseRead(d, meta) +} + +func resourceAwsApiGatewayV2RouteResponseRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + resp, err := conn.GetRouteResponse(&apigatewayv2.GetRouteResponseInput{ + ApiId: aws.String(d.Get("api_id").(string)), + RouteId: aws.String(d.Get("route_id").(string)), + RouteResponseId: aws.String(d.Id()), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + log.Printf("[WARN] API Gateway v2 route response (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("error reading API Gateway v2 route response: %s", err) + } + + d.Set("model_selection_expression", resp.ModelSelectionExpression) + if err := d.Set("response_models", pointersMapToStringList(resp.ResponseModels)); err != nil { + return fmt.Errorf("error setting response_models: %s", err) + } + d.Set("route_response_key", resp.RouteResponseKey) + + return nil +} + +func resourceAwsApiGatewayV2RouteResponseUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + req := &apigatewayv2.UpdateRouteResponseInput{ + ApiId: aws.String(d.Get("api_id").(string)), + RouteId: aws.String(d.Get("route_id").(string)), + RouteResponseId: aws.String(d.Id()), + } + if d.HasChange("model_selection_expression") { + req.ModelSelectionExpression = aws.String(d.Get("model_selection_expression").(string)) + } + if d.HasChange("response_models") { + req.ResponseModels = stringMapToPointers(d.Get("response_models").(map[string]interface{})) + } + if d.HasChange("route_response_key") { + req.RouteResponseKey = aws.String(d.Get("route_response_key").(string)) + } + + log.Printf("[DEBUG] Updating API Gateway v2 route response: %s", req) + _, err := conn.UpdateRouteResponse(req) + if err != nil { + return fmt.Errorf("error updating API Gateway v2 route response: %s", err) + } + + return resourceAwsApiGatewayV2RouteResponseRead(d, meta) +} + +func resourceAwsApiGatewayV2RouteResponseDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + log.Printf("[DEBUG] Deleting API Gateway v2 route response (%s)", d.Id()) + _, err := conn.DeleteRouteResponse(&apigatewayv2.DeleteRouteResponseInput{ + ApiId: aws.String(d.Get("api_id").(string)), + RouteId: aws.String(d.Get("route_id").(string)), + RouteResponseId: aws.String(d.Id()), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + return nil + } + if err != nil { + return fmt.Errorf("error deleting API Gateway v2 route response: %s", err) + } + + return nil +} + +func resourceAwsApiGatewayV2RouteResponseImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + if len(parts) != 3 { + return []*schema.ResourceData{}, fmt.Errorf("Wrong format of resource: %s. Please follow 'api-id/route-id/route-response-id'", d.Id()) + } + + d.SetId(parts[2]) + d.Set("api_id", parts[0]) + d.Set("route_id", parts[1]) + + return []*schema.ResourceData{d}, nil +} diff --git a/aws/resource_aws_apigatewayv2_route_response_test.go b/aws/resource_aws_apigatewayv2_route_response_test.go new file mode 100644 index 00000000000..d38970eaede --- /dev/null +++ b/aws/resource_aws_apigatewayv2_route_response_test.go @@ -0,0 +1,212 @@ +package aws + +import ( + "fmt" + "strings" + "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 TestAccAWSAPIGatewayV2RouteResponse_basic(t *testing.T) { + var apiId, routeId string + var v apigatewayv2.GetRouteResponseOutput + resourceName := "aws_apigatewayv2_route_response.test" + routeResourceName := "aws_apigatewayv2_route.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteResponseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteResponseConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteResponseExists(resourceName, &apiId, &routeId, &v), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "response_models.%", "0"), + resource.TestCheckResourceAttrPair(resourceName, "route_id", routeResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "route_response_key", "$default"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2RouteResponseImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2RouteResponse_disappears(t *testing.T) { + var apiId, routeId string + var v apigatewayv2.GetRouteResponseOutput + resourceName := "aws_apigatewayv2_route_response.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteResponseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteResponseConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteResponseExists(resourceName, &apiId, &routeId, &v), + testAccCheckAWSAPIGatewayV2RouteResponseDisappears(&apiId, &routeId, &v), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2RouteResponse_Model(t *testing.T) { + var apiId, routeId string + var v apigatewayv2.GetRouteResponseOutput + resourceName := "aws_apigatewayv2_route_response.test" + modelResourceName := "aws_apigatewayv2_model.test" + routeResourceName := "aws_apigatewayv2_route.test" + // Model name must be alphanumeric. + rName := strings.ReplaceAll(acctest.RandomWithPrefix("tf-acc-test"), "-", "") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteResponseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteResponseConfig_model(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteResponseExists(resourceName, &apiId, &routeId, &v), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", "action"), + resource.TestCheckResourceAttr(resourceName, "response_models.%", "1"), + resource.TestCheckResourceAttrPair(resourceName, "response_models.test", modelResourceName, "name"), + resource.TestCheckResourceAttrPair(resourceName, "route_id", routeResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "route_response_key", "$default"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2RouteResponseImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSAPIGatewayV2RouteResponseDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_apigatewayv2_route_response" { + continue + } + + _, err := conn.GetRouteResponse(&apigatewayv2.GetRouteResponseInput{ + ApiId: aws.String(rs.Primary.Attributes["api_id"]), + RouteId: aws.String(rs.Primary.Attributes["route_id"]), + RouteResponseId: aws.String(rs.Primary.ID), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + continue + } + if err != nil { + return err + } + + return fmt.Errorf("API Gateway v2 route response %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAWSAPIGatewayV2RouteResponseDisappears(apiId, routeId *string, v *apigatewayv2.GetRouteResponseOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + _, err := conn.DeleteRouteResponse(&apigatewayv2.DeleteRouteResponseInput{ + ApiId: apiId, + RouteId: routeId, + RouteResponseId: v.RouteResponseId, + }) + + return err + } +} + +func testAccCheckAWSAPIGatewayV2RouteResponseExists(n string, vApiId, vRouteId *string, v *apigatewayv2.GetRouteResponseOutput) 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 route response ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + apiId := aws.String(rs.Primary.Attributes["api_id"]) + routeId := aws.String(rs.Primary.Attributes["route_id"]) + resp, err := conn.GetRouteResponse(&apigatewayv2.GetRouteResponseInput{ + ApiId: apiId, + RouteId: routeId, + RouteResponseId: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *vApiId = *apiId + *vRouteId = *routeId + *v = *resp + + return nil + } +} + +func testAccAWSAPIGatewayV2RouteResponseImportStateIdFunc(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/%s", rs.Primary.Attributes["api_id"], rs.Primary.Attributes["route_id"], rs.Primary.ID), nil + } +} + +func testAccAWSAPIGatewayV2RouteResponseConfig_basic(rName string) string { + return testAccAWSAPIGatewayV2RouteConfig_basic(rName) + fmt.Sprintf(` +resource "aws_apigatewayv2_route_response" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + route_id = "${aws_apigatewayv2_route.test.id}" + route_response_key = "$default" +} +`) +} + +func testAccAWSAPIGatewayV2RouteResponseConfig_model(rName string) string { + return testAccAWSAPIGatewayV2RouteConfig_model(rName) + fmt.Sprintf(` +resource "aws_apigatewayv2_route_response" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + route_id = "${aws_apigatewayv2_route.test.id}" + route_response_key = "$default" + + model_selection_expression = "action" + + response_models = { + "test" = "${aws_apigatewayv2_model.test.name}" + } +} +`) +} diff --git a/website/aws.erb b/website/aws.erb index 2ad1eef73ad..42d2ba89cfc 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -236,6 +236,9 @@