From 2fec9449e58dedf3a317b0dcf0c4b6e7a48c7cdd Mon Sep 17 00:00:00 2001 From: Vincent Ting Date: Thu, 23 Apr 2020 22:33:14 +0800 Subject: [PATCH 1/2] resource/apigatewayv2: Add support for `body` parameter --- aws/diff_suppress_funcs.go | 2 + aws/resource_aws_apigatewayv2_api.go | 77 +++ aws/resource_aws_apigatewayv2_api_test.go | 495 ++++++++++++++++++ aws/validators.go | 2 + website/docs/r/apigatewayv2_api.html.markdown | 8 + 5 files changed, 584 insertions(+) diff --git a/aws/diff_suppress_funcs.go b/aws/diff_suppress_funcs.go index b192d208f8a..c3fae0590ff 100644 --- a/aws/diff_suppress_funcs.go +++ b/aws/diff_suppress_funcs.go @@ -115,6 +115,8 @@ func suppressCloudFormationTemplateBodyDiffs(k, old, new string, d *schema.Resou return normalizedOld == normalizedNew } +var suppressOpenAPISpecificationDiffs = suppressCloudFormationTemplateBodyDiffs + // suppressEqualCIDRBlockDiffs provides custom difference suppression for CIDR blocks // that have different string values but represent the same CIDR. func suppressEqualCIDRBlockDiffs(k, old, new string, d *schema.ResourceData) bool { diff --git a/aws/resource_aws_apigatewayv2_api.go b/aws/resource_aws_apigatewayv2_api.go index 63048054e34..2658677d04e 100644 --- a/aws/resource_aws_apigatewayv2_api.go +++ b/aws/resource_aws_apigatewayv2_api.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "reflect" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" @@ -101,6 +102,12 @@ func resourceAwsApiGatewayV2Api() *schema.Resource { Required: true, ValidateFunc: validation.StringLenBetween(1, 128), }, + "body": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: suppressOpenAPISpecificationDiffs, + ValidateFunc: validateOpenAPISpecification, + }, "protocol_type": { Type: schema.TypeString, Required: true, @@ -135,6 +142,64 @@ func resourceAwsApiGatewayV2Api() *schema.Resource { } } +func resourceAwsAPIGatewayV2ImportOpenAPI(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).apigatewayv2conn + + if body, ok := d.GetOk("body"); ok { + revertReq := &apigatewayv2.UpdateApiInput{ + ApiId: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + Description: aws.String(d.Get("description").(string)), + Version: aws.String(d.Get("version").(string)), + } + + log.Printf("[DEBUG] Updating API Gateway from OpenAPI spec %s", d.Id()) + importReq := &apigatewayv2.ReimportApiInput{ + ApiId: aws.String(d.Id()), + Body: aws.String(body.(string)), + } + + _, err := conn.ReimportApi(importReq) + + if err != nil { + return fmt.Errorf("error importing API Gateway v2 API (%s) OpenAPI specification: %s", d.Id(), err) + } + + tags := d.Get("tags") + corsConfiguration := d.Get("cors_configuration") + + if err := resourceAwsApiGatewayV2ApiRead(d, meta); err != nil { + return err + } + + if !reflect.DeepEqual(corsConfiguration, d.Get("cors_configuration")) { + if len(corsConfiguration.([]interface{})) == 0 { + log.Printf("[DEBUG] Deleting CORS configuration for API Gateway v2 API (%s)", d.Id()) + _, err := conn.DeleteCorsConfiguration(&apigatewayv2.DeleteCorsConfigurationInput{ + ApiId: aws.String(d.Id()), + }) + if err != nil { + return fmt.Errorf("error deleting CORS configuration for API Gateway v2 API (%s): %s", d.Id(), err) + } + } else { + revertReq.CorsConfiguration = expandApiGateway2CorsConfiguration(corsConfiguration.([]interface{})) + } + } + + if err := keyvaluetags.Apigatewayv2UpdateTags(conn, d.Get("arn").(string), d.Get("tags"), tags); err != nil { + return fmt.Errorf("error updating API Gateway v2 API (%s) tags: %s", d.Id(), err) + } + + log.Printf("[DEBUG] Reverting API Gateway v2 API: %s", revertReq) + _, err = conn.UpdateApi(revertReq) + if err != nil { + return fmt.Errorf("error updating API Gateway v2 API (%s): %s", d.Id(), err) + } + } + + return nil +} + func resourceAwsApiGatewayV2ApiCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).apigatewayv2conn @@ -177,6 +242,11 @@ func resourceAwsApiGatewayV2ApiCreate(d *schema.ResourceData, meta interface{}) d.SetId(aws.StringValue(resp.ApiId)) + err = resourceAwsAPIGatewayV2ImportOpenAPI(d, meta) + if err != nil { + return err + } + return resourceAwsApiGatewayV2ApiRead(d, meta) } @@ -286,6 +356,13 @@ func resourceAwsApiGatewayV2ApiUpdate(d *schema.ResourceData, meta interface{}) } } + if d.HasChange("body") { + err := resourceAwsAPIGatewayV2ImportOpenAPI(d, meta) + if err != nil { + return err + } + } + return resourceAwsApiGatewayV2ApiRead(d, meta) } diff --git a/aws/resource_aws_apigatewayv2_api_test.go b/aws/resource_aws_apigatewayv2_api_test.go index 8b25d0e72a3..ad1d5ecbcfe 100644 --- a/aws/resource_aws_apigatewayv2_api_test.go +++ b/aws/resource_aws_apigatewayv2_api_test.go @@ -317,6 +317,231 @@ func TestAccAWSAPIGatewayV2Api_AllAttributesHttp(t *testing.T) { }) } +func TestAccAWSAPIGatewayV2Api_Openapi(t *testing.T) { + var v apigatewayv2.GetApiOutput + resourceName := "aws_apigatewayv2_api.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2ApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2ApiConfig_OpenAPI(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "api_endpoint"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "version", ""), + testAccMatchResourceAttrRegionalARNNoAccount(resourceName, "arn", "apigateway", regexp.MustCompile(`/apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + testAccCheckAWSAPIGatewayV2ApiRoutes(&v, []string{"GET /test"}), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"body"}, + }, + { + Config: testAccAWSAPIGatewayV2ApiConfig_UpdatedOpenAPIYaml(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "version", ""), + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + testAccCheckAWSAPIGatewayV2ApiRoutes(&v, []string{"GET /update"}), + ), + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Api_Openapi_WithTags(t *testing.T) { + var v apigatewayv2.GetApiOutput + resourceName := "aws_apigatewayv2_api.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2ApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2ApiConfig_OpenAPIYaml_tags(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "api_endpoint"), + testAccMatchResourceAttrRegionalARNNoAccount(resourceName, "arn", "apigateway", regexp.MustCompile(`/apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + testAccCheckAWSAPIGatewayV2ApiRoutes(&v, []string{"GET /test"}), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"body"}, + }, + { + Config: testAccAWSAPIGatewayV2ApiConfig_OpenAPIYaml_tagsUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + testAccCheckAWSAPIGatewayV2ApiRoutes(&v, []string{"GET /update"}), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1U"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2U"), + ), + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Api_Openapi_WithCorsConfiguration(t *testing.T) { + var v apigatewayv2.GetApiOutput + resourceName := "aws_apigatewayv2_api.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2ApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2ApiConfig_OpenAPIYaml_corsConfiguration(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "api_endpoint"), + testAccMatchResourceAttrRegionalARNNoAccount(resourceName, "arn", "apigateway", regexp.MustCompile(`/apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_methods.#", "1"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_methods.*", "delete"), + resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_origins.#", "1"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_origins.*", "https://www.google.de"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"body"}, + }, + { + Config: testAccAWSAPIGatewayV2ApiConfig_OpenAPIYaml_corsConfigurationUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + testAccCheckAWSAPIGatewayV2ApiRoutes(&v, []string{"GET /update"}), + resource.TestCheckResourceAttr(resourceName, "cors_configuration.#", "0"), + ), + }, + { + Config: testAccAWSAPIGatewayV2ApiConfig_OpenAPIYaml_corsConfigurationUpdated2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + testAccCheckAWSAPIGatewayV2ApiRoutes(&v, []string{"GET /update"}), + resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_methods.#", "2"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_methods.*", "get"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_methods.*", "put"), + resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_origins.#", "2"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_origins.*", "https://www.example.com"), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_origins.*", "https://www.google.de"), + ), + }, + }, + }) +} + +func TestAccAWSAPIGatewayV2Api_OpenapiWithMoreFields(t *testing.T) { + var v apigatewayv2.GetApiOutput + resourceName := "aws_apigatewayv2_api.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayV2ApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayV2ApiConfig_OpenAPIYaml(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttrSet(resourceName, "api_endpoint"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "version", ""), + testAccMatchResourceAttrRegionalARNNoAccount(resourceName, "arn", "apigateway", regexp.MustCompile(`/apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + testAccCheckAWSAPIGatewayV2ApiRoutes(&v, []string{"GET /test"}), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"body"}, + }, + { + Config: testAccAWSAPIGatewayV2ApiConfig_UpdatedOpenAPI2(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "description", "description test"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "version", "2017-04-21T04:08:08Z"), + testAccCheckAWSAPIGatewayV2ApiExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "protocol_type", apigatewayv2.ProtocolTypeHttp), + testAccCheckAWSAPIGatewayV2ApiRoutes(&v, []string{"GET /update"}), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"body"}, + }, + }, + }) +} + +func testAccCheckAWSAPIGatewayV2ApiRoutes(v *apigatewayv2.GetApiOutput, routes []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn + + resp, err := conn.GetRoutes(&apigatewayv2.GetRoutesInput{ + ApiId: v.ApiId, + }) + if err != nil { + return err + } + + actualRoutePaths := map[string]bool{} + for _, route := range resp.Items { + actualRoutePaths[*route.RouteKey] = 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 TestAccAWSAPIGatewayV2Api_Tags(t *testing.T) { var v apigatewayv2.GetApiOutput resourceName := "aws_apigatewayv2_api.test" @@ -765,3 +990,273 @@ resource "aws_apigatewayv2_api" "test" { } `, rName) } + +func testAccAWSAPIGatewayV2ApiConfig_OpenAPI(rName string) string { + return fmt.Sprintf(` +resource "aws_apigatewayv2_api" "test" { + name = "%s" + protocol_type = "HTTP" + body = < Date: Fri, 14 Aug 2020 08:17:00 +0800 Subject: [PATCH 2/2] refactor: renaming CloudFormationTemplate to JsonOrYaml --- aws/data_source_aws_cloudformation_stack.go | 2 +- aws/diff_suppress_funcs.go | 8 +++----- aws/diff_suppress_funcs_test.go | 4 ++-- aws/resource_aws_apigatewayv2_api.go | 4 ++-- aws/resource_aws_cloudformation_stack.go | 10 +++++----- aws/resource_aws_cloudformation_stack_set.go | 4 ++-- aws/structure.go | 2 +- aws/structure_test.go | 6 +++--- aws/validators.go | 4 +--- aws/validators_test.go | 11 ++++++----- 10 files changed, 26 insertions(+), 29 deletions(-) diff --git a/aws/data_source_aws_cloudformation_stack.go b/aws/data_source_aws_cloudformation_stack.go index 4e67bb9de34..f84b8eccb20 100644 --- a/aws/data_source_aws_cloudformation_stack.go +++ b/aws/data_source_aws_cloudformation_stack.go @@ -113,7 +113,7 @@ func dataSourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface return err } - template, err := normalizeCloudFormationTemplate(*tOut.TemplateBody) + template, err := normalizeJsonOrYamlString(*tOut.TemplateBody) if err != nil { return fmt.Errorf("template body contains an invalid JSON or YAML: %s", err) } diff --git a/aws/diff_suppress_funcs.go b/aws/diff_suppress_funcs.go index c3fae0590ff..15e2e2e6011 100644 --- a/aws/diff_suppress_funcs.go +++ b/aws/diff_suppress_funcs.go @@ -97,15 +97,15 @@ func suppressOpenIdURL(k, old, new string, d *schema.ResourceData) bool { return oldUrl.String() == newUrl.String() } -func suppressCloudFormationTemplateBodyDiffs(k, old, new string, d *schema.ResourceData) bool { - normalizedOld, err := normalizeCloudFormationTemplate(old) +func suppressEquivalentJsonOrYamlDiffs(k, old, new string, d *schema.ResourceData) bool { + normalizedOld, err := normalizeJsonOrYamlString(old) if err != nil { log.Printf("[WARN] Unable to normalize Terraform state CloudFormation template body: %s", err) return false } - normalizedNew, err := normalizeCloudFormationTemplate(new) + normalizedNew, err := normalizeJsonOrYamlString(new) if err != nil { log.Printf("[WARN] Unable to normalize Terraform configuration CloudFormation template body: %s", err) @@ -115,8 +115,6 @@ func suppressCloudFormationTemplateBodyDiffs(k, old, new string, d *schema.Resou return normalizedOld == normalizedNew } -var suppressOpenAPISpecificationDiffs = suppressCloudFormationTemplateBodyDiffs - // suppressEqualCIDRBlockDiffs provides custom difference suppression for CIDR blocks // that have different string values but represent the same CIDR. func suppressEqualCIDRBlockDiffs(k, old, new string, d *schema.ResourceData) bool { diff --git a/aws/diff_suppress_funcs_test.go b/aws/diff_suppress_funcs_test.go index 0b406d6438d..3096217fd9d 100644 --- a/aws/diff_suppress_funcs_test.go +++ b/aws/diff_suppress_funcs_test.go @@ -71,7 +71,7 @@ func TestSuppressEquivalentTypeStringBoolean(t *testing.T) { } } -func TestSuppressCloudFormationTemplateBodyDiffs(t *testing.T) { +func TestSuppressEquivalentJsonOrYamlDiffs(t *testing.T) { testCases := []struct { description string equivalent bool @@ -253,7 +253,7 @@ Outputs: } for _, tc := range testCases { - value := suppressCloudFormationTemplateBodyDiffs("test_property", tc.old, tc.new, nil) + value := suppressEquivalentJsonOrYamlDiffs("test_property", tc.old, tc.new, nil) if tc.equivalent && !value { t.Fatalf("expected test case (%s) to be equivalent", tc.description) diff --git a/aws/resource_aws_apigatewayv2_api.go b/aws/resource_aws_apigatewayv2_api.go index 2658677d04e..b0a455fa54b 100644 --- a/aws/resource_aws_apigatewayv2_api.go +++ b/aws/resource_aws_apigatewayv2_api.go @@ -105,8 +105,8 @@ func resourceAwsApiGatewayV2Api() *schema.Resource { "body": { Type: schema.TypeString, Optional: true, - DiffSuppressFunc: suppressOpenAPISpecificationDiffs, - ValidateFunc: validateOpenAPISpecification, + DiffSuppressFunc: suppressEquivalentJsonOrYamlDiffs, + ValidateFunc: validateStringIsJsonOrYaml, }, "protocol_type": { Type: schema.TypeString, diff --git a/aws/resource_aws_cloudformation_stack.go b/aws/resource_aws_cloudformation_stack.go index f9399d3dc1e..1561363e5b1 100644 --- a/aws/resource_aws_cloudformation_stack.go +++ b/aws/resource_aws_cloudformation_stack.go @@ -43,9 +43,9 @@ func resourceAwsCloudFormationStack() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ValidateFunc: validateCloudFormationTemplate, + ValidateFunc: validateStringIsJsonOrYaml, StateFunc: func(v interface{}) string { - template, _ := normalizeCloudFormationTemplate(v) + template, _ := normalizeJsonOrYamlString(v) return template }, }, @@ -121,7 +121,7 @@ func resourceAwsCloudFormationStackCreate(d *schema.ResourceData, meta interface StackName: aws.String(d.Get("name").(string)), } if v, ok := d.GetOk("template_body"); ok { - template, err := normalizeCloudFormationTemplate(v) + template, err := normalizeJsonOrYamlString(v) if err != nil { return fmt.Errorf("template body contains an invalid JSON or YAML: %s", err) } @@ -300,7 +300,7 @@ func resourceAwsCloudFormationStackRead(d *schema.ResourceData, meta interface{} return err } - template, err := normalizeCloudFormationTemplate(*out.TemplateBody) + template, err := normalizeJsonOrYamlString(*out.TemplateBody) if err != nil { return fmt.Errorf("template body contains an invalid JSON or YAML: %s", err) } @@ -365,7 +365,7 @@ func resourceAwsCloudFormationStackUpdate(d *schema.ResourceData, meta interface input.TemplateURL = aws.String(v.(string)) } if v, ok := d.GetOk("template_body"); ok && input.TemplateURL == nil { - template, err := normalizeCloudFormationTemplate(v) + template, err := normalizeJsonOrYamlString(v) if err != nil { return fmt.Errorf("template body contains an invalid JSON or YAML: %s", err) } diff --git a/aws/resource_aws_cloudformation_stack_set.go b/aws/resource_aws_cloudformation_stack_set.go index e82a78864c0..92adbce18bf 100644 --- a/aws/resource_aws_cloudformation_stack_set.go +++ b/aws/resource_aws_cloudformation_stack_set.go @@ -86,8 +86,8 @@ func resourceAwsCloudFormationStackSet() *schema.Resource { Optional: true, Computed: true, ConflictsWith: []string{"template_url"}, - DiffSuppressFunc: suppressCloudFormationTemplateBodyDiffs, - ValidateFunc: validateCloudFormationTemplate, + DiffSuppressFunc: suppressEquivalentJsonOrYamlDiffs, + ValidateFunc: validateStringIsJsonOrYaml, }, "template_url": { Type: schema.TypeString, diff --git a/aws/structure.go b/aws/structure.go index 9782f58ff84..050ab5f8006 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -2267,7 +2267,7 @@ func checkYamlString(yamlString interface{}) (string, error) { return s, err } -func normalizeCloudFormationTemplate(templateString interface{}) (string, error) { +func normalizeJsonOrYamlString(templateString interface{}) (string, error) { if looksLikeJsonString(templateString) { return structure.NormalizeJsonString(templateString.(string)) } diff --git a/aws/structure_test.go b/aws/structure_test.go index 43aa0da03c4..bed89b372b7 100644 --- a/aws/structure_test.go +++ b/aws/structure_test.go @@ -1266,12 +1266,12 @@ abc: } } -func TestNormalizeCloudFormationTemplate(t *testing.T) { +func TestNormalizeJsonOrYamlString(t *testing.T) { var err error var actual string validNormalizedJson := `{"abc":"1"}` - actual, err = normalizeCloudFormationTemplate(validNormalizedJson) + actual, err = normalizeJsonOrYamlString(validNormalizedJson) if err != nil { t.Fatalf("Expected not to throw an error while parsing template, but got: %s", err) } @@ -1281,7 +1281,7 @@ func TestNormalizeCloudFormationTemplate(t *testing.T) { validNormalizedYaml := `abc: 1 ` - actual, err = normalizeCloudFormationTemplate(validNormalizedYaml) + actual, err = normalizeJsonOrYamlString(validNormalizedYaml) if err != nil { t.Fatalf("Expected not to throw an error while parsing template, but got: %s", err) } diff --git a/aws/validators.go b/aws/validators.go index e32203ae2d8..12ae9401c9e 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -976,7 +976,7 @@ func validateIAMPolicyJson(v interface{}, k string) (ws []string, errors []error return } -func validateCloudFormationTemplate(v interface{}, k string) (ws []string, errors []error) { +func validateStringIsJsonOrYaml(v interface{}, k string) (ws []string, errors []error) { if looksLikeJsonString(v) { if _, err := structure.NormalizeJsonString(v); err != nil { errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) @@ -989,8 +989,6 @@ func validateCloudFormationTemplate(v interface{}, k string) (ws []string, error return } -var validateOpenAPISpecification = validateCloudFormationTemplate - func validateApiGatewayIntegrationContentHandling() schema.SchemaValidateFunc { return validation.StringInSlice([]string{ apigateway.ContentHandlingStrategyConvertToBinary, diff --git a/aws/validators_test.go b/aws/validators_test.go index ec996f73152..9439b384c91 100644 --- a/aws/validators_test.go +++ b/aws/validators_test.go @@ -2,11 +2,12 @@ package aws import ( "fmt" - "github.com/aws/aws-sdk-go/service/cognitoidentity" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "regexp" "strings" "testing" + + "github.com/aws/aws-sdk-go/service/cognitoidentity" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" ) func TestValidateTypeStringNullableBoolean(t *testing.T) { @@ -823,7 +824,7 @@ func TestValidateIAMPolicyJsonString(t *testing.T) { } } -func TestValidateCloudFormationTemplate(t *testing.T) { +func TestValidateStringIsJsonOrYaml(t *testing.T) { type testCases struct { Value string ErrCount int @@ -841,7 +842,7 @@ func TestValidateCloudFormationTemplate(t *testing.T) { } for _, tc := range invalidCases { - _, errors := validateCloudFormationTemplate(tc.Value, "template") + _, errors := validateStringIsJsonOrYaml(tc.Value, "template") if len(errors) != tc.ErrCount { t.Fatalf("Expected %q to trigger a validation error.", tc.Value) } @@ -859,7 +860,7 @@ func TestValidateCloudFormationTemplate(t *testing.T) { } for _, tc := range validCases { - _, errors := validateCloudFormationTemplate(tc.Value, "template") + _, errors := validateStringIsJsonOrYaml(tc.Value, "template") if len(errors) != tc.ErrCount { t.Fatalf("Expected %q not to trigger a validation error.", tc.Value) }