> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/ai-sre/ai-sre-for-administrators/set-up-change-management/sources/terraform.md).

# Configure Terraform for Deploy Change Investigator

Track Terraform infrastructure changes by sending deployment webhooks when `terraform apply` completes.

### Before you begin <a href="#before-you-begin" id="before-you-begin"></a>

* **Deploy Change Investigator setup:** Deploy webhook integration created in AI SRE. Go to [Deploy Change Investigator](/ai-sre/ai-sre-for-administrators/set-up-change-management/deploy-change-investigator.md) to create the webhook endpoint.
* **Terraform access:** Permission to modify Terraform configurations or CI/CD pipelines that run Terraform.
* **Deploy webhook URL:** Deploy webhook URL from the AI SRE integrations page.

***

### Integration approaches <a href="#integration-approaches" id="integration-approaches"></a>

Send Terraform deployment webhooks using one of these methods:

1. **CI/CD wrapper (recommended):** Send webhooks from the CI/CD pipeline after `terraform apply`.
2. **Local provisioner:** Use the `local-exec` provisioner in your Terraform configuration.
3. **Terraform Cloud:** Use run notifications.

***

### Option 1: CI/CD wrapper (recommended) <a href="#option-1-cicd-wrapper-recommended" id="option-1-cicd-wrapper-recommended"></a>

Send webhooks from your CI/CD pipeline after Terraform completes.

#### GitHub Actions <a href="#github-actions" id="github-actions"></a>

```yaml
name: Terraform Deploy

on:
  push:
    branches: [main]

env:
  TF_WORKSPACE: production

jobs:
  terraform:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.9.0

      - name: Terraform Init
        run: terraform init

      - name: Terraform Apply
        run: terraform apply -auto-approve

      - name: Send deploy webhook to AI SRE
        if: success()
        run: |
          curl -X POST "${{ secrets.AISRE_DEPLOY_WEBHOOK_URL }}" \
            -H "Content-Type: application/json" \
            -d '{
              "services": [{
                "service": "infrastructure-${{ env.TF_WORKSPACE }}",
                "version": "${{ github.sha }}"
              }],
              "environments": ["${{ env.TF_WORKSPACE }}"],
              "changeId": "${{ github.run_id }}",
              "status": "SUCCESS",
              "deployedBy": "${{ github.actor }}",
              "deployTimestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
              "metadata": {
                "tool": "terraform",
                "workspace": "${{ env.TF_WORKSPACE }}"
              }
            }'

      - name: Send failure webhook
        if: failure()
        run: |
          curl -X POST "${{ secrets.AISRE_DEPLOY_WEBHOOK_URL }}" \
            -H "Content-Type: application/json" \
            -d '{
              "services": [{
                "service": "infrastructure-${{ env.TF_WORKSPACE }}",
                "version": "${{ github.sha }}"
              }],
              "environments": ["${{ env.TF_WORKSPACE }}"],
              "changeId": "${{ github.run_id }}",
              "status": "FAILURE",
              "deployedBy": "${{ github.actor }}",
              "deployTimestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
              "metadata": {
                "tool": "terraform"
              }
            }'
```

***

### Option 2: Local provisioner <a href="#option-2-local-provisioner" id="option-2-local-provisioner"></a>

Use a `null_resource` with `local-exec` provisioner to send webhooks from Terraform.

#### Create webhook notification resource <a href="#create-webhook-notification-resource" id="create-webhook-notification-resource"></a>

```hcl
variable "deploy_webhook_url" {
  description = "AI SRE deploy webhook URL"
  type        = string
  sensitive   = true
}

variable "services" {
  description = "List of services deployed"
  type = list(object({
    service = string
    version = string
  }))
}

variable "environment" {
  description = "Deployment environment"
  type        = string
}

resource "null_resource" "deploy_webhook" {
  # Trigger webhook only when deployed resources actually change
  triggers = {
    services_changed = jsonencode(var.services)
  }

  provisioner "local-exec" {
    when    = create
    command = <<-EOT
      curl -X POST "${var.deploy_webhook_url}" \
        -H "Content-Type: application/json" \
        -d '{
          "services": ${jsonencode(var.services)},
          "environments": ["${var.environment}"],
          "changeId": "${formatdate("YYYY-MM-DD'T'hh:mm:ssZ", timestamp())}",
          "status": "SUCCESS",
          "deployedBy": "terraform",
          "deployTimestamp": "${formatdate("YYYY-MM-DD'T'hh:mm:ssZ", timestamp())}"
        }'
    EOT
  }

  depends_on = [
    # List resources that must complete first
  ]
}
```

#### Use webhook notification <a href="#use-webhook-notification" id="use-webhook-notification"></a>

```hcl
resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  
  tags = {
    Name    = "app-server"
    Version = "1.2.3"
  }
}

resource "null_resource" "deploy_webhook" {
  triggers = {
    instance_id = aws_instance.app.id
  }

  provisioner "local-exec" {
    command = <<-EOT
      curl -X POST "${var.deploy_webhook_url}" \
        -H "Content-Type: application/json" \
        -d '{
          "services": [{
            "service": "app-server",
            "version": "${aws_instance.app.tags["Version"]}"
          }],
          "environments": ["${terraform.workspace}"],
          "changeId": "${timestamp()}",
          "status": "SUCCESS",
          "deployedBy": "terraform",
          "deployTimestamp": "${timestamp()}"
        }'
    EOT
  }

  depends_on = [aws_instance.app]
}
```

***

### Set webhook URL securely <a href="#set-webhook-url-securely" id="set-webhook-url-securely"></a>

#### Using environment variable <a href="#using-environment-variable" id="using-environment-variable"></a>

```bash
export TF_VAR_deploy_webhook_url="https://app.harness.io/..."
terraform apply
```

#### Using terraform.tfvars (add to .gitignore) <a href="#using-terraformtfvars-add-to-gitignore" id="using-terraformtfvars-add-to-gitignore"></a>

```hcl
deploy_webhook_url = "https://app.harness.io/..."
```

#### Using Terraform Cloud variables <a href="#using-terraform-cloud-variables" id="using-terraform-cloud-variables"></a>

Store the webhook URL as a sensitive workspace variable:

1. Navigate to workspace **Settings**, then select **Variables**
2. Add a variable:
   * **Key:** `deploy_webhook_url`
   * **Value:** Webhook URL
   * **Category:** Terraform variable
   * **Sensitive:** Yes

***

### Extract service information <a href="#extract-service-information" id="extract-service-information"></a>

#### From outputs <a href="#from-outputs" id="from-outputs"></a>

Define outputs to list deployed services:

```hcl
output "deployed_services" {
  description = "Services deployed by this configuration"
  value = [
    {
      service = "frontend"
      version = var.frontend_version
    },
    {
      service = "backend"
      version = var.backend_version
    }
  ]
}
```

#### From resource tags <a href="#from-resource-tags" id="from-resource-tags"></a>

```hcl
locals {
  deployed_services = [
    {
      service = aws_instance.app.tags["Service"]
      version = aws_instance.app.tags["Version"]
    }
  ]
}

resource "null_resource" "webhook" {
  provisioner "local-exec" {
    command = "curl ... -d '{\"services\": ${jsonencode(local.deployed_services)}}'"
  }
}
```

***

### Provisioner considerations <a href="#provisioner-considerations" id="provisioner-considerations"></a>

#### Run only on create <a href="#run-only-on-create" id="run-only-on-create"></a>

```hcl
provisioner "local-exec" {
  when    = create
  command = "curl ..."
}
```

#### Handle errors <a href="#handle-errors" id="handle-errors"></a>

```hcl
provisioner "local-exec" {
  command = <<-EOT
    set -e
    response=$(curl -s -w "\n%{http_code}" ... -d '...')
    http_code=$(echo "$response" | tail -n1)
    if [ "$http_code" -ne 200 ]; then
      echo "Webhook failed with status $http_code"
      exit 1
    fi
  EOT
}
```

***

### Testing webhooks <a href="#testing-webhooks" id="testing-webhooks"></a>

#### Test from CI/CD <a href="#test-from-cicd" id="test-from-cicd"></a>

Run an apply in your pipeline and confirm the webhook reaches AI SRE:

1. Run `terraform apply` in CI/CD
2. Check pipeline logs for webhook execution
3. Navigate to **AI SRE**, then select **Integrations**
4. Click the **More** icon on the DEPLOY integration
5. Select **Debug**
6. Verify the webhook appears

#### Test local provisioner <a href="#test-local-provisioner" id="test-local-provisioner"></a>

Run an apply locally and confirm the webhook reaches AI SRE:

1. Run `terraform apply` locally
2. Check the console output for curl execution
3. Verify the webhook in the AI SRE Debug view

***

### Troubleshooting <a href="#troubleshooting" id="troubleshooting"></a>

<details>

<summary>Terraform local-exec provisioner fails silently when sending AI SRE webhooks</summary>

A local-exec provisioner does not fail the apply when curl fails. Add error checking in the provisioner command, for example capture the HTTP status code and exit 1 when it is not 200.

</details>

<details>

<summary>Terraform webhook sent on destroy to AI SRE</summary>

The provisioner was configured with when = destroy, so it runs during terraform destroy. Remove the when = destroy argument. A local-exec provisioner runs at create time by default, and create is not a valid value for when in current Terraform.

</details>

<details>

<summary>Terraform variable interpolation errors in AI SRE webhook commands</summary>

Variables may not expand in the provisioner command. Use proper HCL string interpolation, for example command = <<-EOT curl ... -d '{\\"service\\": \\"${var.service\_name}\\

</details>

***

### Best practices <a href="#best-practices" id="best-practices"></a>

Follow these practices to keep Terraform change tracking reliable:

* **Store webhook URLs securely:** Use environment variables, Terraform Cloud sensitive variables, or CI/CD secrets.
* **Use the CI/CD wrapper:** It is more reliable than provisioners for webhook notifications.
* **Separate infrastructure changes:** Track infrastructure deployments separately from application deployments.
* **Version consistently:** Use the commit SHA or a timestamp for infrastructure change versions.

***

### Next steps <a href="#next-steps" id="next-steps"></a>

* Go to [Deploy Change Investigator](/ai-sre/ai-sre-for-administrators/set-up-change-management/deploy-change-investigator.md) to complete the setup.
* Go to [AI Agent RCA](/ai-sre/ai-sre-for-incident-responders/use-ai-agents/rca-change-agent.md) to understand how the AI agent uses change detection during incidents.
* Go to [Configure Jenkins](/ai-sre/ai-sre-for-administrators/set-up-change-management/sources/jenkins.md) to set up webhooks in Jenkins pipelines.
