From 1e1600357bcef71bfa0e5183ade41ba4ce605286 Mon Sep 17 00:00:00 2001 From: Erik Osterman Date: Wed, 1 Aug 2018 15:05:19 -0700 Subject: [PATCH] Add scripts to easily destroy tfstate backend (#33) * Add scripts to easily destroy tfstate backend * Improve annotations * Address CR * Fix formatting * Use variable for force_destroy * remove vim typo * Update instructions --- aws/tfstate-backend/Makefile | 7 ++++ aws/tfstate-backend/README.md | 33 ++++++++++--------- aws/tfstate-backend/main.tf | 21 ++++++++---- aws/tfstate-backend/scripts/destroy.sh | 30 +++++++++++++++++ .../{install.sh => scripts/init.sh} | 18 ++++++++-- aws/tfstate-backend/terraform.tfvars.example | 1 + 6 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 aws/tfstate-backend/Makefile create mode 100755 aws/tfstate-backend/scripts/destroy.sh rename aws/tfstate-backend/{install.sh => scripts/init.sh} (61%) diff --git a/aws/tfstate-backend/Makefile b/aws/tfstate-backend/Makefile new file mode 100644 index 000000000..51b2b50a5 --- /dev/null +++ b/aws/tfstate-backend/Makefile @@ -0,0 +1,7 @@ +## Initialize the configuration (should only be run once) +init: + @scripts/$@.sh + +## Destroy the configuration (only works if `force_destroy=true`) +destroy: + @scripts/$@.sh diff --git a/aws/tfstate-backend/README.md b/aws/tfstate-backend/README.md index a3edd96fc..5a7634dbf 100644 --- a/aws/tfstate-backend/README.md +++ b/aws/tfstate-backend/README.md @@ -1,26 +1,29 @@ # Bootstrap Process -Run this process the very first time you setup the tfstate bucket. +Perform these steps in each account, the very first time, in order to setup the tfstate bucket. -**IMPORTANT:** This has already been performed for this account, so this is documented here just for reference. +## Create -Ensure the following environment variables have been set in the `Dockerfile`: +Provision the bucket: ``` -ENV TF_BUCKET="cp-staging-terraform-state" -ENV TF_BUCKET_REGION="us-west-2" -ENV TF_DYNAMODB_TABLE="cp-staging-terraform-state-lock" +make init ``` -Then run these commands: - -1. Comment out the `s3 { ... }` section in `main.tf` - -2. Run `init-terraform` +Follow the instructions at the end. Ensure the environment variables have been set in the `Dockerfile`. +They look something like this: +``` +ENV TF_BUCKET="cpco-staging-terraform-state" +ENV TF_BUCKET_REGION="us-west-2" +ENV TF_DYNAMODB_TABLE="cpco-staging-terraform-state-lock" +``` -3. Run `terraform apply` +## Destroy -4. Re-enable `s3 { ... }` section in `main.tf` +To destroy the state bucket, first make sure all services in the account have already been destroyed. -5. Re-run `init-terraform` +Then run: +``` +make destroy +``` -6. Re-run `terraform apply`, answer `yes` when asked to import state +**NOTE:** This will only work if the state was previously initialized with `force_destroy=true`. If not, set `force_destroy=true`, rerun `terraform apply`, then run `make destroy`. diff --git a/aws/tfstate-backend/main.tf b/aws/tfstate-backend/main.tf index fc959c2ef..214f9f98a 100644 --- a/aws/tfstate-backend/main.tf +++ b/aws/tfstate-backend/main.tf @@ -51,12 +51,19 @@ variable "region" { default = "us-west-2" } +variable "force_destroy" { + type = "string" + description = "A boolean that indicates the S3 bucket can be destroyed even if it contains objects. These objects are not recoverable." + default = "false" +} + module "tfstate_backend" { - source = "git::https://github.com/cloudposse/terraform-aws-tfstate-backend.git?ref=tags/0.1.1" - namespace = "${var.namespace}" - name = "${var.name}" - stage = "${var.stage}" - attributes = "${var.attributes}" - tags = "${var.tags}" - region = "${var.region}" + source = "git::https://github.com/cloudposse/terraform-aws-tfstate-backend.git?ref=tags/0.1.1" + namespace = "${var.namespace}" + name = "${var.name}" + stage = "${var.stage}" + attributes = "${var.attributes}" + tags = "${var.tags}" + region = "${var.region}" + force_destroy = "${var.force_destroy}" } diff --git a/aws/tfstate-backend/scripts/destroy.sh b/aws/tfstate-backend/scripts/destroy.sh new file mode 100755 index 000000000..60070a57d --- /dev/null +++ b/aws/tfstate-backend/scripts/destroy.sh @@ -0,0 +1,30 @@ +# Start with a clean slate +rm -rf .terraform terraform.tfstate + +# Init terraform with S3 state enabled. Assumes state was previously initialized. +init-terraform + +# Unmount remote bucket (if mounted) +s3 unmount + +# Store the current state so we can destroy resources without catch-22 +terraform state pull > terraform.tfstate + +# Delete current state folder to remove all hints of local & remote state +rm -rf .terraform + +# Disable S3 state backend so that we use local state file +sed -Ei 's/^(\s+backend\s+)/#\1/' main.tf + +# Reintialize TF state without backend, using local `terraform.tfstate` +terraform init + +# Destroy terraform state. Note, only buckets that were created with `force_destroy=true` will successfully be destroyed. +# https://github.com/hashicorp/terraform/issues/7854#issuecomment-293893541 +terraform destroy -auto-approve + +# Re-enable S3 backend +sed -Ei 's/^#(\s+backend\s+)/\1/' main.tf + +# Clean up +rm -rf .terraform terraform.tfstate diff --git a/aws/tfstate-backend/install.sh b/aws/tfstate-backend/scripts/init.sh similarity index 61% rename from aws/tfstate-backend/install.sh rename to aws/tfstate-backend/scripts/init.sh index e3eb91cf2..52d648b4e 100755 --- a/aws/tfstate-backend/install.sh +++ b/aws/tfstate-backend/scripts/init.sh @@ -1,27 +1,41 @@ #!/usr/bin/env bash +# This script automates the cold-start process of provisioning the Terraform state backend using terraform DISABLE_ROLE_ARN=${DISABLE_ROLE_ARN:-0} +# Start from a clean slate +rm -rf .terraform terraform.tfstate + +# Disable S3 backend sed -Ei 's/^(\s+backend\s+)/#\1/' main.tf + +# Disable Role ARN (necessary for root account on cold-start) [ "${DISABLE_ROLE_ARN}" == "0" ] || sed -Ei 's/^(\s+role_arn\s+)/#\1/' main.tf +# Initialize terraform modules and providers init-terraform -echo "yes" | terraform apply + +# Provision S3 bucket and dynamodb tables +terraform apply -auto-approve export TF_BUCKET=$(terraform output -json | jq -r .tfstate_backend_s3_bucket_id.value) export TF_DYNAMODB_TABLE=$(terraform output -json | jq -r .tfstate_backend_dynamodb_table_id.value) export TF_BUCKET_REGION=${TF_VAR_region} +# Re-enable S3 backend sed -Ei 's/^#(\s+backend\s+)/\1/' main.tf +# Reinitialize terraform to import state to remote backend echo "yes" | init-terraform +# Re-enable Role ARN [ "${DISABLE_ROLE_ARN}" == "0" ] || sed -Ei 's/^#(\s+role_arn\s+)/\1/' main.tf +# Describe how to use the S3/DynamoDB resources with Geodesic echo "Add the following to the Geodesic Module's Dockerfile:" echo "#----------------------------------------------" echo "ENV TF_BUCKET=\"${TF_BUCKET}\"" echo "ENV TF_BUCKET_REGION=\"${TF_BUCKET_REGION}\"" echo "ENV TF_DYNAMODB_TABLE=\"${TF_DYNAMODB_TABLE}\"" echo "#----------------------------------------------" -echo "And rebuild the module" +echo "...and rebuild the module" diff --git a/aws/tfstate-backend/terraform.tfvars.example b/aws/tfstate-backend/terraform.tfvars.example index cc5cc21e2..34721b402 100644 --- a/aws/tfstate-backend/terraform.tfvars.example +++ b/aws/tfstate-backend/terraform.tfvars.example @@ -1,2 +1,3 @@ namespace="cp" stage="staging" +force_destroy="false"