diff --git a/azurerm/internal/services/apimanagement/api_management_api_schema_resource.go b/azurerm/internal/services/apimanagement/api_management_api_schema_resource.go index 49c26786aa19..44a545513c91 100644 --- a/azurerm/internal/services/apimanagement/api_management_api_schema_resource.go +++ b/azurerm/internal/services/apimanagement/api_management_api_schema_resource.go @@ -1,6 +1,7 @@ package apimanagement import ( + "encoding/json" "fmt" "log" "time" @@ -12,6 +13,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/apimanagement/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/apimanagement/schemaz" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -52,6 +54,12 @@ func resourceApiManagementApiSchema() *pluginsdk.Resource { Type: pluginsdk.TypeString, Required: true, ValidateFunc: validation.StringIsNotEmpty, + DiffSuppressFunc: func(k, old, new string, d *pluginsdk.ResourceData) bool { + if d.Get("content_type") == "application/vnd.ms-azure-apim.swagger.definitions+json" || d.Get("content_type") == "application/vnd.oai.openapi.components+json" { + return suppress.JsonDiff(k, old, new, d) + } + return old == new + }, }, }, } @@ -140,10 +148,32 @@ func resourceApiManagementApiSchemaRead(d *pluginsdk.ResourceData, meta interfac if properties := resp.SchemaContractProperties; properties != nil { d.Set("content_type", properties.ContentType) if documentProperties := properties.SchemaDocumentProperties; documentProperties != nil { - d.Set("value", documentProperties.Value) + /* + As per https://docs.microsoft.com/en-us/rest/api/apimanagement/2019-12-01/api-schema/get#schemacontract + + - Swagger Schema use application/vnd.ms-azure-apim.swagger.definitions+json + - WSDL Schema use application/vnd.ms-azure-apim.xsd+xml + - OpenApi Schema use application/vnd.oai.openapi.components+json + - WADL Schema use application/vnd.ms-azure-apim.wadl.grammars+xml. + + Definitions used for Swagger/OpenAPI schemas only, otherwise Value is used + */ + if *properties.ContentType == "application/vnd.ms-azure-apim.swagger.definitions+json" || *properties.ContentType == "application/vnd.oai.openapi.components+json" { + if documentProperties.Definitions != nil { + value, err := json.Marshal(documentProperties.Definitions) + if err != nil { + return fmt.Errorf("[FATAL] Unable to serialize schema to json. Error: %+v. Schema struct: %+v", err, documentProperties.Definitions) + } + d.Set("value", string(value)) + } + } else if *properties.ContentType == "application/vnd.ms-azure-apim.xsd+xml" || *properties.ContentType == "application/vnd.ms-azure-apim.wadl.grammars+xml" { + d.Set("value", documentProperties.Value) + } else { + return fmt.Errorf("[FATAL] Unkown content type %q for schema %q (API Management Service %q / API %q / Resource Group %q)", *properties.ContentType, schemaID, serviceName, apiName, resourceGroup) + } } - } + } return nil } diff --git a/azurerm/internal/services/apimanagement/api_management_api_schema_resource_test.go b/azurerm/internal/services/apimanagement/api_management_api_schema_resource_test.go index 52ce35199c6e..c46d1e8db668 100644 --- a/azurerm/internal/services/apimanagement/api_management_api_schema_resource_test.go +++ b/azurerm/internal/services/apimanagement/api_management_api_schema_resource_test.go @@ -3,6 +3,8 @@ package apimanagement_test import ( "context" "fmt" + "io/ioutil" + "strings" "testing" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" @@ -19,12 +21,31 @@ type ApiManagementApiSchemaResource struct { func TestAccApiManagementApiSchema_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_api_management_api_schema", "test") r := ApiManagementApiSchemaResource{} + schema, _ := ioutil.ReadFile("testdata/api_management_api_schema.xml") data.ResourceTest(t, r, []acceptance.TestStep{ { Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("value").HasValue(string(schema)), + ), + }, + data.ImportStep(), + }) +} + +func TestAccApiManagementApiSchema_basicSwagger(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_api_management_api_schema", "test") + r := ApiManagementApiSchemaResource{} + schema, _ := ioutil.ReadFile("testdata/api_management_api_schema_swagger.json") + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basicSwagger(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("value").HasValue(strings.TrimRight(string(schema), "\r\n")), ), }, data.ImportStep(), @@ -79,6 +100,21 @@ resource "azurerm_api_management_api_schema" "test" { `, r.template(data), data.RandomInteger) } +func (r ApiManagementApiSchemaResource) basicSwagger(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_api_management_api_schema" "test" { + api_name = azurerm_api_management_api.test.name + api_management_name = azurerm_api_management_api.test.api_management_name + resource_group_name = azurerm_api_management_api.test.resource_group_name + schema_id = "acctestSchema%d" + content_type = "application/vnd.ms-azure-apim.swagger.definitions+json" + value = file("testdata/api_management_api_schema_swagger.json") +} +`, r.template(data), data.RandomInteger) +} + func (r ApiManagementApiSchemaResource) requiresImport(data acceptance.TestData) string { return fmt.Sprintf(` %s diff --git a/azurerm/internal/services/apimanagement/testdata/api_management_api_schema_swagger.json b/azurerm/internal/services/apimanagement/testdata/api_management_api_schema_swagger.json new file mode 100644 index 000000000000..66d0d15d10be --- /dev/null +++ b/azurerm/internal/services/apimanagement/testdata/api_management_api_schema_swagger.json @@ -0,0 +1 @@ +{"schema-bug-example":{"properties":{"Field2":{"description":"Field2","type":"string"},"field1":{"description":"Field1","type":"string"}},"required":["field1","Field2"],"type":"object"}} diff --git a/azurerm/internal/tf/suppress/json.go b/azurerm/internal/tf/suppress/json.go new file mode 100644 index 000000000000..ca49f631e442 --- /dev/null +++ b/azurerm/internal/tf/suppress/json.go @@ -0,0 +1,21 @@ +package suppress + +import ( + "encoding/json" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func JsonDiff(_, old, new string, _ *schema.ResourceData) bool { + if old == new { + return true + } + var oldJson interface{} + var newJson interface{} + + json.Unmarshal([]byte(old), &oldJson) + json.Unmarshal([]byte(new), &newJson) + + return reflect.DeepEqual(oldJson, newJson) +} diff --git a/azurerm/internal/tf/suppress/json_test.go b/azurerm/internal/tf/suppress/json_test.go new file mode 100644 index 000000000000..5ce480dbea6f --- /dev/null +++ b/azurerm/internal/tf/suppress/json_test.go @@ -0,0 +1,39 @@ +package suppress + +import "testing" + +func TestJsonDiff(t *testing.T) { + cases := []struct { + Name string + StringA string + StringB string + Suppress bool + }{ + { + Name: "empty", + StringA: "", + StringB: "", + Suppress: true, + }, + { + Name: "simple same object", + StringA: "{\"field\": \"value\"}", + StringB: "{\"field\": \"value\"}", + Suppress: true, + }, + { + Name: "simple object whitespace diff", + StringA: "{\n\"field\": \"value\"\n}", + StringB: "{\"field\": \"value\"}", + Suppress: false, + }, + } + + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + if JsonDiff("test", tc.StringA, tc.StringB, nil) != tc.Suppress { + t.Fatalf("Expected JsonDiff to return %t for '%q' == '%q'", tc.Suppress, tc.StringA, tc.StringB) + } + }) + } +}