Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,26 @@ jobs:
- name: Run down-migration gate self-tests
run: bash scripts/test-migration-down-gate.sh

# Assert that scripts/gcp-import-dev-state.sh takes the GCP project and the
# state backend from GCP_PROJECT_ID and TF_BACKEND_GCP with no default, and
# refuses to start without them (#305). Runs the script end to end against
# stubbed terraform and gcloud; nothing reaches GCP. Fast (shell only), so it
# always runs.
gcp-import-dev-state:
name: GCP dev state import configuration
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

- name: Run import script self-tests
run: bash scripts/test-gcp-import-dev-state.sh

# Summary job - all checks must pass
ci-success:
name: CI Success
Expand All @@ -1731,6 +1751,7 @@ jobs:
- aws-tfstate-platform-key
- azure-kv-access-policy
- migration-down-gate
- gcp-import-dev-state
if: always()
permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion scripts/delete-owned-cloud-sql-instance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
# step already ran three state rm commands: those addresses were root-level
# (`google_sql_database_instance.main`), but the resources live under
# `module.database` (terraform/environments/gcp/database.tf:5;
# scripts/gcp-import-dev-state.sh:273-288 imports them module-qualified).
# scripts/gcp-import-dev-state.sh:334-349 imports them module-qualified).
# Measured on Terraform 1.14.7 against a state holding those three
# module-qualified resources: `terraform state rm
# google_sql_database_instance.main` exits 1 "No matching objects found" and
Expand Down
71 changes: 66 additions & 5 deletions scripts/gcp-import-dev-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,82 @@
# missing from the Terraform github-dev state in GCS.
#
# Usage:
# export GCP_PROJECT_ID=<project-id>
# export TF_BACKEND_GCP='bucket = "<state-bucket>"'
# export GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/cudly-terraform-deploy.json
# cd <repo-root>
# bash scripts/gcp-import-dev-state.sh
#
# GCP_PROJECT_ID and TF_BACKEND_GCP are required and have no defaults. They
# take the same values deploy-gcp.yml reads from vars.GCP_PROJECT_ID and
# secrets.TF_BACKEND_GCP: TF_BACKEND_GCP is the backend-config body (at least
# a `bucket = "..."` line), and this script appends `prefix = "github-dev"`
# exactly as the deploy workflow does. The project is also exported as
# TF_VAR_project_id so terraform and gcloud cannot disagree about it.
#
# Prerequisites:
# - gcloud CLI authenticated (or GOOGLE_APPLICATION_CREDENTIALS set)
# - terraform >= 1.10
# - Access to gs://cudly-terraform-state-cloudprowess
# - Access to the state bucket named in TF_BACKEND_GCP

set -euo pipefail

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
PROJECT="serene-bazaar-666"
usage() {
cat >&2 <<'USAGE'
usage: GCP_PROJECT_ID=<project-id> TF_BACKEND_GCP='bucket = "<state-bucket>"' \
bash scripts/gcp-import-dev-state.sh

GCP_PROJECT_ID GCP project whose cudly-dev resources are imported
(same value as the GCP_PROJECT_ID repository variable)
TF_BACKEND_GCP GCS backend config containing a bucket = "..." line
(same value as the TF_BACKEND_GCP repository secret);
prefix = "github-dev" is appended by this script
USAGE
}

# die MESSAGE: print the error and usage, then exit 2 before anything touches
# terraform, gcloud or the backend.
die() {
echo "ERROR: $*" >&2
usage
exit 2
}

PROJECT="${GCP_PROJECT_ID:-}"
TF_BACKEND="${TF_BACKEND_GCP:-}"

[ -n "$PROJECT" ] || die "GCP_PROJECT_ID is not set"
[ -n "$TF_BACKEND" ] || die "TF_BACKEND_GCP is not set"

# Validate the shape, not merely non-emptiness (same rule as
# delete-owned-cloud-sql-instance.sh). [[ =~ ]] matches the whole string, so a
# value with an embedded newline is rejected too.
[[ "$PROJECT" =~ ^[a-z][a-z0-9-]{5,29}$ ]] ||
die "GCP_PROJECT_ID is malformed ('${PROJECT}')"

# Without a bucket the GCS backend cannot initialise; with its own prefix the
# appended prefix line would be a duplicate attribute and terraform would
# reject the file. Fail on both here, before init runs.
grep -Eq '^[[:space:]]*bucket[[:space:]]*=' <<<"$TF_BACKEND" ||
die 'TF_BACKEND_GCP has no bucket = "..." line'
if grep -Eq '^[[:space:]]*prefix[[:space:]]*=' <<<"$TF_BACKEND"; then
die 'TF_BACKEND_GCP must not set prefix; this script always uses prefix = "github-dev"'
fi

# Terraform reads the project from var.project_id, gcloud from --project. A
# TF_VAR_project_id left over in the environment for another project would
# send imports into config for a different project than the one queried.
if [ -n "${TF_VAR_project_id:-}" ] && [ "$TF_VAR_project_id" != "$PROJECT" ]; then
die "TF_VAR_project_id ('${TF_VAR_project_id}') does not match GCP_PROJECT_ID ('${PROJECT}')"
fi
export TF_VAR_project_id="$PROJECT"

REGION="us-central1"
SERVICE_NAME="cudly-dev"
TF_DIR="$(cd "$(dirname "$0")/.." && pwd)/terraform/environments/gcp"
BACKEND_CONFIG="bucket = \"cudly-terraform-state-cloudprowess\"\nprefix = \"github-dev\""
BACKEND_FILE="/tmp/gcp-dev-backend.tfbackend"

export GOOGLE_APPLICATION_CREDENTIALS="${GOOGLE_APPLICATION_CREDENTIALS:-$HOME/.config/gcloud/cudly-terraform-deploy.json}"

Expand Down Expand Up @@ -61,7 +117,12 @@ echo "TF_DIR: $TF_DIR"
echo "Credentials: $GOOGLE_APPLICATION_CREDENTIALS"
[ -f "$GOOGLE_APPLICATION_CREDENTIALS" ] || { echo "ERROR: credentials file not found"; exit 1; }

printf '%b\n' "$BACKEND_CONFIG" > "$BACKEND_FILE"
# The backend config now comes from the TF_BACKEND_GCP secret, so write it to a
# private mktemp file (mode 0600, unique name) rather than a fixed /tmp path,
# and remove it on exit. terraform init copies what it needs into .terraform/.
BACKEND_FILE="$(mktemp "${TMPDIR:-/tmp}/gcp-dev-backend.XXXXXX")"
trap 'rm -f "$BACKEND_FILE"' EXIT
printf '%s\nprefix = "github-dev"\n' "$TF_BACKEND" > "$BACKEND_FILE"
echo "Backend file written to $BACKEND_FILE"

cd "$TF_DIR"
Expand Down
Loading
Loading