diff --git a/aws/provider.go b/aws/provider.go index 855bd4dca20..adfc1a701e4 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -322,6 +322,7 @@ func Provider() terraform.ResourceProvider { "aws_api_gateway_usage_plan_key": resourceAwsApiGatewayUsagePlanKey(), "aws_api_gateway_vpc_link": resourceAwsApiGatewayVpcLink(), "aws_api_gateway_v2_api": resourceAwsApiGateway2Api(), + "aws_api_gateway_v2_model": resourceAwsApiGateway2Model(), "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), "aws_appautoscaling_target": resourceAwsAppautoscalingTarget(), "aws_appautoscaling_policy": resourceAwsAppautoscalingPolicy(), diff --git a/aws/resource_aws_api_gateway2_model.go b/aws/resource_aws_api_gateway2_model.go new file mode 100644 index 00000000000..096bdd2a63c --- /dev/null +++ b/aws/resource_aws_api_gateway2_model.go @@ -0,0 +1,172 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apigatewayv2" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/structure" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsApiGateway2Model() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsApiGateway2ModelCreate, + Read: resourceAwsApiGateway2ModelRead, + Update: resourceAwsApiGateway2ModelUpdate, + Delete: resourceAwsApiGateway2ModelDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsApiGateway2ModelImport, + }, + + Schema: map[string]*schema.Schema{ + "api_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "content_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 256), + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 128), + validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9]+$`), "must be alphanumeric"), + ), + }, + "schema": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(0, 32768), + validation.ValidateJsonString, + ), + DiffSuppressFunc: suppressEquivalentJsonDiffs, + StateFunc: func(v interface{}) string { + json, _ := structure.NormalizeJsonString(v) + return json + }, + }, + }, + } +} + +func resourceAwsApiGateway2ModelCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + req := &apigatewayv2.CreateModelInput{ + ApiId: aws.String(d.Get("api_id").(string)), + ContentType: aws.String(d.Get("content_type").(string)), + Name: aws.String(d.Get("name").(string)), + Schema: aws.String(d.Get("schema").(string)), + } + if v, ok := d.GetOk("description"); ok { + req.Description = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating API Gateway v2 model: %s", req) + resp, err := conn.CreateModel(req) + if err != nil { + return fmt.Errorf("error creating API Gateway v2 model: %s", err) + } + + d.SetId(aws.StringValue(resp.ModelId)) + + return resourceAwsApiGateway2ModelRead(d, meta) +} + +func resourceAwsApiGateway2ModelRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + resp, err := conn.GetModel(&apigatewayv2.GetModelInput{ + ApiId: aws.String(d.Get("api_id").(string)), + ModelId: aws.String(d.Id()), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + log.Printf("[WARN] API Gateway v2 model (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("error reading API Gateway v2 model: %s", err) + } + + d.Set("content_type", resp.ContentType) + d.Set("description", resp.Description) + d.Set("name", resp.Name) + d.Set("schema", resp.Schema) + + return nil +} + +func resourceAwsApiGateway2ModelUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + req := &apigatewayv2.UpdateModelInput{ + ApiId: aws.String(d.Get("api_id").(string)), + ModelId: aws.String(d.Id()), + } + if d.HasChange("content_type") { + req.ContentType = aws.String(d.Get("content_type").(string)) + } + if d.HasChange("description") { + req.Description = aws.String(d.Get("description").(string)) + } + if d.HasChange("name") { + req.Name = aws.String(d.Get("name").(string)) + } + if d.HasChange("schema") { + req.Schema = aws.String(d.Get("schema").(string)) + } + + log.Printf("[DEBUG] Updating API Gateway v2 model: %s", req) + _, err := conn.UpdateModel(req) + if err != nil { + return fmt.Errorf("error updating API Gateway v2 model: %s", err) + } + + return resourceAwsApiGateway2ModelRead(d, meta) +} + +func resourceAwsApiGateway2ModelDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + log.Printf("[DEBUG] Deleting API Gateway v2 model (%s)", d.Id()) + _, err := conn.DeleteModel(&apigatewayv2.DeleteModelInput{ + ApiId: aws.String(d.Get("api_id").(string)), + ModelId: aws.String(d.Id()), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + return nil + } + if err != nil { + return fmt.Errorf("error deleting API Gateway v2 model: %s", err) + } + + return nil +} + +func resourceAwsApiGateway2ModelImport(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/model-id'", d.Id()) + } + + d.SetId(parts[1]) + d.Set("api_id", parts[0]) + + return []*schema.ResourceData{d}, nil +} diff --git a/aws/resource_aws_api_gateway2_model_test.go b/aws/resource_aws_api_gateway2_model_test.go new file mode 100644 index 00000000000..a9df3a71b1d --- /dev/null +++ b/aws/resource_aws_api_gateway2_model_test.go @@ -0,0 +1,225 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/apigatewayv2" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSAPIGateway2Model_basic(t *testing.T) { + resourceName := "aws_api_gateway_v2_model.test" + rName := fmt.Sprintf("terraformtestaccapigwv2%s", acctest.RandStringFromCharSet(9, acctest.CharSetAlphaNum)) + + schema := ` +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ExampleModel", + "type": "object", + "properties": { + "id": { + "type": "string" + } + } +} +` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGateway2ModelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGateway2ModelConfig_basic(rName, schema), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGateway2ModelExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "content_type", "application/json"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schema", schema), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGateway2ModelImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAPIGateway2Model_AllAttributes(t *testing.T) { + resourceName := "aws_api_gateway_v2_model.test" + rName := fmt.Sprintf("terraformtestaccapigwv2%s", acctest.RandStringFromCharSet(9, acctest.CharSetAlphaNum)) + + schema1 := ` +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ExampleModel1", + "type": "object", + "properties": { + "id": { + "type": "string" + } + } +} +` + schema2 := ` + { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ExampleModel", + "type": "object", + "properties": { + "ids": { + "type": "array", + "items":{ + "type": "integer" + } + } + } + } +` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGateway2ModelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGateway2ModelConfig_allAttributes(rName, schema1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGateway2ModelExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "content_type", "text/x-json"), + resource.TestCheckResourceAttr(resourceName, "description", "test"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schema", schema1), + ), + }, + { + Config: testAccAWSAPIGateway2ModelConfig_basic(rName, schema2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGateway2ModelExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "content_type", "application/json"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schema", schema2), + ), + }, + { + Config: testAccAWSAPIGateway2ModelConfig_allAttributes(rName, schema1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGateway2ModelExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "content_type", "text/x-json"), + resource.TestCheckResourceAttr(resourceName, "description", "test"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schema", schema1), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccAWSAPIGateway2ModelImportStateIdFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSAPIGateway2ModelDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_api_gateway_v2_model" { + continue + } + + _, err := conn.GetModel(&apigatewayv2.GetModelInput{ + ApiId: aws.String(rs.Primary.Attributes["api_id"]), + ModelId: aws.String(rs.Primary.ID), + }) + if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") { + continue + } + if err != nil { + return err + } + + return fmt.Errorf("API Gateway v2 model %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAWSAPIGateway2ModelExists(n string) 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 model ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + _, err := conn.GetModel(&apigatewayv2.GetModelInput{ + ApiId: aws.String(rs.Primary.Attributes["api_id"]), + ModelId: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + return nil + } +} + +func testAccAWSAPIGateway2ModelImportStateIdFunc(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 testAccAWSAPIGateway2ModelConfig_api(rName string) string { + return fmt.Sprintf(` +resource "aws_api_gateway_v2_api" "test" { + name = %[1]q + protocol_type = "WEBSOCKET" + route_selection_expression = "$request.body.action" +} +`, rName) +} + +func testAccAWSAPIGateway2ModelConfig_basic(rName, schema string) string { + return testAccAWSAPIGateway2ModelConfig_api(rName) + fmt.Sprintf(` +resource "aws_api_gateway_v2_model" "test" { + api_id = "${aws_api_gateway_v2_api.test.id}" + content_type = "application/json" + name = %[1]q + schema = %[2]q +} +`, rName, schema) +} + +func testAccAWSAPIGateway2ModelConfig_allAttributes(rName, schema string) string { + return testAccAWSAPIGateway2ModelConfig_api(rName) + fmt.Sprintf(` +resource "aws_api_gateway_v2_model" "test" { + api_id = "${aws_api_gateway_v2_api.test.id}" + content_type = "text/x-json" + name = %[1]q + description = "test" + schema = %[2]q +} +`, rName, schema) +} diff --git a/website/docs/r/api_gateway_v2_model.html.markdown b/website/docs/r/api_gateway_v2_model.html.markdown new file mode 100644 index 00000000000..f63650dcad9 --- /dev/null +++ b/website/docs/r/api_gateway_v2_model.html.markdown @@ -0,0 +1,58 @@ +--- +layout: "aws" +page_title: "AWS: aws_api_gateway_v2_model" +sidebar_current: "docs-aws-resource-api-gateway-v2-model" +description: |- + Manages an Amazon API Gateway Version 2 model. +--- + +# Resource: aws_api_gateway_v2_model + +Manages an Amazon API Gateway Version 2 [model](https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html#models-mappings-models). + +## Example Usage + +### Basic + +```hcl +resource "aws_api_gateway_v2_model" "example" { + api_id = "${aws_api_gateway_v2_api.example.id}" + content_type = "application/json" + name = "example" + + schema = <