diff --git a/aws/provider.go b/aws/provider.go index f44c84c4609..f039444f02a 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -369,6 +369,7 @@ func Provider() terraform.ResourceProvider { "aws_apigatewayv2_authorizer": resourceAwsApiGatewayV2Authorizer(), "aws_apigatewayv2_integration": resourceAwsApiGatewayV2Integration(), "aws_apigatewayv2_model": resourceAwsApiGatewayV2Model(), + "aws_apigatewayv2_route": resourceAwsApiGatewayV2Route(), "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), "aws_appautoscaling_target": resourceAwsAppautoscalingTarget(), "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), diff --git a/aws/resource_aws_apigatewayv2_route.go b/aws/resource_aws_apigatewayv2_route.go new file mode 100644 index 00000000000..185973467b9 --- /dev/null +++ b/aws/resource_aws_apigatewayv2_route.go @@ -0,0 +1,251 @@ +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 resourceAwsApiGatewayV2Route() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsApiGatewayV2RouteCreate, + Read: resourceAwsApiGatewayV2RouteRead, + Update: resourceAwsApiGatewayV2RouteUpdate, + Delete: resourceAwsApiGatewayV2RouteDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsApiGatewayV2RouteImport, + }, + + Schema: map[string]*schema.Schema{ + "api_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "api_key_required": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "authorization_scopes": { + Type: schema.TypeSet, + Optional: true, + 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), + }, + "authorizer_id": { + Type: schema.TypeString, + Optional: true, + }, + "model_selection_expression": { + Type: schema.TypeString, + Optional: true, + }, + "operation_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 64), + }, + "request_models": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "route_key": { + Type: schema.TypeString, + Required: true, + }, + "route_response_selection_expression": { + Type: schema.TypeString, + Optional: true, + }, + "target": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + }, + } +} + +func resourceAwsApiGatewayV2RouteCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + req := &apigatewayv2.CreateRouteInput{ + ApiId: aws.String(d.Get("api_id").(string)), + ApiKeyRequired: aws.Bool(d.Get("api_key_required").(bool)), + AuthorizationType: aws.String(d.Get("authorization_type").(string)), + RouteKey: aws.String(d.Get("route_key").(string)), + } + if v, ok := d.GetOk("authorization_scopes"); ok { + req.AuthorizationScopes = expandStringSet(v.(*schema.Set)) + } + if v, ok := d.GetOk("authorizer_id"); ok { + req.AuthorizerId = aws.String(v.(string)) + } + if v, ok := d.GetOk("model_selection_expression"); ok { + req.ModelSelectionExpression = aws.String(v.(string)) + } + if v, ok := d.GetOk("operation_name"); ok { + req.OperationName = aws.String(v.(string)) + } + if v, ok := d.GetOk("request_models"); ok { + req.RequestModels = stringMapToPointers(v.(map[string]interface{})) + } + if v, ok := d.GetOk("route_response_selection_expression"); ok { + req.RouteResponseSelectionExpression = aws.String(v.(string)) + } + if v, ok := d.GetOk("target"); ok { + req.Target = aws.String(v.(string)) + } + + 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) + } + + d.SetId(aws.StringValue(resp.RouteId)) + + return resourceAwsApiGatewayV2RouteRead(d, meta) +} + +func resourceAwsApiGatewayV2RouteRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + resp, err := conn.GetRoute(&apigatewayv2.GetRouteInput{ + ApiId: aws.String(d.Get("api_id").(string)), + RouteId: aws.String(d.Id()), + }) + if isAWSErr(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) + } + + 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) + } + 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) + } + d.Set("route_key", resp.RouteKey) + d.Set("route_response_selection_expression", resp.RouteResponseSelectionExpression) + d.Set("target", resp.Target) + + return nil +} + +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_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", err) + } + + return resourceAwsApiGatewayV2RouteRead(d, meta) +} + +func resourceAwsApiGatewayV2RouteDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + log.Printf("[DEBUG] Deleting API Gateway v2 route (%s)", d.Id()) + _, err := conn.DeleteRoute(&apigatewayv2.DeleteRouteInput{ + ApiId: aws.String(d.Get("api_id").(string)), + RouteId: aws.String(d.Id()), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + return nil + } + if err != nil { + return fmt.Errorf("error deleting API Gateway v2 route: %s", err) + } + + return nil +} + +func resourceAwsApiGatewayV2RouteImport(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/route-id'", d.Id()) + } + + apiId := parts[0] + routeId := parts[1] + + conn := meta.(*AWSClient).apigatewayv2conn + + resp, err := conn.GetRoute(&apigatewayv2.GetRouteInput{ + ApiId: aws.String(apiId), + RouteId: aws.String(routeId), + }) + if err != nil { + return nil, err + } + + if aws.BoolValue(resp.ApiGatewayManaged) { + return nil, fmt.Errorf("API Gateway v2 route (%s) was created via quick create", routeId) + } + + d.SetId(routeId) + d.Set("api_id", apiId) + + return []*schema.ResourceData{d}, nil +} diff --git a/aws/resource_aws_apigatewayv2_route_test.go b/aws/resource_aws_apigatewayv2_route_test.go new file mode 100644 index 00000000000..6023767df98 --- /dev/null +++ b/aws/resource_aws_apigatewayv2_route_test.go @@ -0,0 +1,535 @@ +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 TestAccAWSAPIGatewayV2Route_basic(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) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteConfig_basic(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, "route_key", "$default"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2RouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Route_disappears(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) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + testAccCheckAWSAPIGatewayV2RouteDisappears(&apiId, &v), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Route_Authorizer(t *testing.T) { + var apiId string + var v apigatewayv2.GetRouteOutput + resourceName := "aws_apigatewayv2_route.test" + authorizerResourceName := "aws_apigatewayv2_authorizer.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteConfig_authorizer(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), + resource.TestCheckResourceAttr(resourceName, "authorization_scopes.#", "0"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeCustom), + resource.TestCheckResourceAttrPair(resourceName, "authorizer_id", authorizerResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "operation_name", ""), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + 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_authorizerUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), + resource.TestCheckResourceAttr(resourceName, "authorization_scopes.#", "0"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeAwsIam), + 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, "route_key", "$connect"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Route_JwtAuthorization(t *testing.T) { + var apiId string + var v apigatewayv2.GetRouteOutput + resourceName := "aws_apigatewayv2_route.test" + authorizerResourceName := "aws_apigatewayv2_authorizer.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteConfig_jwtAuthorization(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), + resource.TestCheckResourceAttr(resourceName, "authorization_scopes.#", "2"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeJwt), + resource.TestCheckResourceAttrPair(resourceName, "authorizer_id", authorizerResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "operation_name", ""), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + 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_jwtAuthorizationUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "false"), + resource.TestCheckResourceAttr(resourceName, "authorization_scopes.#", "1"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeJwt), + resource.TestCheckResourceAttrPair(resourceName, "authorizer_id", authorizerResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "operation_name", ""), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "route_key", "$connect"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Route_Model(t *testing.T) { + var apiId string + var v apigatewayv2.GetRouteOutput + resourceName := "aws_apigatewayv2_route.test" + modelResourceName := "aws_apigatewayv2_model.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: testAccCheckAWSAPIGatewayV2RouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteConfig_model(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", "action"), + resource.TestCheckResourceAttr(resourceName, "operation_name", ""), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "1"), + resource.TestCheckResourceAttrPair(resourceName, "request_models.test", modelResourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2RouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Route_SimpleAttributes(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) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteConfig_simpleAttributes(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "true"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeNone), + resource.TestCheckResourceAttr(resourceName, "authorizer_id", ""), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "operation_name", "GET"), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", "$default"), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + { + Config: testAccAWSAPIGatewayV2RouteConfig_basic(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_parameters.%", "0"), + resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + { + Config: testAccAWSAPIGatewayV2RouteConfig_simpleAttributes(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2RouteExists(resourceName, &apiId, &v), + resource.TestCheckResourceAttr(resourceName, "api_key_required", "true"), + resource.TestCheckResourceAttr(resourceName, "authorization_type", apigatewayv2.AuthorizationTypeNone), + resource.TestCheckResourceAttr(resourceName, "authorizer_id", ""), + resource.TestCheckResourceAttr(resourceName, "model_selection_expression", ""), + resource.TestCheckResourceAttr(resourceName, "operation_name", "GET"), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "0"), + resource.TestCheckResourceAttr(resourceName, "route_key", "$default"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", "$default"), + resource.TestCheckResourceAttr(resourceName, "target", ""), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2RouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Route_Target(t *testing.T) { + var apiId string + var v apigatewayv2.GetRouteOutput + resourceName := "aws_apigatewayv2_route.test" + integrationResourceName := "aws_apigatewayv2_integration.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2RouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2RouteConfig_target(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, "route_key", "$default"), + resource.TestCheckResourceAttr(resourceName, "route_response_selection_expression", ""), + testAccCheckAWSAPIGatewayV2RouteTarget(resourceName, integrationResourceName), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGatewayV2RouteImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSAPIGatewayV2RouteDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_apigatewayv2_route" { + continue + } + + _, err := conn.GetRoute(&apigatewayv2.GetRouteInput{ + ApiId: aws.String(rs.Primary.Attributes["api_id"]), + RouteId: aws.String(rs.Primary.ID), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + continue + } + if err != nil { + return err + } + + return fmt.Errorf("API Gateway v2 route %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAWSAPIGatewayV2RouteDisappears(apiId *string, v *apigatewayv2.GetRouteOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + _, err := conn.DeleteRoute(&apigatewayv2.DeleteRouteInput{ + ApiId: apiId, + RouteId: v.RouteId, + }) + + return err + } +} + +func testAccCheckAWSAPIGatewayV2RouteExists(n string, vApiId *string, v *apigatewayv2.GetRouteOutput) 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 ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + apiId := aws.String(rs.Primary.Attributes["api_id"]) + resp, err := conn.GetRoute(&apigatewayv2.GetRouteInput{ + ApiId: apiId, + RouteId: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *vApiId = *apiId + *v = *resp + + return nil + } +} + +func testAccAWSAPIGatewayV2RouteImportStateIdFunc(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 testAccCheckAWSAPIGatewayV2RouteTarget(resourceName, integrationResourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[integrationResourceName] + if !ok { + return fmt.Errorf("Not Found: %s", integrationResourceName) + } + + return resource.TestCheckResourceAttr(resourceName, "target", fmt.Sprintf("integrations/%s", rs.Primary.ID))(s) + } +} + +func testAccAWSAPIGatewayV2RouteConfig_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 testAccAWSAPIGatewayV2RouteConfig_basic(rName string) string { + return testAccAWSAPIGatewayV2RouteConfig_apiWebSocket(rName) + fmt.Sprintf(` +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) + fmt.Sprintf(` +resource "aws_apigatewayv2_route" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + route_key = "$connect" + + authorization_type = "CUSTOM" + authorizer_id = "${aws_apigatewayv2_authorizer.test.id}" +} +`) +} + +func testAccAWSAPIGatewayV2RouteConfig_authorizerUpdated(rName string) string { + return testAccAWSAPIGatewayV2AuthorizerConfig_basic(rName) + fmt.Sprintf(` +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) + fmt.Sprintf(` +resource "aws_apigatewayv2_route" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + route_key = "$connect" + + authorization_type = "JWT" + authorizer_id = "${aws_apigatewayv2_authorizer.test.id}" + + authorization_scopes = ["user.id", "user.email"] +} +`) +} + +func testAccAWSAPIGatewayV2RouteConfig_jwtAuthorizationUpdated(rName string) string { + return testAccAWSAPIGatewayV2AuthorizerConfig_jwt(rName) + fmt.Sprintf(` +resource "aws_apigatewayv2_route" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + route_key = "$connect" + + authorization_type = "JWT" + authorizer_id = "${aws_apigatewayv2_authorizer.test.id}" + + authorization_scopes = ["user.email"] +} +`) +} + +func testAccAWSAPIGatewayV2RouteConfig_model(rName string) string { + schema := ` +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ExampleModel", + "type": "object", + "properties": { + "id": { + "type": "string" + } + } +} +` + + return testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema) + fmt.Sprintf(` +resource "aws_apigatewayv2_route" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + route_key = "$default" + + model_selection_expression = "action" + + request_models = { + "test" = "${aws_apigatewayv2_model.test.name}" + } +} +`) +} + +// Simple attributes - No authorization, models or targets. +func testAccAWSAPIGatewayV2RouteConfig_simpleAttributes(rName string) string { + return testAccAWSAPIGatewayV2RouteConfig_apiWebSocket(rName) + fmt.Sprintf(` +resource "aws_apigatewayv2_route" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + route_key = "$default" + + api_key_required = true + operation_name = "GET" + route_response_selection_expression = "$default" +} +`) +} + +func testAccAWSAPIGatewayV2RouteConfig_target(rName string) string { + return testAccAWSAPIGatewayV2IntegrationConfig_basic(rName) + fmt.Sprintf(` +resource "aws_apigatewayv2_route" "test" { + api_id = "${aws_apigatewayv2_api.test.id}" + route_key = "$default" + + target = "integrations/${aws_apigatewayv2_integration.test.id}" +} +`) +} diff --git a/website/aws.erb b/website/aws.erb index 8e1aeebabcb..ba1410291fc 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -227,6 +227,9 @@
  • aws_apigatewayv2_model
  • +
  • + aws_apigatewayv2_route +
  • diff --git a/website/docs/r/apigatewayv2_integration.html.markdown b/website/docs/r/apigatewayv2_integration.html.markdown index 0596f45650d..386fd66d3da 100644 --- a/website/docs/r/apigatewayv2_integration.html.markdown +++ b/website/docs/r/apigatewayv2_integration.html.markdown @@ -82,3 +82,5 @@ In addition to all arguments above, the following attributes are exported: ``` $ terraform import aws_apigatewayv2_integration.example aabbccddee/1122334 ``` + +-> **Note:** The API Gateway managed integration created as part of [_quick_create_](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-basic-concept.html#apigateway-definition-quick-create) cannot be imported. diff --git a/website/docs/r/apigatewayv2_route.html.markdown b/website/docs/r/apigatewayv2_route.html.markdown new file mode 100644 index 00000000000..2f700f62516 --- /dev/null +++ b/website/docs/r/apigatewayv2_route.html.markdown @@ -0,0 +1,58 @@ +--- +subcategory: "API Gateway v2 (WebSocket and HTTP APIs)" +layout: "aws" +page_title: "AWS: aws_apigatewayv2_route" +description: |- + Manages an Amazon API Gateway Version 2 route. +--- + +# Resource: aws_apigatewayv2_route + +Manages an Amazon API Gateway Version 2 route. +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_route" "example" { + api_id = "${aws_apigatewayv2_api.example.id}" + route_key = "$default" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `api_id` - (Required) The API identifier. +* `route_key` - (Required) The route key for the route. +* `api_key_required` - (Optional) Boolean whether an API key is required for the route. Defaults to `false`. +* `authorization_scopes` - (Optional) The authorization scopes supported by this route. The scopes are used with a JWT authorizer to authorize the method invocation. +* `authorization_type` - (Optional) The authorization type for the route. +For WebSocket APIs, valid values are `NONE` for open access, `AWS_IAM` for using AWS IAM permissions, and `CUSTOM` for using a Lambda authorizer. +For HTTP APIs, valid values are `NONE` for open access, or `JWT` for using JSON Web Tokens. +Defaults to `NONE`. +* `authorizer_id` - (Optional) The identifier of the [`aws_apigatewayv2_authorizer`](/docs/providers/aws/r/apigatewayv2_authorizer.html) resource to be associated with this route, if the authorizationType is `CUSTOM`. +* `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. +* `operation_name` - (Optional) The operation name for the route. +* `request_models` - (Optional) The request models for the route. +* `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. +* `target` - (Optional) The target for the route. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The route identifier. + +## Import + +`aws_apigatewayv2_route` can be imported by using the API identifier and route identifier, e.g. + +``` +$ terraform import aws_apigatewayv2_route.example aabbccddee/1122334 +``` + +-> **Note:** The API Gateway managed route created as part of [_quick_create_](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-basic-concept.html#apigateway-definition-quick-create) cannot be imported.