Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/api_management_api_policy: sending policy as Raw XML #4140

Merged
merged 8 commits into from
Nov 1, 2019
42 changes: 42 additions & 0 deletions azurerm/internal/services/apimanagement/diff_suppress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package apimanagement

import (
"strings"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress"
)

// XmlWithDotNetInterpolationsDiffSuppress is a Diff Suppress Func for when the XML contains
// .net interpolations, and thus isn't valid XML to parse
// whilst really we should be parsing the XML Tokens and skipping over the error - in practice
func XmlWithDotNetInterpolationsDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
// try parsing this as valid xml if we can, to handle ordering differences
same := suppress.XmlDiff(k, old, new, d)
if same {
return same
}

// otherwise best-effort this via string comparison
oldVal := normalizeXmlWithDotNetInterpolationsString(old)
newVal := normalizeXmlWithDotNetInterpolationsString(new)
return oldVal == newVal
}

// normalizeXmlWithDotNetInterpolationsString is intended as a fallback to diff two xml strings
// containing .net interpolations, which means that they aren't directly valid xml
// whilst we /could/ xml.EscapeString these that encodes the entire string, rather than the expression
// we could do that as a potential extension, but this seems sufficient in testing :shrug:
func normalizeXmlWithDotNetInterpolationsString(input string) string {
value := input

value = strings.ReplaceAll(value, "\n", "")
value = strings.ReplaceAll(value, "\r", "")
value = strings.ReplaceAll(value, "\t", "")
value = strings.ReplaceAll(value, " ", "")
value = strings.ReplaceAll(value, " ", "")
value = strings.ReplaceAll(value, " ", "")
value = strings.ReplaceAll(value, """, "\"")

return value
}
103 changes: 103 additions & 0 deletions azurerm/internal/services/apimanagement/diff_suppress_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package apimanagement

import (
"log"
"testing"
)

func TestXmlWithDotNetInterpolationsDiffSuppress(t *testing.T) {
testData := []struct {
old string
new string
same bool
}{
{
old: "",
new: "",
same: true,
},
{
old: "<hello />",
new: "",
same: false,
},
{
old: "",
new: "<hello />",
same: false,
},
{
old: "<hello />",
new: "<world />",
same: false,
},
{
// no expressions - with whitespace
old: "<policies><inbound><set-variable name=\"abc\" value=\"bcd\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
new: "<policies><inbound><set-variable name=\"abc\" value=\"bcd\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
same: true,
},
{
// not xml encoded - with whitespace
old: "<policies>\n <inbound>\n <set-variable name=\"abc\" value=\"bcd\" />\n <find-and-replace from=\"xyz\" to=\"abc\" />\n </inbound>\n</policies>\n",
new: "<policies>\r\n\t<inbound>\r\n\t\t<set-variable name=\"abc\" value=\"bcd\" />\r\n\t\t<find-and-replace from=\"xyz\" to=\"abc\" />\r\n\t</inbound>\r\n</policies>",
same: true,
},
{
// both are xml encoded - with whitespace
old: "<policies><inbound><set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(\"X-Header-Name\", \"\"))\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
new: "<policies><inbound><set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(\"X-Header-Name\", \"\"))\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
same: true,
},
{
// not xml encoded - with whitespace
old: "<policies>\n <inbound>\n <set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(\"X-Header-Name\", \"\"))\" />\n <find-and-replace from=\"xyz\" to=\"abc\" />\n </inbound>\n</policies>\n",
new: "<policies>\r\n\t<inbound>\r\n\t\t<set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(\"X-Header-Name\", \"\"))\" />\r\n\t\t<find-and-replace from=\"xyz\" to=\"abc\" />\r\n\t</inbound>\r\n</policies>",
same: true,
},
{
// both are xml encoded - no whitespace
old: "<policies><inbound><set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(&quot;X-Header-Name&quot;, &quot;&quot;))\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
new: "<policies><inbound><set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(&quot;X-Header-Name&quot;, &quot;&quot;))\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
same: true,
},
{
// both are xml encoded with whitespace
old: "<policies>\n <inbound>\n <set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(&quot;X-Header-Name&quot;, &quot;&quot;))\" />\n <find-and-replace from=\"xyz\" to=\"abc\" />\n </inbound>\n</policies>\n",
new: "<policies>\r\n\t<inbound>\r\n\t\t<set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(&quot;X-Header-Name&quot;, &quot;&quot;))\" />\r\n\t\t<find-and-replace from=\"xyz\" to=\"abc\" />\r\n\t</inbound>\r\n</policies>",
same: true,
},
{
// new is xml encoded, old isn't
old: "<policies><inbound><set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(\"X-Header-Name\", \"\"))\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
new: "<policies><inbound><set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(&quot;X-Header-Name&quot;, &quot;&quot;))\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
same: true,
},
{
// new is xml encoded, old isn't
old: "<policies>\n <inbound>\n <set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(\"X-Header-Name\", \"\"))\" />\n <find-and-replace from=\"xyz\" to=\"abc\" />\n </inbound>\n</policies>\n",
new: "<policies>\r\n\t<inbound>\r\n\t\t<set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(&quot;X-Header-Name&quot;, &quot;&quot;))\" />\r\n\t\t<find-and-replace from=\"xyz\" to=\"abc\" />\r\n\t</inbound>\r\n</policies>",
same: true,
},
{
// old is xml encoded, new isn't
old: "<policies><inbound><set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(&quot;X-Header-Name&quot;, &quot;&quot;))\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
new: "<policies><inbound><set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(\"X-Header-Name\", \"\"))\" /><find-and-replace from=\"xyz\" to=\"abc\" /></inbound></policies>",
same: true,
},
{
// old is xml encoded, new isn't
old: "<policies>\r\n\t<inbound>\r\n\t\t<set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(&quot;X-Header-Name&quot;, &quot;&quot;))\" />\r\n\t\t<find-and-replace from=\"xyz\" to=\"abc\" />\r\n\t</inbound>\r\n</policies>",
new: "<policies>\n <inbound>\n <set-variable name=\"abc\" value=\"@(context.Request.Headers.GetValueOrDefault(\"X-Header-Name\", \"\"))\" />\n <find-and-replace from=\"xyz\" to=\"abc\" />\n </inbound>\n</policies>\n",
same: true,
},
}

for _, v := range testData {
log.Printf("[DEBUG] Testing %q vs %q..", v.old, v.new)
actual := XmlWithDotNetInterpolationsDiffSuppress("", v.old, v.new, nil)
if actual != v.same {
t.Fatalf("Expected %t but got %t", v.same, actual)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ resource "azurerm_api_management_api_operation_policy" "test" {
api_management_name = "${azurerm_api_management.test.name}"
resource_group_name = "${azurerm_resource_group.test.name}"
operation_id = "${azurerm_api_management_api_operation.test.operation_id}"
xml_link = "https://gist.githubusercontent.com/tombuildsstuff/4f58581599d2c9f64b236f505a361a67/raw/0d29dcb0167af1e5afe4bd52a6d7f69ba1e05e1f/example.xml"
xml_link = "https://gist.githubusercontent.com/riordanp/ca22f8113afae0eb38cc12d718fd048d/raw/d6ac89a2f35a6881a7729f8cb4883179dc88eea1/example.xml"
}
`, template)
}
Expand Down Expand Up @@ -200,6 +200,7 @@ resource "azurerm_api_management_api_operation_policy" "test" {
xml_content = <<XML
<policies>
<inbound>
<set-variable name="abc" value="@(context.Request.Headers.GetValueOrDefault("X-Header-Name", ""))" />
<find-and-replace from="xyz" to="abc" />
</inbound>
</policies>
Expand Down
31 changes: 21 additions & 10 deletions azurerm/resource_arm_api_management_api_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package azurerm

import (
"fmt"
"html"
"log"
"time"

"github.com/Azure/azure-sdk-for-go/services/apimanagement/mgmt/2018-01-01/apimanagement"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features"
apimanagementSvc "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/apimanagement"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)
Expand Down Expand Up @@ -44,7 +45,7 @@ func resourceArmApiManagementApiPolicy() *schema.Resource {
Optional: true,
Computed: true,
ConflictsWith: []string{"xml_link"},
DiffSuppressFunc: suppress.XmlDiff,
DiffSuppressFunc: apimanagementSvc.XmlWithDotNetInterpolationsDiffSuppress,
},

"xml_link": {
Expand Down Expand Up @@ -83,17 +84,22 @@ func resourceArmApiManagementAPIPolicyCreateUpdate(d *schema.ResourceData, meta
xmlContent := d.Get("xml_content").(string)
xmlLink := d.Get("xml_link").(string)

if xmlContent != "" {
if xmlLink != "" {
parameters.PolicyContractProperties = &apimanagement.PolicyContractProperties{
ContentFormat: apimanagement.XML,
PolicyContent: utils.String(xmlContent),
ContentFormat: apimanagement.RawxmlLink,
PolicyContent: utils.String(xmlLink),
}
} else if xmlContent != "" {
// this is intentionally an else-if since `xml_content` is computed

// clear out any existing value for xml_link
if !d.IsNewResource() {
d.Set("xml_link", "")
}
}

if xmlLink != "" {
parameters.PolicyContractProperties = &apimanagement.PolicyContractProperties{
ContentFormat: apimanagement.XMLLink,
PolicyContent: utils.String(xmlLink),
ContentFormat: apimanagement.Rawxml,
riordanp marked this conversation as resolved.
Show resolved Hide resolved
PolicyContent: utils.String(xmlContent),
}
}

Expand Down Expand Up @@ -146,9 +152,14 @@ func resourceArmApiManagementAPIPolicyRead(d *schema.ResourceData, meta interfac
d.Set("api_name", apiName)

if properties := resp.PolicyContractProperties; properties != nil {
policyContent := ""
if pc := properties.PolicyContent; pc != nil {
policyContent = html.UnescapeString(*pc)
}

// when you submit an `xml_link` to the API, the API downloads this link and stores it as `xml_content`
// as such there is no way to set `xml_link` and we'll let Terraform handle it
d.Set("xml_content", properties.PolicyContent)
d.Set("xml_content", policyContent)
}

return nil
Expand Down
33 changes: 30 additions & 3 deletions azurerm/resource_arm_api_management_api_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,33 @@ func TestAccAzureRMApiManagementAPIPolicy_update(t *testing.T) {
),
},
{
Config: testAccAzureRMApiManagementAPIPolicy_updated(ri, location),
Config: testAccAzureRMApiManagementAPIPolicy_customPolicy(ri, location),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMApiManagementAPIPolicyExists(resourceName),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"xml_link"},
},
},
})
}

func TestAccAzureRMApiManagementAPIPolicy_customPolicy(t *testing.T) {
resourceName := "azurerm_api_management_api_policy.test"
ri := tf.AccRandTimeInt()
location := testLocation()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMApiManagementAPIPolicyDestroy,
Steps: []resource.TestStep{
{
Config: testAccAzureRMApiManagementAPIPolicy_customPolicy(ri, location),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMApiManagementAPIPolicyExists(resourceName),
),
Expand Down Expand Up @@ -186,7 +212,7 @@ resource "azurerm_api_management_api_policy" "test" {
api_name = "${azurerm_api_management_api.test.name}"
api_management_name = "${azurerm_api_management.test.name}"
resource_group_name = "${azurerm_resource_group.test.name}"
xml_link = "https://gist.githubusercontent.com/tombuildsstuff/4f58581599d2c9f64b236f505a361a67/raw/0d29dcb0167af1e5afe4bd52a6d7f69ba1e05e1f/example.xml"
xml_link = "https://gist.githubusercontent.com/riordanp/ca22f8113afae0eb38cc12d718fd048d/raw/d6ac89a2f35a6881a7729f8cb4883179dc88eea1/example.xml"
}
`, rInt, location, rInt, rInt)
}
Expand All @@ -205,7 +231,7 @@ resource "azurerm_api_management_api_policy" "import" {
`, template)
}

func testAccAzureRMApiManagementAPIPolicy_updated(rInt int, location string) string {
func testAccAzureRMApiManagementAPIPolicy_customPolicy(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
Expand Down Expand Up @@ -243,6 +269,7 @@ resource "azurerm_api_management_api_policy" "test" {
xml_content = <<XML
<policies>
<inbound>
<set-variable name="abc" value="@(context.Request.Headers.GetValueOrDefault("X-Header-Name", ""))" />
<find-and-replace from="xyz" to="abc" />
</inbound>
</policies>
Expand Down
3 changes: 2 additions & 1 deletion azurerm/resource_arm_api_management_product_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ resource "azurerm_api_management_product_policy" "test" {
product_id = "${azurerm_api_management_product.test.product_id}"
api_management_name = "${azurerm_api_management.test.name}"
resource_group_name = "${azurerm_resource_group.test.name}"
xml_link = "https://gist.githubusercontent.com/tombuildsstuff/4f58581599d2c9f64b236f505a361a67/raw/0d29dcb0167af1e5afe4bd52a6d7f69ba1e05e1f/example.xml"
xml_link = "https://gist.githubusercontent.com/riordanp/ca22f8113afae0eb38cc12d718fd048d/raw/d6ac89a2f35a6881a7729f8cb4883179dc88eea1/example.xml"
}
`, rInt, location, rInt)
}
Expand Down Expand Up @@ -241,6 +241,7 @@ resource "azurerm_api_management_product_policy" "test" {
xml_content = <<XML
<policies>
<inbound>
<set-variable name="abc" value="@(context.Request.Headers.GetValueOrDefault("X-Header-Name", ""))" />
<find-and-replace from="xyz" to="abc" />
</inbound>
</policies>
Expand Down