Hopefully a few people will find this post useful as I am trying to do a few things ive seen other people struggle with. My scenario is that I want to dynamically get the url for a logic app in terraform from the call back url for the Logic App and then use it within the deployment of an APIM Policy so that the APIM operation will forward calls to the logic app.
The problem is that in terraform there is no data source which lets you access the call back url.
In solving this problem I am also able to demonstrate a way to interact with Azure CLI from Terraform to be able to do things which arent yet supported by the provider. You can do this with local-exec but I believe there are limitations on being able to return data back to terraform so I chose to use the external data source which once I figured out how to do it seems to provide a cool extension point.
Lets do a quick step thru
Powershell Script
I chose to write a powershell script which will contain my script so I can call out to Azure CLI and manipulate some input and output. The script will accept input in a json format from Terraform and then output json back to Terraform via the console.
in the script I will use Az Rest to query the logic app url information and then parse it to get the various bits of the url and also encode them so that the data source will be useful for a few scenarios where I might need to use it.
#This script will be called from Terraform to access a logic app and retrieve the trigger url and return it to terraform as variables that can be used in an APIM policy
#We are adding system.web to parse the url later and encode it
Add-Type -AssemblyName System.Web
#You can pass in input from Terraform like this from the external data source and then
#convert it to a json object so you can use it
$jsonpayload = [Console]::In.ReadLine()
$json = ConvertFrom-Json $jsonpayload
# Access JSON values
$terraformSubscriptionId = $json.subscriptionId
$terraformResourceGroup = $json.resourceGroup
$terraformLogicAppName = $json.logicAppName
$logicAppUrl = "https://management.azure.com/subscriptions/" + $terraformSubscriptionId + "/resourceGroups/" + $terraformResourceGroup + "/providers/Microsoft.Logic/workflows/" + $terraformLogicAppName + "/triggers/manual/listCallbackUrl?api-version=2016-06-01"
#az login
try{
$output = az rest --method post --uri $logicAppUrl
$logicAppJson = $output | ConvertFrom-Json
$logicAppUriString = $logicAppJson.value
#Use system.uri to parse the url and get the various bits of it
$logicAppUri = [System.Uri]$logicAppJson.value
$logicAppUriPathAndQuery = $logicAppUri.PathAndQuery
$logicAppBaseUrl = $logicAppUri.GetLeftPart('Authority')
#Encode the strings so they are easy to use in things like APIM policy
$encodedUriFull = [System.Web.HttpUtility]::HtmlEncode($logicAppUriString)
$encodedUriPath = [System.Web.HttpUtility]::HtmlEncode($logicAppUriPathAndQuery)
$outputJson = @{
logicAppBaseUrl = "$logicAppBaseUrl";
logicAppUriPathAndQueryXmlEncoded = "$encodedUriPath";
logicAppUriPathAndQuery = "$logicAppUriPathAndQuery";
logicAppFullUrl = "$logicAppUriString";
logicAppFullUrlXmlEncoded = "$encodedUriFull";
} | ConvertTo-Json
#Outputting like this will write the json object back to Terraform, note it needs to be like a string dictionary
Write-Output $outputJson
}
catch{
#You can output errors to Terraform too, if you dont do this its sometimes tricky to troubleshoot issues in your script when terraform runs it
Write-Error $_
exit 1
}
I just put this script in my terraform directory and then I can use it.
One point to note is that I could have possibly used the powershell azure cmdlets but I have found a few times its been a bit of a pain with the logic app ones conflicting with other versions of the az cmdlets so I can avoid this using az rest against the management api, and also using terraform im also logged into an az cli session so there are no additional authentication hoops to jump through.
Terraform External Data Source
The below is an example of using the terraform external data source. You can see that it uses powershell and points to be script. I am able to send some variables to the script from Terraform such as resource group name and logic app name which the script needs. As mentioned above the query setting below in terraform appears in the script as a json string.
data "external" "logicapps_mylogicapp" {
program = ["Powershell.exe", "Set-ExecutionPolicy Bypass -Scope Process -Force; ./GetLogicAppUrls.ps1"]
query = {
logicAppName = "My-Logic-App-Name"
resourceGroup = var.eai_resource_group
subscriptionId = data.azurerm_client_config.current.subscription_id
}
}
Referencing the External Data Source
The output from the data source is a result property with a set of sub-properties based on the json you return from the script. If you remember above I returned a few different elements from the url for the logic app in a way which will be useful in terraform.
Below are a couple of examples where I am outputting these as output variables to show how you can use them.
output "logicAppBaseUrl" {
value = data.external.logicapps_mylogicapp.result.logicAppBaseUrl
}
output "logicAppUriPathAndQuery" {
value = data.external.logicapps_mylogicapp.result.logicAppUriPathAndQuery
}
output "logicAppFullUrl" {
value = data.external.logicapps_mylogicapp.result.logicAppFullUrl
}
output "logicAppUriPathAndQueryXmlEncoded" {
value = data.external.logicapps_mylogicapp.result.logicAppUriPathAndQueryXmlEncoded
}
output "logicAppFullUrlXmlEncoded" {
value = data.external.logicapps_mylogicapp.result.logicAppFullUrlXmlEncoded
}
APIM Policy
As mentioned at the start of the post I am wanting to use these logic app url elements within a policy which is in xml in my terraform to deploy an api operation. Below are 2 snippets from the policy where I am using the uri rewrite and set backend policies to point the operation to my logic app.
If you note that I use the xml encoded url path property in the APIM policy because the policy is an xml file which when loaded to Azure needs the path to be encoded which we did earlier in the powershell script.
<rewrite-uri template="${data.external.logicapps_mylogicapp.result.logicAppUriPathAndQueryXmlEncoded}" copy-unmatched-params="true" />
<set-backend-service base-url="${data.external.logicapps_mylogicapp.result.logicAppBaseUrl}" />
Summary
Hopefully this shows you first off that its easy in Terraform to reach out to Az CLI where you need to so that you can query data which isnt supported by the terraform provider. You can then return info back to terraform and use it in your script.
It took a little pain to get this working but theres a couple of places I think I can use this to help me extend my terraform to do more cool stuff.