diff --git a/azurerm/config.go b/azurerm/config.go
index e191f31f6e88..7b8cd7c6f67a 100644
--- a/azurerm/config.go
+++ b/azurerm/config.go
@@ -135,6 +135,7 @@ type ArmClient struct {
apiManagementGroupUsersClient apimanagement.GroupUserClient
apiManagementLoggerClient apimanagement.LoggerClient
apiManagementOpenIdConnectClient apimanagement.OpenIDConnectProviderClient
+ apiManagementPolicyClient apimanagement.PolicyClient
apiManagementProductsClient apimanagement.ProductClient
apiManagementProductApisClient apimanagement.ProductAPIClient
apiManagementProductGroupsClient apimanagement.ProductGroupClient
@@ -532,6 +533,10 @@ func (c *ArmClient) registerApiManagementServiceClients(endpoint, subscriptionId
c.configureClient(&loggerClient.Client, auth)
c.apiManagementLoggerClient = loggerClient
+ policyClient := apimanagement.NewPolicyClientWithBaseURI(endpoint, subscriptionId)
+ c.configureClient(&policyClient.Client, auth)
+ c.apiManagementPolicyClient = policyClient
+
serviceClient := apimanagement.NewServiceClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&serviceClient.Client, auth)
c.apiManagementServiceClient = serviceClient
diff --git a/azurerm/helpers/suppress/xml.go b/azurerm/helpers/suppress/xml.go
new file mode 100644
index 000000000000..6f53ad2b769f
--- /dev/null
+++ b/azurerm/helpers/suppress/xml.go
@@ -0,0 +1,47 @@
+package suppress
+
+import (
+ "encoding/xml"
+ "io"
+ "reflect"
+ "strings"
+
+ "github.com/hashicorp/terraform/helper/schema"
+)
+
+func SuppressXmlDiff(_, old, new string, _ *schema.ResourceData) bool {
+ oldTokens, err := expandXmlTokensFromString(old)
+ if err != nil {
+ return false
+ }
+
+ newTokens, err := expandXmlTokensFromString(new)
+ if err != nil {
+ return false
+ }
+
+ return reflect.DeepEqual(oldTokens, newTokens)
+}
+
+// This function will extract all XML tokens from a string, but ignoring all white-space tokens
+func expandXmlTokensFromString(input string) ([]xml.Token, error) {
+ decoder := xml.NewDecoder(strings.NewReader(input))
+ tokens := make([]xml.Token, 0)
+ for {
+ token, err := decoder.Token()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return nil, err
+ }
+ if chars, ok := token.(xml.CharData); ok {
+ text := string(chars)
+ if strings.TrimSpace(text) == "" {
+ continue
+ }
+ }
+ tokens = append(tokens, xml.CopyToken(token))
+ }
+ return tokens, nil
+}
diff --git a/azurerm/helpers/suppress/xml_test.go b/azurerm/helpers/suppress/xml_test.go
new file mode 100644
index 000000000000..3a10a284238e
--- /dev/null
+++ b/azurerm/helpers/suppress/xml_test.go
@@ -0,0 +1,81 @@
+package suppress
+
+import "testing"
+
+func TestSuppressXmlDiff(t *testing.T) {
+ cases := []struct {
+ Name string
+ XmlA string
+ XmlB string
+ Suppress bool
+ }{
+ {
+ Name: "empty",
+ XmlA: "",
+ XmlB: "",
+ Suppress: true,
+ },
+ {
+ Name: "neither are xml",
+ XmlA: "this is not an xml",
+ XmlB: "neither is this",
+ Suppress: false,
+ },
+ {
+ Name: "identical texts",
+ XmlA: "this is not an xml",
+ XmlB: "this is not an xml",
+ Suppress: true,
+ },
+ {
+ Name: "xml vs text",
+ XmlA: "",
+ XmlB: "this is not an xml",
+ Suppress: false,
+ },
+ {
+ Name: "text vs xml",
+ XmlA: "this is not an xml",
+ XmlB: "",
+ Suppress: false,
+ },
+ {
+ Name: "identical xml",
+ XmlA: "",
+ XmlB: "",
+ Suppress: true,
+ },
+ {
+ Name: "xml with different line endings",
+ XmlA: "\n\n\n",
+ XmlB: "\r\n\r\n\r\n",
+ Suppress: true,
+ },
+ {
+ Name: "xml with different indentations",
+ XmlA: "\n \n \n",
+ XmlB: "\r\n\t\r\n\t\r\n",
+ Suppress: true,
+ },
+ {
+ Name: "xml with different quotation marks",
+ XmlA: "",
+ XmlB: "\r\n\t\r\n\t\r\n",
+ Suppress: true,
+ },
+ {
+ Name: "xml with different spaces",
+ XmlA: "",
+ XmlB: "\r\n\t\r\n\t\r\n",
+ Suppress: true,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ if SuppressXmlDiff("test", tc.XmlA, tc.XmlB, nil) != tc.Suppress {
+ t.Fatalf("Expected SuppressXmlDiff to return %t for '%q' == '%q'", tc.Suppress, tc.XmlA, tc.XmlB)
+ }
+ })
+ }
+}
diff --git a/azurerm/resource_arm_api_management.go b/azurerm/resource_arm_api_management.go
index 5825aeebae0a..5e5ef8d5fc4f 100644
--- a/azurerm/resource_arm_api_management.go
+++ b/azurerm/resource_arm_api_management.go
@@ -255,6 +255,27 @@ func resourceArmApiManagementService() *schema.Resource {
},
},
+ "policy": {
+ Type: schema.TypeList,
+ Optional: true,
+ MaxItems: 1,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "xml_content": {
+ Type: schema.TypeString,
+ Required: true,
+ ConflictsWith: []string{"policy.0.xml_link"},
+ },
+
+ "xml_link": {
+ Type: schema.TypeString,
+ Required: true,
+ ConflictsWith: []string{"policy.0.xml_content"},
+ },
+ },
+ },
+ },
+
"sign_in": {
Type: schema.TypeList,
Optional: true,
@@ -431,6 +452,26 @@ func resourceArmApiManagementServiceCreateUpdate(d *schema.ResourceData, meta in
return fmt.Errorf("Error setting Sign Up settings for API Management Service %q (Resource Group %q): %+v", name, resourceGroup, err)
}
+ policyClient := meta.(*ArmClient).apiManagementPolicyClient
+ policiesRaw := d.Get("policy").([]interface{})
+ policy, err := expandApiManagementPolicies(policiesRaw)
+ if err != nil {
+ return err
+ }
+
+ if policy != nil {
+ if _, err := policyClient.CreateOrUpdate(ctx, resourceGroup, name, *policy); err != nil {
+ return fmt.Errorf("Error setting Policies for API Management Service %q (Resource Group %q): %+v", name, resourceGroup, err)
+ }
+ } else {
+ // reset them
+ if resp, err := policyClient.Delete(ctx, resourceGroup, name, ""); err != nil {
+ if !utils.ResponseWasNotFound(resp) {
+ return fmt.Errorf("Error removing Policies from API Management Service %q (Resource Group %q): %+v", name, resourceGroup, err)
+ }
+ }
+ }
+
return resourceArmApiManagementServiceRead(d, meta)
}
@@ -469,6 +510,14 @@ func resourceArmApiManagementServiceRead(d *schema.ResourceData, meta interface{
return fmt.Errorf("Error retrieving Sign Up Settings for API Management Service %q (Resource Group %q): %+v", name, resourceGroup, err)
}
+ policyClient := meta.(*ArmClient).apiManagementPolicyClient
+ policy, err := policyClient.Get(ctx, resourceGroup, name)
+ if err != nil {
+ if !utils.ResponseWasNotFound(policy.Response) {
+ return fmt.Errorf("Error retrieving Policy for API Management Service %q (Resource Group %q): %+v", name, resourceGroup, err)
+ }
+ }
+
d.Set("name", name)
d.Set("resource_group_name", resourceGroup)
@@ -520,6 +569,10 @@ func resourceArmApiManagementServiceRead(d *schema.ResourceData, meta interface{
flattenAndSetTags(d, resp.Tags)
+ if err := d.Set("policy", flattenApiManagementPolicies(policy)); err != nil {
+ return fmt.Errorf("Error setting `policy`: %+v", err)
+ }
+
return nil
}
@@ -1045,3 +1098,57 @@ func flattenApiManagementSignUpSettings(input apimanagement.PortalSignupSettings
},
}
}
+
+func expandApiManagementPolicies(input []interface{}) (*apimanagement.PolicyContract, error) {
+ if len(input) == 0 {
+ return nil, nil
+ }
+
+ vs := input[0].(map[string]interface{})
+ xmlContent := vs["xml_content"].(string)
+ xmlLink := vs["xml_link"].(string)
+
+ if xmlContent != "" {
+ return &apimanagement.PolicyContract{
+ PolicyContractProperties: &apimanagement.PolicyContractProperties{
+ ContentFormat: apimanagement.XML,
+ PolicyContent: utils.String(xmlContent),
+ },
+ }, nil
+ }
+
+ if xmlLink != "" {
+ return &apimanagement.PolicyContract{
+ PolicyContractProperties: &apimanagement.PolicyContractProperties{
+ ContentFormat: apimanagement.XMLLink,
+ PolicyContent: utils.String(xmlLink),
+ },
+ }, nil
+ }
+
+ return nil, fmt.Errorf("Either `xml_content` or `xml_link` should be set if the `policy` block is defined.")
+}
+
+func flattenApiManagementPolicies(input apimanagement.PolicyContract) []interface{} {
+ output := map[string]interface{}{
+ "xml_content": "",
+ "xml_link": "",
+ }
+
+ if props := input.PolicyContractProperties; props != nil {
+ if props.PolicyContent == nil {
+ return []interface{}{output}
+ }
+
+ switch props.ContentFormat {
+ case apimanagement.XML:
+ output["xml_content"] = *props.PolicyContent
+ case apimanagement.XMLLink:
+ output["xml_link"] = *props.PolicyContent
+ default:
+ log.Printf("[DEBUG] Unsupported Content Format %q for Policy", string(props.ContentFormat))
+ }
+ }
+
+ return []interface{}{output}
+}
diff --git a/azurerm/resource_arm_api_management_test.go b/azurerm/resource_arm_api_management_test.go
index cac05837df60..f8b8251b69e5 100644
--- a/azurerm/resource_arm_api_management_test.go
+++ b/azurerm/resource_arm_api_management_test.go
@@ -150,6 +150,53 @@ func TestAccAzureRMApiManagement_signInSignUpSettings(t *testing.T) {
})
}
+func TestAccAzureRMApiManagement_policy(t *testing.T) {
+ resourceName := "azurerm_api_management.test"
+ ri := tf.AccRandTimeInt()
+ location := testLocation()
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testAccProviders,
+ CheckDestroy: testCheckAzureRMApiManagementDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccAzureRMApiManagement_policyXmlContent(ri, location),
+ Check: resource.ComposeTestCheckFunc(
+ testCheckAzureRMApiManagementExists(resourceName),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ {
+ Config: testAccAzureRMApiManagement_policyXmlLink(ri, location),
+ Check: resource.ComposeTestCheckFunc(
+ testCheckAzureRMApiManagementExists(resourceName),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ {
+ Config: testAccAzureRMApiManagement_basic(ri, location),
+ Check: resource.ComposeTestCheckFunc(
+ testCheckAzureRMApiManagementExists(resourceName),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
func testCheckAzureRMApiManagementDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*ArmClient).apiManagementServiceClient
@@ -228,6 +275,64 @@ resource "azurerm_api_management" "test" {
`, rInt, location, rInt)
}
+func testAccAzureRMApiManagement_policyXmlContent(rInt int, location string) string {
+ return fmt.Sprintf(`
+resource "azurerm_resource_group" "test" {
+ name = "acctestRG-%d"
+ location = "%s"
+}
+
+resource "azurerm_api_management" "test" {
+ name = "acctestAM-%d"
+ location = "${azurerm_resource_group.test.location}"
+ resource_group_name = "${azurerm_resource_group.test.name}"
+ publisher_name = "pub1"
+ publisher_email = "pub1@email.com"
+
+ sku {
+ name = "Developer"
+ capacity = 1
+ }
+
+ policy {
+ xml_content = <
+
+
+
+
+XML
+ }
+}
+`, rInt, location, rInt)
+}
+
+func testAccAzureRMApiManagement_policyXmlLink(rInt int, location string) string {
+ return fmt.Sprintf(`
+resource "azurerm_resource_group" "test" {
+ name = "acctestRG-%d"
+ location = "%s"
+}
+
+resource "azurerm_api_management" "test" {
+ name = "acctestAM-%d"
+ location = "${azurerm_resource_group.test.location}"
+ resource_group_name = "${azurerm_resource_group.test.name}"
+ publisher_name = "pub1"
+ publisher_email = "pub1@email.com"
+
+ sku {
+ name = "Developer"
+ capacity = 1
+ }
+
+ policy {
+ xml_link = "https://gist.githubusercontent.com/tombuildsstuff/4f58581599d2c9f64b236f505a361a67/raw/0d29dcb0167af1e5afe4bd52a6d7f69ba1e05e1f/example.xml"
+ }
+}
+`, rInt, location, rInt)
+}
+
func testAccAzureRMApiManagement_requiresImport(rInt int, location string) string {
template := testAccAzureRMApiManagement_basic(rInt, location)
return fmt.Sprintf(`