diff --git a/.changelog/18410.txt b/.changelog/18410.txt new file mode 100644 index 000000000000..a15b77ed2c2b --- /dev/null +++ b/.changelog/18410.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_apigatewayv2_route: Add `request_parameter` attribute +``` diff --git a/aws/resource_aws_apigatewayv2_route.go b/aws/resource_aws_apigatewayv2_route.go index 4ddb0537b923..b4811981232e 100644 --- a/aws/resource_aws_apigatewayv2_route.go +++ b/aws/resource_aws_apigatewayv2_route.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/apigatewayv2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) @@ -38,15 +39,10 @@ func resourceAwsApiGatewayV2Route() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "authorization_type": { - Type: schema.TypeString, - Optional: true, - Default: apigatewayv2.AuthorizationTypeNone, - ValidateFunc: validation.StringInSlice([]string{ - apigatewayv2.AuthorizationTypeNone, - apigatewayv2.AuthorizationTypeAwsIam, - apigatewayv2.AuthorizationTypeCustom, - apigatewayv2.AuthorizationTypeJwt, - }, false), + Type: schema.TypeString, + Optional: true, + Default: apigatewayv2.AuthorizationTypeNone, + ValidateFunc: validation.StringInSlice(apigatewayv2.AuthorizationType_Values(), false), }, "authorizer_id": { Type: schema.TypeString, @@ -66,6 +62,23 @@ func resourceAwsApiGatewayV2Route() *schema.Resource { Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "request_parameter": { + Type: schema.TypeSet, + Optional: true, + MinItems: 0, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "request_parameter_key": { + Type: schema.TypeString, + Required: true, + }, + "required": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, "route_key": { Type: schema.TypeString, Required: true, @@ -107,6 +120,9 @@ func resourceAwsApiGatewayV2RouteCreate(d *schema.ResourceData, meta interface{} if v, ok := d.GetOk("request_models"); ok { req.RequestModels = stringMapToPointers(v.(map[string]interface{})) } + if v, ok := d.GetOk("request_parameter"); ok && v.(*schema.Set).Len() > 0 { + req.RequestParameters = expandApiGatewayV2RouteRequestParameters(v.(*schema.Set).List()) + } if v, ok := d.GetOk("route_response_selection_expression"); ok { req.RouteResponseSelectionExpression = aws.String(v.(string)) } @@ -117,7 +133,7 @@ func resourceAwsApiGatewayV2RouteCreate(d *schema.ResourceData, meta interface{} log.Printf("[DEBUG] Creating API Gateway v2 route: %s", req) resp, err := conn.CreateRoute(req) if err != nil { - return fmt.Errorf("error creating API Gateway v2 route: %s", err) + return fmt.Errorf("error creating API Gateway v2 route: %w", err) } d.SetId(aws.StringValue(resp.RouteId)) @@ -132,25 +148,30 @@ func resourceAwsApiGatewayV2RouteRead(d *schema.ResourceData, meta interface{}) ApiId: aws.String(d.Get("api_id").(string)), RouteId: aws.String(d.Id()), }) - if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + + if tfawserr.ErrCodeEquals(err, apigatewayv2.ErrCodeNotFoundException) { log.Printf("[WARN] API Gateway v2 route (%s) not found, removing from state", d.Id()) d.SetId("") return nil } + if err != nil { - return fmt.Errorf("error reading API Gateway v2 route: %s", err) + return fmt.Errorf("error reading API Gateway v2 route (%s): %w", d.Id(), err) } d.Set("api_key_required", resp.ApiKeyRequired) if err := d.Set("authorization_scopes", flattenStringSet(resp.AuthorizationScopes)); err != nil { - return fmt.Errorf("error setting authorization_scopes: %s", err) + return fmt.Errorf("error setting authorization_scopes: %w", err) } d.Set("authorization_type", resp.AuthorizationType) d.Set("authorizer_id", resp.AuthorizerId) d.Set("model_selection_expression", resp.ModelSelectionExpression) d.Set("operation_name", resp.OperationName) if err := d.Set("request_models", pointersMapToStringList(resp.RequestModels)); err != nil { - return fmt.Errorf("error setting request_models: %s", err) + return fmt.Errorf("error setting request_models: %w", err) + } + if err := d.Set("request_parameter", flattenApiGatewayV2RouteRequestParameters(resp.RequestParameters)); err != nil { + return fmt.Errorf("error setting request_parameter: %w", err) } d.Set("route_key", resp.RouteKey) d.Set("route_response_selection_expression", resp.RouteResponseSelectionExpression) @@ -162,45 +183,86 @@ func resourceAwsApiGatewayV2RouteRead(d *schema.ResourceData, meta interface{}) func resourceAwsApiGatewayV2RouteUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).apigatewayv2conn - req := &apigatewayv2.UpdateRouteInput{ - ApiId: aws.String(d.Get("api_id").(string)), - RouteId: aws.String(d.Id()), - } - if d.HasChange("api_key_required") { - req.ApiKeyRequired = aws.Bool(d.Get("api_key_required").(bool)) - } - if d.HasChange("authorization_scopes") { - req.AuthorizationScopes = expandStringSet(d.Get("authorization_scopes").(*schema.Set)) - } - if d.HasChange("authorization_type") { - req.AuthorizationType = aws.String(d.Get("authorization_type").(string)) - } - if d.HasChange("authorizer_id") { - req.AuthorizerId = aws.String(d.Get("authorizer_id").(string)) - } - if d.HasChange("model_selection_expression") { - req.ModelSelectionExpression = aws.String(d.Get("model_selection_expression").(string)) - } - if d.HasChange("operation_name") { - req.OperationName = aws.String(d.Get("operation_name").(string)) - } - if d.HasChange("request_models") { - req.RequestModels = stringMapToPointers(d.Get("request_models").(map[string]interface{})) - } - if d.HasChange("route_key") { - req.RouteKey = aws.String(d.Get("route_key").(string)) - } - if d.HasChange("route_response_selection_expression") { - req.RouteResponseSelectionExpression = aws.String(d.Get("route_response_selection_expression").(string)) - } - if d.HasChange("target") { - req.Target = aws.String(d.Get("target").(string)) + var requestParameters map[string]*apigatewayv2.ParameterConstraints + + if d.HasChange("request_parameter") { + o, n := d.GetChange("request_parameter") + os := o.(*schema.Set) + ns := n.(*schema.Set) + + for _, tfMapRaw := range os.Difference(ns).List() { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + if v, ok := tfMap["request_parameter_key"].(string); ok && v != "" { + log.Printf("[DEBUG] Deleting API Gateway v2 route (%s) request parameter (%s)", d.Id(), v) + _, err := conn.DeleteRouteRequestParameter(&apigatewayv2.DeleteRouteRequestParameterInput{ + ApiId: aws.String(d.Get("api_id").(string)), + RequestParameterKey: aws.String(v), + RouteId: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, apigatewayv2.ErrCodeNotFoundException) { + continue + } + + if err != nil { + return fmt.Errorf("error deleting API Gateway v2 route (%s) request parameter (%s): %w", d.Id(), v, err) + } + } + } + + requestParameters = expandApiGatewayV2RouteRequestParameters(ns.List()) } - log.Printf("[DEBUG] Updating API Gateway v2 route: %s", req) - _, err := conn.UpdateRoute(req) - if err != nil { - return fmt.Errorf("error updating API Gateway v2 route: %s", err) + if d.HasChangesExcept("request_parameter") || len(requestParameters) > 0 { + req := &apigatewayv2.UpdateRouteInput{ + ApiId: aws.String(d.Get("api_id").(string)), + RouteId: aws.String(d.Id()), + } + if d.HasChange("api_key_required") { + req.ApiKeyRequired = aws.Bool(d.Get("api_key_required").(bool)) + } + if d.HasChange("authorization_scopes") { + req.AuthorizationScopes = expandStringSet(d.Get("authorization_scopes").(*schema.Set)) + } + if d.HasChange("authorization_type") { + req.AuthorizationType = aws.String(d.Get("authorization_type").(string)) + } + if d.HasChange("authorizer_id") { + req.AuthorizerId = aws.String(d.Get("authorizer_id").(string)) + } + if d.HasChange("model_selection_expression") { + req.ModelSelectionExpression = aws.String(d.Get("model_selection_expression").(string)) + } + if d.HasChange("operation_name") { + req.OperationName = aws.String(d.Get("operation_name").(string)) + } + if d.HasChange("request_models") { + req.RequestModels = stringMapToPointers(d.Get("request_models").(map[string]interface{})) + } + if d.HasChange("request_parameter") { + req.RequestParameters = requestParameters + } + if d.HasChange("route_key") { + req.RouteKey = aws.String(d.Get("route_key").(string)) + } + if d.HasChange("route_response_selection_expression") { + req.RouteResponseSelectionExpression = aws.String(d.Get("route_response_selection_expression").(string)) + } + if d.HasChange("target") { + req.Target = aws.String(d.Get("target").(string)) + } + + log.Printf("[DEBUG] Updating API Gateway v2 route: %s", req) + _, err := conn.UpdateRoute(req) + + if err != nil { + return fmt.Errorf("error updating API Gateway v2 route (%s): %w", d.Id(), err) + } } return resourceAwsApiGatewayV2RouteRead(d, meta) @@ -214,11 +276,13 @@ func resourceAwsApiGatewayV2RouteDelete(d *schema.ResourceData, meta interface{} ApiId: aws.String(d.Get("api_id").(string)), RouteId: aws.String(d.Id()), }) - if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + + if tfawserr.ErrCodeEquals(err, apigatewayv2.ErrCodeNotFoundException) { return nil } + if err != nil { - return fmt.Errorf("error deleting API Gateway v2 route: %s", err) + return fmt.Errorf("error deleting API Gateway v2 route (%s): %w", d.Id(), err) } return nil @@ -252,3 +316,52 @@ func resourceAwsApiGatewayV2RouteImport(d *schema.ResourceData, meta interface{} return []*schema.ResourceData{d}, nil } + +func expandApiGatewayV2RouteRequestParameters(tfList []interface{}) map[string]*apigatewayv2.ParameterConstraints { + if len(tfList) == 0 { + return nil + } + + apiObjects := map[string]*apigatewayv2.ParameterConstraints{} + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := &apigatewayv2.ParameterConstraints{} + + if v, ok := tfMap["required"].(bool); ok { + apiObject.Required = aws.Bool(v) + } + + if v, ok := tfMap["request_parameter_key"].(string); ok && v != "" { + apiObjects[v] = apiObject + } + } + + return apiObjects +} + +func flattenApiGatewayV2RouteRequestParameters(apiObjects map[string]*apigatewayv2.ParameterConstraints) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for k, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, map[string]interface{}{ + "request_parameter_key": k, + "required": aws.BoolValue(apiObject.Required), + }) + } + + return tfList +} diff --git a/aws/resource_aws_apigatewayv2_route_response_test.go b/aws/resource_aws_apigatewayv2_route_response_test.go index d2f47aea3b02..6a843c1b205d 100644 --- a/aws/resource_aws_apigatewayv2_route_response_test.go +++ b/aws/resource_aws_apigatewayv2_route_response_test.go @@ -26,7 +26,7 @@ func TestAccAWSAPIGatewayV2RouteResponse_basic(t *testing.T) { CheckDestroy: testAccCheckAWSAPIGatewayV2RouteResponseDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAPIGatewayV2RouteResponseConfig_basic(rName), + Config: testAccAWSAPIGatewayV2RouteResponseConfig_basicWebSocket(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAPIGatewayV2RouteResponseExists(resourceName, &apiId, &routeId, &v), resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), @@ -58,7 +58,7 @@ func TestAccAWSAPIGatewayV2RouteResponse_disappears(t *testing.T) { CheckDestroy: testAccCheckAWSAPIGatewayV2RouteResponseDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAPIGatewayV2RouteResponseConfig_basic(rName), + Config: testAccAWSAPIGatewayV2RouteResponseConfig_basicWebSocket(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAPIGatewayV2RouteResponseExists(resourceName, &apiId, &routeId, &v), testAccCheckAWSAPIGatewayV2RouteResponseDisappears(&apiId, &routeId, &v), @@ -188,18 +188,22 @@ func testAccAWSAPIGatewayV2RouteResponseImportStateIdFunc(resourceName string) r } } -func testAccAWSAPIGatewayV2RouteResponseConfig_basic(rName string) string { - return testAccAWSAPIGatewayV2RouteConfig_basic(rName) + ` +func testAccAWSAPIGatewayV2RouteResponseConfig_basicWebSocket(rName string) string { + return composeConfig( + testAccAWSAPIGatewayV2RouteConfig_basicWebSocket(rName), + ` 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) + ` + return composeConfig( + testAccAWSAPIGatewayV2RouteConfig_model(rName), + ` resource "aws_apigatewayv2_route_response" "test" { api_id = aws_apigatewayv2_api.test.id route_id = aws_apigatewayv2_route.test.id @@ -211,5 +215,5 @@ resource "aws_apigatewayv2_route_response" "test" { "test" = aws_apigatewayv2_model.test.name } } -` +`) } diff --git a/aws/resource_aws_apigatewayv2_route_test.go b/aws/resource_aws_apigatewayv2_route_test.go index 9fdf09aa95a4..4bea408bc1cf 100644 --- a/aws/resource_aws_apigatewayv2_route_test.go +++ b/aws/resource_aws_apigatewayv2_route_test.go @@ -25,7 +25,7 @@ func TestAccAWSAPIGatewayV2Route_basic(t *testing.T) { CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAPIGatewayV2RouteConfig_basic(rName), + Config: testAccAWSAPIGatewayV2RouteConfig_basicWebSocket(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), @@ -34,6 +34,7 @@ func TestAccAWSAPIGatewayV2Route_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -62,7 +63,7 @@ func TestAccAWSAPIGatewayV2Route_disappears(t *testing.T) { CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSAPIGatewayV2RouteConfig_basic(rName), + Config: testAccAWSAPIGatewayV2RouteConfig_basicWebSocket(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), testAccCheckResourceDisappears(testAccProvider, resourceAwsApiGatewayV2Route(), resourceName), @@ -97,6 +98,7 @@ func TestAccAWSAPIGatewayV2Route_Authorizer(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "$connect"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -119,6 +121,7 @@ func TestAccAWSAPIGatewayV2Route_Authorizer(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "$connect"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -152,6 +155,7 @@ func TestAccAWSAPIGatewayV2Route_JwtAuthorization(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "GET /test"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -174,6 +178,7 @@ func TestAccAWSAPIGatewayV2Route_JwtAuthorization(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "GET /test"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -208,6 +213,7 @@ func TestAccAWSAPIGatewayV2Route_Model(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "1"), resource.TestCheckResourceAttrPair(resourceName, "request_models.test", modelResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -223,6 +229,88 @@ func TestAccAWSAPIGatewayV2Route_Model(t *testing.T) { }) } +func TestAccAWSAPIGatewayV2Route_RequestParameters(t *testing.T) { + var apiId string + var v apigatewayv2.GetRouteOutput + resourceName := "aws_apigatewayv2_route.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, apigatewayv2.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteConfig_requestParameters(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeNone), + resource.TestCheckResourceAttr(resourceName, "authorizer_id", ""), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "operation_name", ""), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "request_parameter.*", map[string]string{ + "request_parameter_key": "route.request.header.authorization", + "required": "true", + }), + resource.TestCheckResourceAttr(resourceName, "route_key", "$connect"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + { + Config: testAccAWSAPIGatewayV2RouteConfig_requestParametersUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeNone), + resource.TestCheckResourceAttr(resourceName, "authorizer_id", ""), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "operation_name", ""), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "request_parameter.*", map[string]string{ + "request_parameter_key": "route.request.header.authorization", + "required": "false", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "request_parameter.*", map[string]string{ + "request_parameter_key": "route.request.querystring.authToken", + "required": "true", + }), + resource.TestCheckResourceAttr(resourceName, "route_key", "$connect"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2RouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSAPIGatewayV2RouteConfig_noRequestParameters(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeNone), + resource.TestCheckResourceAttr(resourceName, "authorizer_id", ""), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "operation_name", ""), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), + resource.TestCheckResourceAttr(resourceName, "route_key", "$connect"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + }, + }) +} + func TestAccAWSAPIGatewayV2Route_SimpleAttributes(t *testing.T) { var apiId string var v apigatewayv2.GetRouteOutput @@ -245,13 +333,14 @@ func TestAccAWSAPIGatewayV2Route_SimpleAttributes(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", "GET"), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", "$default"), resource.TestCheckResourceAttr(resourceName, "target", ""), ), }, { - Config: testAccAWSAPIGatewayV2RouteConfig_basic(rName), + Config: testAccAWSAPIGatewayV2RouteConfig_basicWebSocket(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), @@ -260,7 +349,7 @@ func TestAccAWSAPIGatewayV2Route_SimpleAttributes(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), - resource.TestCheckResourceAttr(resourceName, "request_parameters.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -276,6 +365,7 @@ func TestAccAWSAPIGatewayV2Route_SimpleAttributes(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", "GET"), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", "$default"), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -314,6 +404,7 @@ func TestAccAWSAPIGatewayV2Route_Target(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), testAccCheckAWSAPIGatewayV2RouteTarget(resourceName, integrationResourceName), @@ -351,6 +442,7 @@ func TestAccAWSAPIGatewayV2Route_UpdateRouteKey(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "GET /path"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -366,6 +458,7 @@ func TestAccAWSAPIGatewayV2Route_UpdateRouteKey(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "operation_name", ""), resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "request_parameter.#", "0"), resource.TestCheckResourceAttr(resourceName, "route_key", "POST /new/path"), resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), resource.TestCheckResourceAttr(resourceName, "target", ""), @@ -476,17 +569,21 @@ resource "aws_apigatewayv2_api" "test" { `, rName) } -func testAccAWSAPIGatewayV2RouteConfig_basic(rName string) string { - return testAccAWSAPIGatewayV2RouteConfig_apiWebSocket(rName) + ` +func testAccAWSAPIGatewayV2RouteConfig_basicWebSocket(rName string) string { + return composeConfig( + testAccAWSAPIGatewayV2RouteConfig_apiWebSocket(rName), + ` resource "aws_apigatewayv2_route" "test" { api_id = aws_apigatewayv2_api.test.id route_key = "$default" } -` +`) } func testAccAWSAPIGatewayV2RouteConfig_authorizer(rName string) string { - return testAccAWSAPIGatewayV2AuthorizerConfig_basic(rName) + ` + return composeConfig( + testAccAWSAPIGatewayV2AuthorizerConfig_basic(rName), + ` resource "aws_apigatewayv2_route" "test" { api_id = aws_apigatewayv2_api.test.id route_key = "$connect" @@ -494,22 +591,26 @@ resource "aws_apigatewayv2_route" "test" { authorization_type = "CUSTOM" authorizer_id = aws_apigatewayv2_authorizer.test.id } -` +`) } func testAccAWSAPIGatewayV2RouteConfig_authorizerUpdated(rName string) string { - return testAccAWSAPIGatewayV2AuthorizerConfig_basic(rName) + ` + return composeConfig( + testAccAWSAPIGatewayV2AuthorizerConfig_basic(rName), + ` resource "aws_apigatewayv2_route" "test" { api_id = aws_apigatewayv2_api.test.id route_key = "$connect" authorization_type = "AWS_IAM" } -` +`) } func testAccAWSAPIGatewayV2RouteConfig_jwtAuthorization(rName string) string { - return testAccAWSAPIGatewayV2AuthorizerConfig_jwt(rName) + ` + return composeConfig( + testAccAWSAPIGatewayV2AuthorizerConfig_jwt(rName), + ` resource "aws_apigatewayv2_route" "test" { api_id = aws_apigatewayv2_api.test.id route_key = "GET /test" @@ -519,11 +620,13 @@ resource "aws_apigatewayv2_route" "test" { authorization_scopes = ["user.id", "user.email"] } -` +`) } func testAccAWSAPIGatewayV2RouteConfig_jwtAuthorizationUpdated(rName string) string { - return testAccAWSAPIGatewayV2AuthorizerConfig_jwt(rName) + ` + return composeConfig( + testAccAWSAPIGatewayV2AuthorizerConfig_jwt(rName), + ` resource "aws_apigatewayv2_route" "test" { api_id = aws_apigatewayv2_api.test.id route_key = "GET /test" @@ -533,7 +636,7 @@ resource "aws_apigatewayv2_route" "test" { authorization_scopes = ["user.email"] } -` +`) } func testAccAWSAPIGatewayV2RouteConfig_model(rName string) string { @@ -550,7 +653,9 @@ func testAccAWSAPIGatewayV2RouteConfig_model(rName string) string { } ` - return testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema) + ` + return composeConfig( + testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema), + ` resource "aws_apigatewayv2_route" "test" { api_id = aws_apigatewayv2_api.test.id route_key = "$default" @@ -561,7 +666,55 @@ resource "aws_apigatewayv2_route" "test" { "test" = aws_apigatewayv2_model.test.name } } -` +`) +} + +func testAccAWSAPIGatewayV2RouteConfig_noRequestParameters(rName string) string { + return composeConfig( + testAccAWSAPIGatewayV2RouteConfig_apiWebSocket(rName), + ` +resource "aws_apigatewayv2_route" "test" { + api_id = aws_apigatewayv2_api.test.id + route_key = "$connect" +} +`) +} + +func testAccAWSAPIGatewayV2RouteConfig_requestParameters(rName string) string { + return composeConfig( + testAccAWSAPIGatewayV2RouteConfig_apiWebSocket(rName), + ` +resource "aws_apigatewayv2_route" "test" { + api_id = aws_apigatewayv2_api.test.id + route_key = "$connect" + + request_parameter { + request_parameter_key = "route.request.header.authorization" + required = true + } +} +`) +} + +func testAccAWSAPIGatewayV2RouteConfig_requestParametersUpdated(rName string) string { + return composeConfig( + testAccAWSAPIGatewayV2RouteConfig_apiWebSocket(rName), + ` +resource "aws_apigatewayv2_route" "test" { + api_id = aws_apigatewayv2_api.test.id + route_key = "$connect" + + request_parameter { + request_parameter_key = "route.request.header.authorization" + required = false + } + + request_parameter { + request_parameter_key = "route.request.querystring.authToken" + required = true + } +} +`) } func testAccAWSAPIGatewayV2RouteConfig_routeKey(rName, routeKey string) string { diff --git a/website/docs/r/apigatewayv2_route.html.markdown b/website/docs/r/apigatewayv2_route.html.markdown index 11d9a7463d2d..c491f0da86f2 100644 --- a/website/docs/r/apigatewayv2_route.html.markdown +++ b/website/docs/r/apigatewayv2_route.html.markdown @@ -68,9 +68,15 @@ Defaults to `NONE`. * `model_selection_expression` - (Optional) The [model selection expression](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html#apigateway-websocket-api-model-selection-expressions) for the route. Supported only for WebSocket APIs. * `operation_name` - (Optional) The operation name for the route. Must be between 1 and 64 characters in length. * `request_models` - (Optional) The request models for the route. Supported only for WebSocket APIs. +* `request_parameter` - (Optional) The request parameters for the route. Supported only for WebSocket APIs. * `route_response_selection_expression` - (Optional) The [route response selection expression](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html#apigateway-websocket-api-route-response-selection-expressions) for the route. Supported only for WebSocket APIs. * `target` - (Optional) The target for the route, of the form `integrations/`*`IntegrationID`*, where *`IntegrationID`* is the identifier of an [`aws_apigatewayv2_integration`](apigatewayv2_integration.html) resource. +The `request_parameter` object supports the following: + +* `request_parameter_key` - (Required) Request parameter key. This is a [request data mapping parameter](https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-data-mapping.html#websocket-mapping-request-parameters). +* `required` - (Required) Boolean whether or not the parameter is required. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: