How to destroy an infra deployed with Terraform by .tfstate
Tags: terraform, devops, cicd, aws
Published: 2024-02-26 18:30:11

Sometimes it's necessary to remove previously deployed infrastructure using Terraform, but in some cases it can be challenging to do so.

This could be due to old pipelines where destroying cannot be started due to breaking changes in configuration (e.g. environment variables no longer exists) or manually created infrastructure during development.

Today I want to show you a quick and easy way using only the .tfstate files where Terrafrom stores the [current state](https://developer.hashicorp.com/terraform/language/state) (assuming you follow best practices and store them in one place like an S3 bucket).

## Initial configuration

In my examples, I'll use AWS, but you can easily adapt this to any other [supported](https://registry.terraform.io/browse/providers) provider.

You'll need the correct version of Terraform (the version of Terraform used for apply can be found in `terraform_version` at the beginning of the .tfstate file), to switch between versions I recommend [tfenv](https://github.com/tfutils/tfenv).

First, create an `.env` where we'll specify all the variables. For backend configuration we'll pass it as command-line arguments because Terraform still doesn't support variables in the backend section.

```dotenv
TF_VAR_account_id=000000000000
TF_VAR_region=us-east-1
TF_VAR_access_key=XXXXXXXXXXXXXXXXXXXX
TF_VAR_secret_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TF_BACKEND_BUCKET=your-bucket-name
TF_BACKEND_KEY=path/to/terraform.tfstate
TF_BACKEND_ENCRYPT=true
```

Next, create `main.tf` specifying the provider, its version, and the used backend. Use the optional `allowed_account_ids` parameter to ensure it runs in the correct AWS account.

```terraform
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "~> 5.9"
    }
  }

  backend "s3" {}
}

provider "aws" {
  allowed_account_ids = [var.account_id]
  region = var.region
  access_key = var.access_key
  secret_key = var.secret_key
}
```

Now let's run `terraform init`, (Note: we use the `env $(cat .env | xargs)` to read variables from the .env file).

```shell
env $(cat .env | xargs) terraform init \
  -backend-config="region=$TF_VAR_region" \
  -backend-config="access_key=$TF_VAR_access_key" \
  -backend-config="secret_key=$TF_VAR_secret_key" \
  -backend-config="bucket=$TF_BACKEND_BUCKET" \
  -backend-config="key=$TF_BACKEND_KEY" \
  -backend-config="encrypt=$TF_BACKEND_ENCRYPT"
```

And now we can destroy all the infra registered in the .tfstate file.

```shell
terraform destroy
```

## Creating a utility

Usually if you start doing this you'll have to execute multiple times which is not very convenient, let's create a `run.sh` entrypoint where we can pass the path to the .tfstate file as argument.

```shell
#!/bin/bash

if [ -z $1 ]; then
  echo "Path to terraform.tfstate is required"
  exit 1
fi

export $(cat .env | xargs)
export TF_BACKEND_KEY=$1

terraform init -reconfigure \
  -backend-config="region=$TF_VAR_region" \
  -backend-config="access_key=$TF_VAR_access_key" \
  -backend-config="secret_key=$TF_VAR_secret_key" \
  -backend-config="bucket=$TF_BACKEND_BUCKET" \
  -backend-config="key=$TF_BACKEND_KEY" \
  -backend-config="encrypt=$TF_BACKEND_ENCRYPT"

terraform destroy -auto-approve
```

We added the `-reconfigure` to init to reuse cached [plugins and modules](https://developer.hashicorp.com/terraform/cli/init#working-directory-contents) (don't delete the .terraform folder between calls, it'll significantly speed up the process). To avoid confirming destroy each time, we added the `-auto-approve` flag.

## How to use it

Now, you can run it (remember to make it executable with `chmod +x`).

```shell
./run.sh path/to/terraform.tfstate
```

To batch run, we need to find all matching .tfstate files and call our script for each of them using `awk`. Please double check that you have filtered only what you need to destroy before running it.

For AWS with the S3 backend run this (if you are using another backend, you'll need to adapt this command).

```shell
aws s3 ls s3://your-bucket-name --recursive |
  grep terraform.tfstate |
  awk '{ system("./run.sh" $4) }'
```

That's it, just run it and wait for Terraform to do the rest.