terraformazure-api-managementterraform-provider-azure

How to share xml_content in Terraform API Management resource


We are creating our API management instance and associated endpoints through Terraform. All our API endpoints (close to a hundred) share the same policy logic for routing the request to an Azure function.

An example policy is like so -

resource "azurerm_api_management_api_operation_policy" 
"api_put_policy" {
  api_name            = azurerm_api_management_api.my_api.name
  resource_group_name = azurerm_resource_group.main.name
  api_management_name = azurerm_api_management.my_api.name
  operation_id        = azurerm_api_management_api_operation.my_api.operation_id

xml_content = <<XML
<policies>
<inbound>
    <base />
    <choose>
        <when condition="@(context.Request.Headers.GetValueOrDefault("Key") == "password")">
            <set-backend-service base-url="${data.azurerm_function_app.MyFunctionApp.default_hostname}" />
        </when>
        <when condition="@(context.Request.Headers.GetValueOrDefault("Key") != null)">
            <return-response>
                <set-status code="400" reason="Bad Request" />
                <set-body>An incorrect Key header has been passed in the request</set-body>
            </return-response>
        </when>
        <otherwise>
            <set-backend-service base-url="${other-route-variable}" />
        </otherwise>
    </choose>
</inbound>
<backend>
    <base />
</backend>
<outbound>
    <base />
</outbound>
<on-error>
    <base />
</on-error>

So we have the same XML_content being used on every API endpoint, only the variables get set differently depending what function app is going to be routed to.

Is there a way this xml content could be moved into a file where parameters can be passed through to then generate the XML for every API policy so we have the logic stored in only one place?

I have looked at a variety of uses of the file() function but can't see anything that could be done to achieve what I need here.

Thanks


Solution

  • Yes, you can use the templatefile function for that [1]. The templatefile function works in the following way:

    templatefile(path, vars)

    Where the path represents the file location and the vars are a map of variables that will be used to replace the placeholders in the file itself. I will give an example based on the XML file you have. You would first create the template file inside of the same directory probably (e.g., xml_content.tpl):

    <policies>
    <inbound>
        <base />
        <choose>
            <when condition="@(context.Request.Headers.GetValueOrDefault("Key") == ${password})">
                <set-backend-service base-url="${hostname_url}" />
            </when>
            <when condition="@(context.Request.Headers.GetValueOrDefault("Key") != null)">
                <return-response>
                    <set-status code="400" reason="Bad Request" />
                    <set-body>An incorrect Key header has been passed in the request</set-body>
                </return-response>
            </when>
            <otherwise>
                <set-backend-service base-url="${other-route-variable}" />
            </otherwise>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
    

    Notice that I removed the password value and the data source output in url. Those will now expect variables with names password and hostname_url to be provided when using the templatefile function:

    resource "azurerm_api_management_api_operation_policy" "api_put_policy" {
      api_name            = azurerm_api_management_api.my_api.name
      resource_group_name = azurerm_resource_group.main.name
      api_management_name = azurerm_api_management.my_api.name
      operation_id        = azurerm_api_management_api_operation.my_api.operation_id
    
        xml_content  = templatefile("${path.root}/xml_content.tpl", 
        password     = var.password
        hostname_url = data.azurerm_function_app.MyFunctionApp.default_hostname
      )
    }
    

    Whenever this is called, it will look for the placeholder values and replace them. Two additional things to note:

    1. With the current setup, the "${other-route-variable}" would be required to be provided in the templatefile function call, otherwise it would fail.

    2. The path.root option is built-in in Terraform [2].

    In theory, if you were to create a module from this to make it more portable, then you would just have to change the path to the file so it can be provided through a variable probably.


    [1] https://www.terraform.io/language/functions/templatefile

    [2] https://www.terraform.io/language/expressions/references#filesystem-and-workspace-info