Skip to content

Automating Infrastructure When Terraform Azure Provider Doesn’t Support a Feature

Terraform is our primary tool for automating infrastructure on Azure. However, one of the common challenges we face is keeping up with Azure’s frequent updates. When Azure releases new features, they first become available via ARM templates. However, since Terraform is a separate tool maintained by HashiCorp, support for these new features often lags behind. This delay can lead to manual updates, increasing the risk of human error and oversight.

To address this issue effectively, Terraform provides two key solutions:

  • Using azapi_update_resource – This allows us to integrate ARM template configurations within Terraform.
  • Using null_resource with local-exec – This enables us to run CLI commands to update specific resources.

Below, we will explore these two solutions in detail with practical examples.

1) Using azapi_update_resource

Terraform allows us to apply ARM template configurations directly using azapi_update_resource. This is particularly useful when a resource or property is not yet supported in the Terraform provider, but it is available via ARM.

Example: Updating Elastic Web App Scale Configuration

resource "azapi_update_resource" "elastic_web_app_scale" { 

    type      = "Microsoft.Web/sites@2022-09-01"

    name      = local.formatted_names[var.app_name]

    parent_id = data.azurerm_resource_group.main.id

    body = jsonencode({

      properties = {

        siteConfig = {

          minimumElasticInstanceCount = 1  # Always ready instances

          elasticWebAppScaleLimit     = 20 # Maximum scale limit

        }

      }

    })

    depends_on = [module.app_service]

   }

Explanation:

  • type: Specifies the Azure resource type (in this case, Microsoft.Web/sites with the API version 2022-09-01).
  • name: The name of the resource being updated.
  • parent_id: The ID of the resource group containing the resource.
  • body: Contains the JSON payload for updating the resource properties.
  • depends_on: Ensures the update happens after the related App Service module is created.

2) Using null_resource with local-exec

In cases where azapi_update_resource is not sufficient, we can use null_resource with local-exec to run Azure CLI (az) commands directly.

Example: Updating a Disk Using Azure CLI

resource "null_resource" "update_disk" { 

    provisioner "local-exec" {

      command = << EOC

        set -ex

        az disk update --name ${local.formatted_names_without_product[var.workstation_vm_disk]} \

                       --resource-group ${local.formatted_names_without_product[var.main_hub_rg_name]} \

                       --data-access-auth-mode=AzureActiveDirectory \

                       --public-network-access=Disabled \

                       --network-access-policy=DenyAll

      EOC

    }

    triggers = {

      always_run = "${timestamp()}"

    }

   }

Explanation:

  • provisioner “local-exec”: Executes a local shell command.
  • az disk update: Updates the specified Azure disk resource.
  • triggers: Ensures that the command is executed on every Terraform apply, forcing updates whenever necessary.

Handling null_resource Execution in Azure DevOps Pipelines

When executing null_resource commands in DevOps pipelines, authentication can be a challenge since Azure CLI commands require login. To resolve this, we add a login step using a service principal before executing our az commands.

Example: Authenticating in Azure DevOps Pipeline

resource "null_resource" "update_disk" { 

    provisioner "local-exec" {

      command = << EOC

        set -ex

        if [ -n "$ARM_CLIENT_ID" ]; then

          az login --service-principal \

                   --username="$ARM_CLIENT_ID" \

                   --password="$ARM_CLIENT_SECRET" \

                   --tenant="$ARM_TENANT_ID" \

                   --output=none

        fi

        az disk update --name ${local.formatted_names_without_product[var.workstation_vm_disk]} \

                       --resource-group ${local.formatted_names_without_product[var.main_hub_rg_name]} \

                       --data-access-auth-mode=AzureActiveDirectory \

                       --public-network-access=Disabled \

                       --network-access-policy=DenyAll

      EOC

    }

    triggers = {

      always_run = "${timestamp()}"

    }

   }

Explanation:

  • Checks if the ARM_CLIENT_ID environment variable exists, indicating an Azure DevOps pipeline execution.
  • If running inside a pipeline, logs into Azure using service principal credentials.
  • Executes the az disk update command only after authentication.

Conclusion

By leveraging these techniques, we ensure that our infrastructure remains automated, reducing the need for manual updates and minimizing human error. These strategies help us stay agile and keep our Terraform-based automation aligned with the latest Azure updates.

Leave a Reply

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