Azure, Cloud, DevOps, IaC

Dirty Terraform Tricks with Azure

No matter how much you want to automate your Azure Infrastructure deployment using terraform, you are going to find some use cases...

Written by Freddy Ayala · 3 min read >

No matter how much you want to automate your Azure Infrastructure deployment using terraform, you are going to find some use cases where you can’t do things in a conventional ways and you have to get your hands dirty.

Terraform is a wonderful technology that feels very natural. Developers and Sysadmins feel at home with the simple syntax. Furthermore the current Azure RM Terraform Provider covers most use cases and has most Azure resources available. So terraform seems great, what’s the catch? It is simple, even tough terraform is great there are some situations where you have no other option but to cheat, so let’s see some of such situations:

Your resource is not supported by terraform

There are just some resources or functionalities that just aren’t supported yet by terraform, in order to solve this problem we have the following options:

  • Deploy it outside terraform using arm templates, powershell, our AZ CLI
  • Deploy it inside terraform using AZ CLI or the comamnd line
  • Deploy it inside terraform using arm templates
What?! ARM inside terraform

The 3 options can be used, but it depends on your use case. Personally when the resource is quite small such as a webhook, diagnostic setting or alert, I would just use AZ CLI inside terraform using a null_resource:

#Window Server 2019 Shared Image Deployment with plan, no other solution

resource "null_resource" "deploy_cis-windows-server-2019" {
  provisioner "local-exec" {
    command ="az sig image-definition create  --gallery-name ${azurerm_shared_image_gallery.example.name} --resource-group ${var.resource_group} --gallery-image-definition cis-windows-server-2019 --publisher center-for-internet-security-inc --offer cis-windows-server-2019-v1-0-0-l1 --sku cis-ws2019-l1 --os-type windows --plan-name cis-ws2019-l1 --plan-product cis-windows-server-2019-v1-0-0-l1  --plan-publisher center-for-internet-security-inc"
  }

provisioner "local-exec" {
when = "destroy"
command= "az sig image-definition delete--gallery-name ${azurerm_shared_image_gallery.example.name} --resource-group ${var.resource_group} --gallery-image-definition cis-windows-server-2019"
}
  
}

In this case the plan information is not supported in the official terraform resource so had no choice but the use a local-exec to keep the deployment consistent and avoid to create different pipelines just for one resource.

Furthermore we are required to add the section when =”destroy” to be able to cleanup the resource when using terraform deploy.

This way you can deploy resources that normally you wouldn’t be able to using terraform. My recommendation is to use this only if you have no other choice and don’t take it too far.

What about ARM Templates? Well you can use terraform to deploy ARM templates as well:

resource "azurerm_template_deployment" "deploy_webhook" {
  name                = "deploy-webhook-imgfact"
  resource_group_name = var.resource_group
  template_body       = file("${path.module}/../arm/deploywebhook.json")

  parameters = {
    "automationAccountName" = var.azurerm_automation_account.name
    "runbookName"           = azurerm_automation_runbook.runbook.name
    "webhookName"           = "imgfact-webhook"
    "webhookUri"            =  "http://myweboohook.com"
  }

  deployment_mode = "Incremental"
    depends_on      = [azurerm_automation_runbook.runbook,null_resource.generate_uri_automation]


}

As the previous case, use if if you have no other choice.

The plan is not consistent

When you thought that using local_exec was the the silver bullet we were looking for our terraform azure deployments we find could run into a very ugly error message:

Provider produced inconsistent final plan

Take the following example:

resource "null_resource" "generate_uri_automation" {
  provisioner "local-exec" {
    command = "echo -n `az rest -m post --header 'Accept=application/json' -u https://management.azure.com/subscriptions/${var.sp.subscription_id}/resourceGroups/${var.resource_group}/providers/Microsoft.Automation/automationAccounts/${var.azurerm_automation_account.name}/webhooks/generateUri?api-version=2015-10-31 -o tsv` &> ./automation/generate_uri/webhookUri2.txt"
  }
  

}

# Use ARM deployment because there is no Terraform deployment available for Automation Webhooks
resource "azurerm_template_deployment" "deploy_webhook" {
  name                = "deploy-webhook-imgfact"
  resource_group_name = var.resource_group
  template_body       = file("${path.module}/../arm/deploywebhook.json")

  parameters = {
    "automationAccountName" = var.azurerm_automation_account.name
    "runbookName"           = azurerm_automation_runbook.runbook.name
    "webhookName"           = "imgfact-webhook"
    "webhookUri"            =  "${file("${path.module}/../generate_uri/webhookUri2.txt")}"
  }

  deployment_mode = "Incremental"
    depends_on      = [azurerm_automation_runbook.runbook,null_resource.generate_uri_automation]


}

In this example we use a local provisioner to generate a webhook uri (there’s no other way to do it, thank you Microsoft!) and use an arm template to deploy the webhook (for some reason the official terraform resource is not working). So in the beginning of the terraform apply command everything goes well.

However at the end of the execution terraform will throw an error because the values of webhookuri have changed and the plan is no longer consistent.

So is there a way to tell terraform that hte WebHookUri is dynamic?

resource "azurerm_template_deployment" "deploy_webhook" {
  name                = "deploy-webhook-imgfact"
  resource_group_name = var.resource_group
  template_body       = file("${path.module}/../arm/deploywebhook.json")

  parameters = {
    "automationAccountName" = var.azurerm_automation_account.name
    "runbookName"           = azurerm_automation_runbook.runbook.name
    "webhookName"           = "imgfact-webhook"
    "webhookUri"            =  replace("=${azurerm_automation_runbook.runbook.id}=${file("${path.module}/../generate_uri/webhookUri2.txt")}", "=${azurerm_automation_runbook.runbook.id}=", "") #Dirty trick to force consistent plan
  }

  deployment_mode = "Incremental"
    depends_on      = [azurerm_automation_runbook.runbook,null_resource.generate_uri_automation]


}

replace(“=${azurerm_automation_runbook.runbook.id}=${file(“${path.module}/../generate_uri/webhookUri2.txt”)}”, “=${azurerm_automation_runbook.runbook.id}=”, “”)

So by using replace including an output value from another resource, we are signaling Terraform that indeed the webhookUri should be treated as a dynamic value.

what the … is this?

This way our plan will be consistent. Dirty but works.

Module depends_on

As of terraform 0.13 Module support now depends_on (thanks heaven!) but for the rest of the world that have to older versions of terraform, there is only way to make a module the execution of another one, it is to use output variables from another module as input variables to force terraform to wait the execution of the first module.

module "alert" {
  source                        = "./alert/"
  resource_group                = var.resource_group
  sp                            = var.sp
  onboarding_action_group_id    = module.onboarding.action_group_id
  backup_action_group_id        = module.backup.action_group_id
  image_factory_action_group_id = module.image_factory.action_group_id
}

Conclusion

These are some tricks that are very useful. However, they should be used as a last resort, you should be careful. This list will be updated when new tricks are discovered/needed. Time to get you hands dirty and terraform to your heart’s content.

DNS Resolution Problems in AKS

Freddy Ayala in Azure, Cloud
  ·   1 min read

4 Replies to “Dirty Terraform Tricks with Azure”

Leave a Reply

Your email address will not be published. Required fields are marked *