From 2af3ccd989ca08f6c4ec39dcc9c7fed42b277899 Mon Sep 17 00:00:00 2001 From: Patrick de Ruiter Date: Sun, 2 Nov 2025 12:33:44 +0100 Subject: [PATCH] Add Terraform caching and destroy workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimizations: - Added Terraform provider caching to terraform-init job - Apply job now reuses cached .terraform directory - Cache persists across workflow runs (keyed by .terraform.lock.hcl) - Significantly faster init times on subsequent runs New terraform-destroy job: - Only triggered on pull requests with 'destroy' label - Requires manual approval via 'destroy-approval' environment - Self-contained with fresh init (no cache for safety) - Clear warnings and authorization verification - Three-step process: verify → plan → execute Security features: - Destroy only runs on labeled pull requests - Requires environment protection approval - Fresh terraform init without cache for verification - Detailed logging of who/what/when/where - Cannot be triggered on direct push to master Usage: 1. Create pull request with proposed destroy changes 2. Add 'destroy' label to the PR 3. Approve via Gitea environment protection 4. Review destroy plan in logs 5. Approve destroy-approval environment to execute Benefits: - Faster apply workflow (cached providers) - Safe destroy process with multiple safeguards - Clear audit trail for destructive operations - Self-contained destroy for maximum safety --- .gitea/workflows/sonarqube.yaml | 111 ++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/.gitea/workflows/sonarqube.yaml b/.gitea/workflows/sonarqube.yaml index fb08983..cb581d8 100644 --- a/.gitea/workflows/sonarqube.yaml +++ b/.gitea/workflows/sonarqube.yaml @@ -92,6 +92,16 @@ jobs: with: terraform_version: latest + - name: Cache Terraform Providers + uses: actions/cache@v3 + with: + path: | + .terraform + .terraform.lock.hcl + key: terraform-${{ hashFiles('**/.terraform.lock.hcl') }} + restore-keys: | + terraform- + - name: Terraform Init env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} @@ -168,6 +178,16 @@ jobs: with: terraform_version: latest + - name: Restore Terraform Cache + uses: actions/cache@v3 + with: + path: | + .terraform + .terraform.lock.hcl + key: terraform-${{ hashFiles('**/.terraform.lock.hcl') }} + restore-keys: | + terraform- + - name: Install AWS CLI run: | curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" @@ -220,3 +240,94 @@ jobs: TF_VAR_environment: ${{ secrets.ENVIRONMENT }} VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: terraform apply -auto-approve tfplan + + terraform-destroy: + name: Terraform Destroy + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'destroy') + environment: + name: destroy-approval + steps: + - name: Verify Destroy Authorization + run: | + echo "⚠️ CRITICAL: INFRASTRUCTURE DESTRUCTION REQUESTED" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "PR: ${{ github.event.pull_request.html_url }}" + echo "Requested by: ${{ github.actor }}" + echo "Repository: ${{ github.repository }}" + echo "Branch: ${{ github.head_ref }}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo "This action will PERMANENTLY DESTROY all infrastructure" + echo "managed by this Terraform configuration." + echo "" + echo "Waiting for manual approval via environment protection rules..." + + - name: Checking out + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: latest + + - name: Terraform Init (Fresh - No Cache) + env: + AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} + TF_BACKEND_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }} + TF_BACKEND_BUCKET: ${{ secrets.MINIO_BUCKET }} + TF_BACKEND_KEY: ${{ secrets.MINIO_STATE_KEY }} + TF_BACKEND_REGION: "main" + TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} + TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} + VAULT_ADDR: ${{ secrets.VAULT_ADDR }} + run: | + echo "Performing fresh terraform init (no cache for safety)..." + terraform init \ + -backend-config="endpoints={s3=\"${TF_BACKEND_ENDPOINT}\"}" \ + -backend-config="bucket=${TF_BACKEND_BUCKET}" \ + -backend-config="key=${TF_BACKEND_KEY}" \ + -backend-config="region=${TF_BACKEND_REGION}" \ + -backend-config="skip_credentials_validation=true" \ + -backend-config="skip_metadata_api_check=true" \ + -backend-config="skip_requesting_account_id=true" \ + -backend-config="skip_region_validation=true" \ + -backend-config="use_path_style=true" + + - name: Terraform Destroy Plan + env: + AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} + TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} + TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} + TF_VAR_datacenter: ${{ secrets.VSPHERE_DATACENTER }} + TF_VAR_cluster_name: ${{ secrets.VSPHERE_CLUSTER }} + TF_VAR_environment: ${{ secrets.ENVIRONMENT }} + VAULT_ADDR: ${{ secrets.VAULT_ADDR }} + run: | + echo "Generating destroy plan..." + terraform plan -destroy -out=destroy.tfplan + echo "" + echo "Destroy plan generated. Review the plan above carefully." + + - name: Terraform Destroy Execute + env: + AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} + TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} + TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} + TF_VAR_datacenter: ${{ secrets.VSPHERE_DATACENTER }} + TF_VAR_cluster_name: ${{ secrets.VSPHERE_CLUSTER }} + TF_VAR_environment: ${{ secrets.ENVIRONMENT }} + VAULT_ADDR: ${{ secrets.VAULT_ADDR }} + run: | + echo "🔥 DESTROYING INFRASTRUCTURE..." + echo "This cannot be undone!" + echo "" + terraform apply -auto-approve destroy.tfplan + echo "" + echo "✅ Infrastructure has been destroyed" + echo "State file updated in MinIO: ${{ secrets.MINIO_STATE_KEY }}"