-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add initial buildspec.yml for tf-run-executor configuration
- Loading branch information
Dave Arnold
committed
May 11, 2026
1 parent
87c0926
commit 97921ff
Showing
1 changed file
with
225 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| version: 0.2 | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # tf-run-executor buildspec | ||
| # | ||
| # Required env-var overrides per build (supplied by Lambda or manual CLI): | ||
| # ACCOUNT_REPO - account repo name, e.g. 229685449397-csvd-dev-platform-dev-gov | ||
| # LAYER - terraform layer: common | infrastructure | vpc | ||
| # REGION_DIR - region directory: east | west | ||
| # GITHUB_TOKEN - GHE PAT (type PLAINTEXT, value from Secrets Manager) | ||
| # | ||
| # Optional env-var overrides: | ||
| # GIT_BRANCH - branch to commit/PR from (default: repo-init) | ||
| # TF_RUN_START_TAG - tf-run.data TAG label to start from (default: empty = from top) | ||
| # TEMPLATE_REPO - GHE repo containing Jinja2/.tf template files (default: empty) | ||
| # TEMPLATE_VARS - JSON map of Jinja2 variables for template rendering (default: {}) | ||
| # EXTRA_FILES - JSON map {"relative/path": "content"} written after template rendering | ||
| # DRY_RUN - "true" = tf plan only, no apply (default: "false") | ||
| # TARGET_ACCOUNT_ID - AWS account ID to assume role in before running tf-run | ||
| # (default: empty = run with CodeBuild's own credentials, | ||
| # i.e. csvd-dev. Set this when targeting a different account.) | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| env: | ||
| variables: | ||
| GITHUB_ORG: "SCT-Engineering" | ||
| # S3 prefixes — filenames are resolved at build time from terraform/support VERSION files. | ||
| # The S3 bucket must contain the version pinned in terraform/support (keep in sync). | ||
| TF_BINARY_S3_PREFIX: "s3://csvd-packer-pipeline-assets/terraform" | ||
| GH_CLI_S3_PREFIX: "s3://csvd-packer-pipeline-assets/tools" | ||
| CENSUS_CA_S3: "s3://csvd-packer-pipeline-assets/certs/census-ca.pem" | ||
| # Org-canonical version governance: clone this repo to read VERSION files | ||
| TERRAFORM_SUPPORT_REPO: "terraform/support" | ||
| HTTPS_PROXY: "http://proxy.tco.census.gov:3128" | ||
| NO_PROXY: "github.e.it.census.gov,169.254.169.254,169.254.170.2" | ||
| # Per-build defaults (overridden via environmentVariablesOverride in Lambda) | ||
| GIT_BRANCH: "repo-init" | ||
| DRY_RUN: "false" | ||
| TF_RUN_START_TAG: "" | ||
| TEMPLATE_REPO: "" | ||
| TEMPLATE_VARS: "{}" | ||
| EXTRA_FILES: "{}" | ||
| TARGET_ACCOUNT_ID: "" | ||
|
|
||
| phases: | ||
| install: | ||
| commands: | ||
| # --- Version governance: clone terraform/support to read org-canonical versions --- | ||
| # This repo (github.e.it.census.gov/terraform/support) is the single source of truth | ||
| # for which Terraform and gh CLI versions the org has blessed. We read VERSION files | ||
| # from it rather than hardcoding versions here. | ||
| - git clone --depth 1 "https://${GITHUB_TOKEN}@github.e.it.census.gov/${TERRAFORM_SUPPORT_REPO}.git" /tmp/tf-support | ||
| - export TF_VERSION=$(cat /tmp/tf-support/terraform/VERSION) | ||
| - export GH_VERSION=$(cat /tmp/tf-support/github-cli-releases/VERSION) | ||
| - echo "Using Terraform ${TF_VERSION}, gh CLI ${GH_VERSION}" | ||
|
|
||
| # --- Terraform binary (registry.terraform.io is blocked on Census network; use S3) --- | ||
| # S3 bucket must contain the version pinned in terraform/support/terraform/VERSION. | ||
| - aws s3 cp "${TF_BINARY_S3_PREFIX}/terraform_${TF_VERSION}_linux_amd64.zip" /tmp/terraform.zip | ||
| - unzip -o /tmp/terraform.zip -d /usr/local/bin/ && chmod +x /usr/local/bin/terraform | ||
| - ln -sf /usr/local/bin/terraform /usr/local/bin/tf | ||
|
|
||
| # --- Census CA certificate (required for TLS to github.e.it.census.gov) --- | ||
| - aws s3 cp "$CENSUS_CA_S3" /etc/pki/ca-trust/source/anchors/census-ca.pem | ||
| - update-ca-trust extract | ||
|
|
||
| # --- tf-run toolchain (sourced from terraform/support, already cloned above) --- | ||
| # Canonical versions live in terraform/support local-app/ — no copies kept in this repo. | ||
| - cp /tmp/tf-support/local-app/tf-run/tf-run.sh /usr/local/bin/tf-run | ||
| - cp /tmp/tf-support/local-app/tf-control/tf-control.sh /usr/local/bin/tf-control.sh | ||
| - cp /tmp/tf-support/local-app/tf-directory-setup/tf-directory-setup.py /usr/local/bin/tf-directory-setup.py | ||
| - chmod +x /usr/local/bin/tf-run /usr/local/bin/tf-control.sh /usr/local/bin/tf-directory-setup.py | ||
| # Create tf-{action} symlinks expected by tf-run and account repo steps | ||
| - > | ||
| for action in init plan apply destroy refresh output validate import state fmt taint console; do | ||
| ln -sf /usr/local/bin/tf-control.sh /usr/local/bin/tf-${action}; | ||
| done | ||
| # --- Python deps for tf-directory-setup.py and template rendering --- | ||
| - pip3 install --quiet jinja2 python-dateutil pyyaml | ||
|
|
||
| # --- gh CLI (S3 bucket must contain the version pinned in terraform/support) --- | ||
| - aws s3 cp "${GH_CLI_S3_PREFIX}/gh_${GH_VERSION}_linux_amd64.tar.gz" /tmp/gh.tar.gz | ||
| - mkdir -p /tmp/gh-cli | ||
| - tar -xzf /tmp/gh.tar.gz -C /tmp/gh-cli --strip-components=1 | ||
| - cp /tmp/gh-cli/bin/gh /usr/local/bin/gh && chmod +x /usr/local/bin/gh | ||
|
|
||
| build: | ||
| commands: | ||
| # --- Configure git to rewrite SSH URLs to HTTPS --- | ||
| # Account repos reference Terraform modules via ssh://git@github.e.it.census.gov/... | ||
| # This rewrite makes those module fetches work transparently via HTTPS + PAT, | ||
| # avoiding the need for a per-repo deploy key. | ||
| - git config --global url."https://${GITHUB_TOKEN}@github.e.it.census.gov/".insteadOf "ssh://git@github.e.it.census.gov/" | ||
| - git config --global url."https://${GITHUB_TOKEN}@github.e.it.census.gov/".insteadOf "git@github.e.it.census.gov:" | ||
|
|
||
| # --- Clone account repo --- | ||
| - git clone "https://${GITHUB_TOKEN}@github.e.it.census.gov/${GITHUB_ORG}/${ACCOUNT_REPO}.git" repo | ||
| - cd repo | ||
| - git checkout -B "${GIT_BRANCH}" | ||
|
|
||
| # --- Render template repo (if specified) into account repo --- | ||
| # Clone TEMPLATE_REPO, render .j2 files with TEMPLATE_VARS via Jinja2, | ||
| # copy non-template files as-is. Results land in the account repo tree | ||
| # at the same relative paths. EXTRA_FILES applied afterwards can override. | ||
| - | | ||
| if [ -n "${TEMPLATE_REPO}" ]; then | ||
| git clone "https://${GITHUB_TOKEN}@github.e.it.census.gov/${GITHUB_ORG}/${TEMPLATE_REPO}.git" /tmp/template-repo | ||
| python3 - <<'PYEOF' | ||
| import json, os, pathlib, shutil | ||
| from jinja2 import Environment, FileSystemLoader, StrictUndefined | ||
| template_vars = json.loads(os.environ.get('TEMPLATE_VARS', '{}')) | ||
| src_root = pathlib.Path('/tmp/template-repo') | ||
| dst_root = pathlib.Path('.') # already inside cloned account repo | ||
| rendered = 0 | ||
| copied = 0 | ||
| for src in src_root.rglob('*'): | ||
| if src.is_dir() or any(part.startswith('.git') for part in src.parts): | ||
| continue | ||
| rel = src.relative_to(src_root) | ||
| if src.suffix == '.j2': | ||
| # Render Jinja2 template; strip .j2 extension in destination | ||
| dst = dst_root / rel.with_suffix('') | ||
| dst.parent.mkdir(parents=True, exist_ok=True) | ||
| env = Environment( | ||
| loader=FileSystemLoader(str(src.parent)), | ||
| undefined=StrictUndefined, | ||
| keep_trailing_newline=True, | ||
| ) | ||
| content = env.get_template(src.name).render(**template_vars) | ||
| dst.write_text(content) | ||
| rendered += 1 | ||
| else: | ||
| dst = dst_root / rel | ||
| dst.parent.mkdir(parents=True, exist_ok=True) | ||
| shutil.copy2(src, dst) | ||
| copied += 1 | ||
| print(f'Template repo: rendered {rendered} .j2 file(s), copied {copied} file(s)') | ||
| PYEOF | ||
| else | ||
| echo 'No TEMPLATE_REPO specified — skipping template rendering' | ||
| fi | ||
| # --- Write extra config files passed in from Lambda (JSON map path -> content) --- | ||
| # Applied after template rendering; keys here override template output. | ||
| - | | ||
| python3 -c " | ||
| import json, os, pathlib | ||
| files = json.loads(os.environ.get('EXTRA_FILES', '{}')) | ||
| for path, content in files.items(): | ||
| p = pathlib.Path(path) | ||
| p.parent.mkdir(parents=True, exist_ok=True) | ||
| p.write_text(content) | ||
| print(f'Wrote {len(files)} extra file(s)') | ||
| " | ||
| # --- Commit and push (--allow-empty handles no-change case) --- | ||
| - git add -A | ||
| - | | ||
| git -c user.email="sc-automation@census.gov" \ | ||
| -c user.name="SC Automation" \ | ||
| commit -m "SC automation: ${LAYER}/${REGION_DIR} [${ACCOUNT_REPO}]" \ | ||
| --allow-empty | ||
| - git push origin "${GIT_BRANCH}" | ||
|
|
||
| # --- Assume cross-account role (if TARGET_ACCOUNT_ID is set) --- | ||
| # CodeBuild runs in csvd-dev by default. To run tf-run apply against resources | ||
| # in a different AWS account, set TARGET_ACCOUNT_ID. The role | ||
| # sc-automation-codebuild-role must exist in that account and trust the | ||
| # CodeBuild IAM role from csvd-dev. | ||
| - | | ||
| if [ -n "${TARGET_ACCOUNT_ID}" ]; then | ||
| PARTITION=$(aws sts get-caller-identity --query Arn --output text | cut -d: -f2) | ||
| ROLE_ARN="arn:${PARTITION}:iam::${TARGET_ACCOUNT_ID}:role/sc-automation-codebuild-role" | ||
| echo "Assuming cross-account role: ${ROLE_ARN}" | ||
| CREDS=$(aws sts assume-role \ | ||
| --role-arn "${ROLE_ARN}" \ | ||
| --role-session-name "sc-automation-${ACCOUNT_REPO}" \ | ||
| --query Credentials \ | ||
| --output json) | ||
| export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | python3 -c "import json,sys; print(json.load(sys.stdin)['AccessKeyId'])") | ||
| export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | python3 -c "import json,sys; print(json.load(sys.stdin)['SecretAccessKey'])") | ||
| export AWS_SESSION_TOKEN=$(echo "$CREDS" | python3 -c "import json,sys; print(json.load(sys.stdin)['SessionToken'])") | ||
| echo "Successfully assumed role in account ${TARGET_ACCOUNT_ID}" | ||
| else | ||
| echo "No TARGET_ACCOUNT_ID set — running with CodeBuild role (csvd-dev)" | ||
| fi | ||
| # --- Run Terraform in target layer/region directory --- | ||
| # tf-run auto-proceeds on non-TTY stdin (read -t timeout defaults to "y") | ||
| - cd "${LAYER}/${REGION_DIR}" | ||
| - | | ||
| if [ "${DRY_RUN}" = "true" ]; then | ||
| tf-plan -no-color | ||
| elif [ -n "${TF_RUN_START_TAG}" ]; then | ||
| TFARGS="-auto-approve" tf-run apply "tag:${TF_RUN_START_TAG}" | ||
| else | ||
| TFARGS="-auto-approve" tf-run apply | ||
| fi | ||
| # --- Open PR (idempotent: skip if PR already exists) --- | ||
| - | | ||
| GH_HOST=github.e.it.census.gov \ | ||
| GH_TOKEN="${GITHUB_TOKEN}" \ | ||
| gh pr create \ | ||
| --title "SC automation: ${LAYER}/${REGION_DIR} [${ACCOUNT_REPO}]" \ | ||
| --body "Triggered by Service Catalog provisioning of **${ACCOUNT_REPO}**." \ | ||
| --base main \ | ||
| --head "${GIT_BRANCH}" \ | ||
| || echo "PR already exists or create failed, continuing" | ||
| post_build: | ||
| commands: | ||
| - echo "BUILD_RESULT=${CODEBUILD_BUILD_SUCCEEDING}" | ||
| # Emit PR_URL so Lambda can parse it from the build output | ||
| - | | ||
| PR_URL=$(GH_HOST=github.e.it.census.gov \ | ||
| GH_TOKEN="${GITHUB_TOKEN}" \ | ||
| gh pr view \ | ||
| --repo "${GITHUB_ORG}/${ACCOUNT_REPO}" \ | ||
| "${GIT_BRANCH}" \ | ||
| --json url -q .url 2>/dev/null || echo "") | ||
| echo "PR_URL=${PR_URL}" |