When working with Terraform it might become necessary to include an ARM template deployment for part of the solution. When this happens and the ARM template is creating resources with a managed identity it is necessary to return the managed identity to the Terraform script.
ARM templates can output values as part of their deployment process. These output details can be utilized in Terraform by using the output_content
of the azurerm_resource_group_template_deployment
.
Let’s build a simple example to see how to use the ARM template output.
Step 1: Building the ARM template
For this example, an ARM template to create a Storage Account with managed identity will be created. To make the ARM template easy to construct and easier to read, Bicep will be used.
param resourceGroupLocation string
param workloadName string
resource storageResourceGroup 'Microsoft.Storage/storageAccounts@2020-08-01-preview' = {
name: 'stor${workloadName}${resourceGroupLocation}'
location: resourceGroupLocation
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
accessTier: 'Hot'
minimumTlsVersion: 'TLS1_2'
}
identity: {
type: 'SystemAssigned'
}
}
output storageObjectId string = storageResourceGroup.identity.principalId
Lines 1 and 2 will create the parameters of the ARM template, so Terraform can populate the values. Line 5 is using the parameters to construct the Storage account name. Finally, line 21 an output parameter of type string is created to hold the Principal ID of the created Storage account.
Once we have the bicep template, the bicep build command is executed to generate the ARM template. Reproduced below.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"resourceGroupLocation": {
"type": "string"
},
"workloadName": {
"type": "string"
}
},
"functions": [],
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2020-08-01-preview",
"name": "[format('stor{0}{1}', parameters('workloadName'), parameters('resourceGroupLocation'))]",
"location": "[parameters('resourceGroupLocation')]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"properties": {
"accessTier": "Hot",
"minimumTlsVersion": "TLS1_2"
},
"identity": {
"type": "SystemAssigned"
}
}
],
"outputs": {
"storageObjectId": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Storage/storageAccounts', format('stor{0}{1}', parameters('workloadName'), parameters('resourceGroupLocation'))), '2020-08-01-preview', 'full').identity.principalId]"
}
}
}
Creating the Terraform script
Using the azurerm_resource_group_template_deployment
resource in Terraform, the script it will expand the ARM template created earlier and will deploy it. For simplicity of demonstration the Terraform script will just output the value returned by the ARM template.
resource "azurerm_resource_group_template_deployment" "template_deployment" {
name = "stor-template-deployment"
resource_group_name = azurerm_resource_group.rg_storage.name
deployment_mode = "Incremental"
template_content = file("arm/storage_account.json")
parameters_content = jsonencode({
resourceGroupLocation = { value = azurerm_resource_group.rg_storage.location }
workloadName = { value = "kdm" }
})
}
output "StorageID" {
value = azurerm_resource_group_template_deployment.template_deployment.output_content
}
When the script gets executed it will create the Storage Account and the value will get outputted as a JSON string. Sample below:
Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: StorageID = {"storageObjectId":{"type":"String","value":"<em><Some GUID></em>"}}
To access the value it is necessary to convert the string to a JSON object, access the parameter of interest and finally the value.
Updating the example to return the value of the Storage Account Principal ID.
output "StorageID" {
value = jsondecode(azurerm_resource_group_template_deployment.template_deployment.output_content).storageObjectId.value
}
The output will be more what one would expect.
Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: StorageID = <em><Some GUID></em>
Note for consideration
During development or updates, it might be necessary to update the output variables. When this scenario is encountered it is important to consider how Terraform determines the changes. Terraform will read values in the Terraform state to compare it to the desired state. When it comes to access the new output variable, it will throw an error on the property of the JSON object as it is not in the state.
A solution to deal with this scenario is to run the change in two phases. First run the ARM template with the new output parameters. Then update the Terraform script to use the output variable introduced. This will ensure that the new output variable is in output_content
property of the template deployment resource.