Handling Sensitive Values in Terraform

🔐 Real-World Use Case: Secrets That Shouldn’t Leak

Imagine this: you’re building an AKS cluster, provisioning Key Vault secrets, and injecting them into Kubernetes secrets or application settings. You deal with:

      • Client secrets
      • Database passwords
      • API tokens
      • Certificates or private keys

Now imagine someone running terraform apply in a CI/CD pipeline—and your secret values show up in terminal output or Terraform Cloud logs. This isn’t just bad hygiene—it’s a serious security risk.

Continue reading “Handling Sensitive Values in Terraform”

How to Use Terraform null_resource for Real-World Automation Scenarios

“Everything should be declarative… until it can’t be.”

When Terraform Meets the Real World

You’ve built a beautiful Terraform module—cleanly structured, fully parameterized, and cloud-native. You’re provisioning an Azure Kubernetes Service (AKS) cluster with node pools, RBAC, and even identity bindings. All good. But then your enterprise architecture demands something extra:

🔸 “Every time a cluster is deployed or updated, we need to register it in our external CMDB system via a REST API call.”

You check Terraform Registry—no CMDB provider.
You check Azure native capabilities—no integration hook.

And that’s when it hits you: Terraform is declarative, not procedural. It doesn’t natively do things after a resource is provisioned. It just models infrastructure.

But in the real world, things must happen after infra is built—scripts must run, records must be updated, configurations must be patched. This is where null_resource becomes your secret weapon.


Real-World Example 1: Registering a Resource in External CMDB

resource "null_resource" "register_aks_cmdb" {
  triggers = {
    cluster_name = azurerm_kubernetes_cluster.myaks.name
    location     = azurerm_kubernetes_cluster.myaks.location
    version      = azurerm_kubernetes_cluster.myaks.kubernetes_version
    owner        = var.owner_tag
  }

  provisioner "local-exec" {
    command = <<EOT
      curl -X POST https://cmdb.company.com/api/cluster/register \
      -H "Authorization: Bearer ${var.cmdb_api_token}" \
      -H "Content-Type: application/json" \
      -d '{
        "cluster": "${self.triggers.cluster_name}",
        "location": "${self.triggers.location}",
        "version": "${self.triggers.version}",
        "owner": "${self.triggers.owner}"
      }'
    EOT
  }
}

This wraps external CMDB integration neatly into Terraform without requiring another tool. Changes to the AKS cluster name, location, or version will automatically re-trigger this block.


Real-World Example 2: Bootstrap Configuration on Azure VM

resource "null_resource" "vm_bootstrap" {
  depends_on = [azurerm_linux_virtual_machine.appvm]

  triggers = {
    hostname   = azurerm_linux_virtual_machine.appvm.name
    config_ver = var.app_config_version
  }

  provisioner "file" {
    source      = "${path.module}/configs/bootstrap_${var.env}.cfg"
    destination = "/tmp/bootstrap.cfg"
  }

  provisioner "remote-exec" {
    inline = [
      "sudo cp /tmp/bootstrap.cfg /etc/app/config.cfg",
      "sudo chown root:root /etc/app/config.cfg",
      "sudo systemctl restart app"
    ]
  }

  connection {
    type        = "ssh"
    user        = "azureuser"
    private_key = file(var.ssh_private_key)
    host        = azurerm_linux_virtual_machine.appvm.public_ip_address
  }
}

This example uses both file and remote-exec provisioners to bootstrap a VM post-provisioning based on config version.

Note:

💡 null_resource and null value are completely different things in Terraform.

null_resource is a pseudo-resource to attach provisioners and triggers.
null value is used in expressions as an unset or fallback value.

They serve entirely different purposes.


How null_resource Really Works

At its core, null_resource uses a triggers block—a key-value map that Terraform watches. If any of the values change between runs, Terraform destroys and recreates the null_resource, re-executing its provisioners.

This allows you to attach imperative logic (like scripts or file uploads) to changes in your infrastructure—without modifying the infrastructure itself.

Supported provisioners include:

    • local-exec – Runs commands on the local machine where Terraform is executed.
    • remote-exec – Runs commands over SSH or WinRM on the provisioned remote machine.
    • file – Transfers files to remote machines over SCP or SFTP.

⚠️ Important: Provisioners should always be treated as a last resort in Terraform. If there’s a provider-based or declarative approach to achieve the same outcome, that is always preferred.

Still, null_resource becomes essential in situations where you must perform actions that Terraform can’t natively model—like interacting with external systems or performing custom post-deploy steps.


Using null_resource to Refresh Azure Diagnostic Settings

Another underrated but powerful use of null_resource is in the context of managing Azure Diagnostic Settings, especially when you’re applying them across many resources or want to force a refresh of configuration periodically or conditionally.

🔧 Common Problems with Diagnostic Settings

    • Terraform may not detect category-level changes or fail to reapply settings cleanly.
    • Diagnostic categories often differ between resource types and evolve over time.
    • You may want to re-trigger the configuration based on a config change or time window.

✅ Solution with null_resource and timestamp()

resource "null_resource" "force_diag_setting" {
  triggers = {
    resource_id = azurerm_windows_virtual_machine.vm.id
    config_hash = filesha256("${path.module}/diag_config.json")
    timestamp   = timestamp()
  }

  provisioner "local-exec" {
    command = "echo 'Re-evaluating diagnostic setting at ${self.triggers.timestamp}'"
  }
}

resource "azurerm_monitor_diagnostic_setting" "diag" {
  name                       = "diag-${azurerm_windows_virtual_machine.vm.name}"
  target_resource_id         = azurerm_windows_virtual_machine.vm.id
  log_analytics_workspace_id = var.law_id

  log {
    category = "Administrative"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  depends_on = [null_resource.force_diag_setting]
}

🎯 Optional: Use timestamp() Conditionally

locals {
  force_refresh = var.force_diag_refresh ? timestamp() : ""
}

resource "null_resource" "diag_retrigger" {
  triggers = {
    resource_id = azurerm_key_vault.kv.id
    force_time  = local.force_refresh
  }

  provisioner "local-exec" {
    command = "echo 'Triggered diagnostic refresh due to force flag.'"
  }
}

This gives you controlled imperativeness—a pattern that works well when strict refresh logic is needed without always forcing changes.


Thumb-Rules for Production Usage

    • Use triggers wisely: Base them on stable, deterministic inputs only.
    • Don’t chain null_resource instances: It’s not a workflow engine.
    • Keep actions idempotent: Scripts should be safe to re-run.
    • Use as a fallback: Avoid null_resource when native resources exist.
    • Isolate logic: Treat complex null_resource workflows as edge-case modules.

Final Thoughts

The null_resource in Terraform is like a pressure valve. When your pristine, declarative infrastructure hits the edge of reality, it gives you just enough imperative power to fill the gap—without switching tools.

But treat it like duct tape: useful in emergencies, dangerous if overused.

Use it to patch. Not to build.

 

Terraform flatten: Tackling Complex Data Structures in Azure Deployments

In the world of infrastructure automation, we’re constantly juggling concepts like “clean state”, “idempotency”, and “predictable output”.

But when you’re deep into Azure — where everything from subnets to role assignments tends to be wrapped in nested lists or objects — things can get messy fast. That’s where Terraform’s flatten function steps in. It’s not just another utility function — it’s a lifesaver when your data starts to spiral out of control.

Continue reading “Terraform flatten: Tackling Complex Data Structures in Azure Deployments”