From 42030072458dd921a572f389b6b121b48b6f1819 Mon Sep 17 00:00:00 2001 From: Luke Hoban Date: Thu, 20 Jul 2017 13:03:54 -0700 Subject: [PATCH 1/3] Add a body property to API Gateway RestAPI The body property allows supplying an Swagger/OpenAPI spec to a RestAPI to create the API definition using the routes and integrations defined in the spec. --- aws/resource_aws_api_gateway_rest_api.go | 29 ++++ aws/resource_aws_api_gateway_rest_api_test.go | 150 ++++++++++++++++++ .../docs/r/api_gateway_rest_api.html.markdown | 1 + 3 files changed, 180 insertions(+) diff --git a/aws/resource_aws_api_gateway_rest_api.go b/aws/resource_aws_api_gateway_rest_api.go index 02d84ded449..a135d5cf71f 100644 --- a/aws/resource_aws_api_gateway_rest_api.go +++ b/aws/resource_aws_api_gateway_rest_api.go @@ -37,6 +37,11 @@ func resourceAwsApiGatewayRestApi() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, + "body": { + Type: schema.TypeString, + Optional: true, + }, + "root_resource_id": { Type: schema.TypeString, Computed: true, @@ -76,6 +81,17 @@ func resourceAwsApiGatewayRestApiCreate(d *schema.ResourceData, meta interface{} d.SetId(*gateway.Id) + if body, ok := d.GetOk("body"); ok { + _, err := conn.PutRestApi(&apigateway.PutRestApiInput{ + RestApiId: gateway.Id, + Mode: aws.String("overwrite"), + Body: []byte(body.(string)), + }) + if err != nil { + return fmt.Errorf("Error putting API Gateway specification: %s", err) + } + } + if err = resourceAwsApiGatewayRestApiRefreshResources(d, meta); err != nil { return err } @@ -155,6 +171,19 @@ func resourceAwsApiGatewayRestApiUpdate(d *schema.ResourceData, meta interface{} conn := meta.(*AWSClient).apigateway log.Printf("[DEBUG] Updating API Gateway %s", d.Id()) + if d.HasChange("body") { + if body, ok := d.GetOk("body"); ok { + _, err := conn.PutRestApi(&apigateway.PutRestApiInput{ + RestApiId: aws.String(d.Id()), + Mode: aws.String("overwrite"), + Body: []byte(body.(string)), + }) + if err != nil { + return err + } + } + } + _, err := conn.UpdateRestApi(&apigateway.UpdateRestApiInput{ RestApiId: aws.String(d.Id()), PatchOperations: resourceAwsApiGatewayRestApiUpdateOperations(d), diff --git a/aws/resource_aws_api_gateway_rest_api_test.go b/aws/resource_aws_api_gateway_rest_api_test.go index 1052a60c04b..82bee930d9f 100644 --- a/aws/resource_aws_api_gateway_rest_api_test.go +++ b/aws/resource_aws_api_gateway_rest_api_test.go @@ -56,6 +56,47 @@ func TestAccAWSAPIGatewayRestApi_basic(t *testing.T) { }) } +func TestAccAWSAPIGatewayRestApi_openapi(t *testing.T) { + var conf apigateway.RestApi + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayRestAPIDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayRestAPIConfigOpenAPI, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayRestAPIExists("aws_api_gateway_rest_api.test", &conf), + testAccCheckAWSAPIGatewayRestAPINameAttribute(&conf, "test"), + testAccCheckAWSAPIGatewayRestAPIRoutes(&conf, []string{"/", "/test"}), + resource.TestCheckResourceAttr( + "aws_api_gateway_rest_api.test", "name", "test"), + resource.TestCheckResourceAttr( + "aws_api_gateway_rest_api.test", "description", ""), + resource.TestCheckResourceAttrSet( + "aws_api_gateway_rest_api.test", "created_date"), + resource.TestCheckNoResourceAttr( + "aws_api_gateway_rest_api.test", "binary_media_types"), + ), + }, + + { + Config: testAccAWSAPIGatewayRestAPIUpdateConfigOpenAPI, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayRestAPIExists("aws_api_gateway_rest_api.test", &conf), + testAccCheckAWSAPIGatewayRestAPINameAttribute(&conf, "test"), + testAccCheckAWSAPIGatewayRestAPIRoutes(&conf, []string{"/", "/update"}), + resource.TestCheckResourceAttr( + "aws_api_gateway_rest_api.test", "name", "test"), + resource.TestCheckResourceAttrSet( + "aws_api_gateway_rest_api.test", "created_date"), + ), + }, + }, + }) +} + func testAccCheckAWSAPIGatewayRestAPINameAttribute(conf *apigateway.RestApi, name string) resource.TestCheckFunc { return func(s *terraform.State) error { if *conf.Name != name { @@ -76,6 +117,37 @@ func testAccCheckAWSAPIGatewayRestAPIDescriptionAttribute(conf *apigateway.RestA } } +func testAccCheckAWSAPIGatewayRestAPIRoutes(conf *apigateway.RestApi, routes []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigateway + + resp, err := conn.GetResources(&apigateway.GetResourcesInput{ + RestApiId: conf.Id, + }) + if err != nil { + return err + } + + actualRoutePaths := map[string]bool{} + for _, resource := range resp.Items { + actualRoutePaths[*resource.Path] = true + } + + for _, route := range routes { + if _, ok := actualRoutePaths[route]; !ok { + return fmt.Errorf("Expected path %v but did not find it in %v", route, actualRoutePaths) + } + delete(actualRoutePaths, route) + } + + if len(actualRoutePaths) > 0 { + return fmt.Errorf("Found unexpected paths %v", actualRoutePaths) + } + + return nil + } +} + func testAccCheckAWSAPIGatewayRestAPIExists(n string, res *apigateway.RestApi) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -144,3 +216,81 @@ resource "aws_api_gateway_rest_api" "test" { binary_media_types = ["application/octet-stream"] } ` + +const testAccAWSAPIGatewayRestAPIConfigOpenAPI = ` +resource "aws_api_gateway_rest_api" "test" { + name = "test" + body = < Date: Fri, 25 Aug 2017 16:26:26 -0700 Subject: [PATCH 2/3] Respond to PR feedback on #1197 --- aws/resource_aws_api_gateway_rest_api.go | 11 +++++++---- aws/resource_aws_api_gateway_rest_api_test.go | 16 ++++++++-------- .../docs/r/api_gateway_rest_api.html.markdown | 11 +++++++++++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_api_gateway_rest_api.go b/aws/resource_aws_api_gateway_rest_api.go index a135d5cf71f..3b555b6b9d7 100644 --- a/aws/resource_aws_api_gateway_rest_api.go +++ b/aws/resource_aws_api_gateway_rest_api.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -82,13 +83,14 @@ func resourceAwsApiGatewayRestApiCreate(d *schema.ResourceData, meta interface{} d.SetId(*gateway.Id) if body, ok := d.GetOk("body"); ok { + log.Printf("[DEBUG] Initializing API Gateway from OpenAPI spec %s", d.Id()) _, err := conn.PutRestApi(&apigateway.PutRestApiInput{ RestApiId: gateway.Id, - Mode: aws.String("overwrite"), + Mode: aws.String(apigateway.PutModeOverwrite), Body: []byte(body.(string)), }) if err != nil { - return fmt.Errorf("Error putting API Gateway specification: %s", err) + return errwrap.Wrapf("Error creating API Gateway specification: {{err}}", err) } } @@ -173,13 +175,14 @@ func resourceAwsApiGatewayRestApiUpdate(d *schema.ResourceData, meta interface{} if d.HasChange("body") { if body, ok := d.GetOk("body"); ok { + log.Printf("[DEBUG] Updating API Gateway from OpenAPI spec: %s", d.Id()) _, err := conn.PutRestApi(&apigateway.PutRestApiInput{ RestApiId: aws.String(d.Id()), - Mode: aws.String("overwrite"), + Mode: aws.String(apigateway.PutModeOverwrite), Body: []byte(body.(string)), }) if err != nil { - return err + return errwrap.Wrapf("Error updating API Gateway specification: {{err}}", err) } } } diff --git a/aws/resource_aws_api_gateway_rest_api_test.go b/aws/resource_aws_api_gateway_rest_api_test.go index 82bee930d9f..00be39cc5d2 100644 --- a/aws/resource_aws_api_gateway_rest_api_test.go +++ b/aws/resource_aws_api_gateway_rest_api_test.go @@ -222,10 +222,10 @@ resource "aws_api_gateway_rest_api" "test" { name = "test" body = < Date: Tue, 29 Aug 2017 09:30:28 +0200 Subject: [PATCH 3/3] Update api_gateway_rest_api.html.markdown --- website/docs/r/api_gateway_rest_api.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/api_gateway_rest_api.html.markdown b/website/docs/r/api_gateway_rest_api.html.markdown index 23d0e8ac32b..29c663db235 100644 --- a/website/docs/r/api_gateway_rest_api.html.markdown +++ b/website/docs/r/api_gateway_rest_api.html.markdown @@ -28,7 +28,7 @@ The following arguments are supported: * `binary_media_types` - (Optional) The list of binary media types supported by the RestApi. By default, the RestApi supports only UTF-8-encoded text payloads. * `body` - (Optional) An OpenAPI specification that defines the set of routes and integrations to create as part of the REST API. -__Note__: If the `body` argument is provided, the OpenAPI specification will be used to configure the resources, methods and integrations for the Rest API. If this argument is provided, the following resources should not be configured manually for the same REST API, as updates may cause manual resource updates to be overwritten: +__Note__: If the `body` argument is provided, the OpenAPI specification will be used to configure the resources, methods and integrations for the Rest API. If this argument is provided, the following resources should not be managed as separate ones, as updates may cause manual resource updates to be overwritten: * `aws_api_gateway_resource` * `aws_api_gateway_method`