terraform-provider-azapi icon indicating copy to clipboard operation
terraform-provider-azapi copied to clipboard

`azapi_resource` for Policy Fragments yielding constant diff with `rawxml` submissions

Open PrestonR opened this issue 2 years ago • 5 comments

We're getting constant diffs when submitting policy fragments via the following resource block:

resource "azapi_resource" "api" {
  for_each  = local.files
  type      = var.type
  name      = each.key
  parent_id = var.parent_id

  ignore_casing             = true
  ignore_missing_property   = true
  schema_validation_enabled = false

  body = jsonencode({
    properties = {
      description = each.key
      format      = "rawxml"
      value = templatefile(each.value, {
        subscription_id     = var.subscription_id
        resource_group_name = var.resource_group_name
      })
    }
  })

  lifecycle {
    create_before_destroy = true
  }
}

Here is a sample policy we submit as a rawxml fragment:

<fragment>
  <set-variable name="UserId" value="@{
    return ((Jwt)context.Variables["jwt"]).Claims.GetValueOrDefault("sub", new string[1]).First();
  }" />
  <set-body>@{
    var userId = context.Variables.GetValueOrDefault<string>("UserId", "");
    JObject inBody = context.Request.Body?.As<JObject>();
    if (inBody != null) {
      inBody.Add("user_id", userId);
    }
    return inBody.ToString();
  }</set-body>
</fragment>

Here is the output from the Terraform plan (after an apply):

<fragment>\r\n\t<set-variable name=\"UserId\" value=\"@{&#xA; return ((Jwt)context.Variables[&quot;jwt&quot;]).Claims.GetValueOrDefault(&quot;sub&quot;, new string[1]).First();&#xA; }\" />\r\n\t<set-body>@{\r\n var userId = context.Variables.GetValueOrDefault&lt;string&gt;(\"UserId\", \"\");\r\n JObject inBody = context.Request.Body?.As&lt;JObject&gt;();\r\n if (inBody != null) {\r\n inBody.Add(\"user_id\", userId);\r\n }\r\n return inBody.ToString();\r\n }</set-body>\r\n</fragment>

Here is the same output from a GET request directly against the REST api with properties: https://management.azure.com/subscriptions/{{SUBSCRIPTION_ID}}/resourceGroups/{{RESOURCE_GROUP}}/providers/Microsoft.ApiManagement/service/{{SERVICE_NAME}}/policyFragments/:policyFragmentName?api-version=2022-08-01&format=rawxml and response:

<fragment>\r\n\t<set-variable name=\"UserId\" value=\"@{\n    return ((Jwt)context.Variables[\"jwt\"]).Claims.GetValueOrDefault(\"sub\", new string[1]).First();\n  }\" />\r\n\t<set-body>@{\n    var userId = context.Variables.GetValueOrDefault<string>(\"UserId\", \"\");\n    JObject inBody = context.Request.Body?.As<JObject>();\n    if (inBody != null) {\n      inBody.Add(\"user_id\", userId);\n    }\n    return inBody.ToString();\n  }</set-body>\r\n</fragment>

and the xml response:

<fragment>\r\n\t<set-variable name=\"UserId\" value=\"@{&#xA;    return ((Jwt)context.Variables[&quot;jwt&quot;]).Claims.GetValueOrDefault(&quot;sub&quot;, new string[1]).First();&#xA;  }\" />\r\n\t<set-body>@{\r\n    var userId = context.Variables.GetValueOrDefault&lt;string&gt;(\"UserId\", \"\");\r\n    JObject inBody = context.Request.Body?.As&lt;JObject&gt;();\r\n    if (inBody != null) {\r\n      inBody.Add(\"user_id\", userId);\r\n    }\r\n    return inBody.ToString();\r\n  }</set-body>\r\n</fragment>

and the raw state body looks like this:

{\"properties\":{\"description\":\"append-userId-to-body\",\"format\":\"rawxml\",\"value\":\"\\u003cfragment\\u003e\\r\\n\\t\\u003cset-variable name=\\\"UserId\\\" value=\\\"@{\\u0026#xA;    return ((Jwt)context.Variables[\\u0026quot;jwt\\u0026quot;]).Claims.GetValueOrDefault(\\u0026quot;sub\\u0026quot;, new string[1]).First();\\u0026#xA;  }\\\" /\\u003e\\r\\n\\t\\u003cset-body\\u003e@{\\r\\n    var userId = context.Variables.GetValueOrDefault\\u0026lt;string\\u0026gt;(\\\"UserId\\\", \\\"\\\");\\r\\n    JObject inBody = context.Request.Body?.As\\u0026lt;JObject\\u0026gt;();\\r\\n    if (inBody != null) {\\r\\n      inBody.Add(\\\"user_id\\\", userId);\\r\\n    }\\r\\n    return inBody.ToString();\\r\\n  }\\u003c/set-body\\u003e\\r\\n\\u003c/fragment\\u003e\"}}

That looks like a mix of encodings and escape sequences (JSON, Unicode, XML, HTML, and special chars).

PrestonR avatar Aug 10 '23 19:08 PrestonR

Hi @PrestonR ,

Thank you for taking time to report this issue and provide the details!

It seems that the API doesn't return the value as the payload. This should be an upstream-api bug.

I have a workaround, it uses azapi_resource_action to create the policy fragment, but please notice deleting the azapi_resource_action will not remove the policy fragment:

resource "azapi_resource_action" "put_policyFragment" {
  type      = "Microsoft.ApiManagement/service/policyFragments@2023-03-01-preview"
  resource_id = "${azapi_resource.service.id}/policyFragments/henglu001"
  method = "PUT"

  body = jsonencode({
    properties = {
      description = "some description"
      format      = "rawxml"
      value       = local.input
    }
  })
}

I've created an issue to track it: https://github.com/Azure/azure-rest-api-specs/issues/25306

ms-henglu avatar Aug 15 '23 05:08 ms-henglu

@ms-henglu is there any update on this?

travisgan avatar Jan 26 '24 02:01 travisgan

Hi @travisgan , sorry for late response. It seems there's no updates on the API side. Would you please try the above workaround?

ms-henglu avatar Mar 13 '24 05:03 ms-henglu