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
91 changes: 75 additions & 16 deletions .github/actions/terraform/action.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,92 @@
name: Terraform (workspace-driven)
name: Terraform
description: >
Run terraform for a root. The workspace and its variables come from the root's
workspace/<name>.tfvars: the filename is the terraform workspace, the contents
are its variables. Keeps workspace selection declarative and version-controlled
(not a local, gitignored .terraform/environment).
Run Terraform for a workspace-driven root: set up Terraform, mint an Infisical
OIDC token, assume an AWS role via OIDC (for remote state access) and retrieve Scaleway Machine Identity credentials, then init → workspace select →
plan/apply/destroy. The repository must already be checked out.

This action takes NO secret inputs. Provider credentials are read from the
calling job's environment (set them there — the reusable terraform.yml workflow
does, sourcing SCW_ACCESS_KEY/SCW_SECRET_KEY from its `secrets:` block):
- SCW_ACCESS_KEY / SCW_SECRET_KEY (Scaleway API key)
- SCW_DEFAULT_ORGANIZATION_ID / _PROJECT_ID
- INFISICAL_MACHINE_IDENTITY_ID (for the minted INFISICAL_AUTH_JWT)

inputs:
root:
description: Path to the terraform root (passed to -chdir).
description: Path to the Terraform root (passed to -chdir).
required: true
tfvars-file:
description: >
Filename of the .tfvars under env/ (e.g. 02-cluster-staging.tfvars).
Workspace name is derived by stripping the .tfvars extension.
required: true
apply:
description: 'When "true", apply; otherwise plan.'
command:
description: 'Terraform command to run: plan | apply | destroy.'
required: false
default: plan
aws-role-arn:
description: ARN of the IAM role to assume via OIDC (Terraform-state R/W).
required: true
aws-region:
description: AWS region for the assumed role.
required: false
default: eu-west-3
infisical-oidc-audience:
description: >
OIDC audience for the Infisical machine identity. When set (the default),
mint a GitHub OIDC JWT into INFISICAL_AUTH_JWT. Set to "" to skip it for
roots that don't read Infisical.
required: false
default: "false"
default: https://github.com/IntegratedDynamic

runs:
using: composite
steps:
- shell: bash
- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
# Keep in sync with mise.toml [tools] terraform version.
terraform_version: "1.14"

# Keyless Infisical auth: mint a GitHub OIDC JWT into INFISICAL_AUTH_JWT (the
# provider's default token env var). The audience must match the identity's
# boundAudiences. getIDToken masks the value automatically.
- name: Mint Infisical OIDC token
if: inputs.infisical-oidc-audience != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
AUDIENCE: ${{ inputs.infisical-oidc-audience }}
with:
script: |
const jwt = await core.getIDToken(process.env.AUDIENCE)
core.exportVariable('INFISICAL_AUTH_JWT', jwt)

- name: Assume AWS role via OIDC
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
with:
role-to-assume: ${{ inputs.aws-role-arn }}
aws-region: ${{ inputs.aws-region }}

- name: Terraform ${{ inputs.command }}
shell: bash
env:
ROOT: ${{ inputs.root }}
APPLY: ${{ inputs.apply }}
TFVARS: ${{ inputs.tfvars-file }}
COMMAND: ${{ inputs.command }}
# SCW_* and INFISICAL_MACHINE_IDENTITY_ID are inherited from the job env.
run: |
set -euo pipefail
ws="$(basename "$ROOT"/workspace/*.tfvars .tfvars)"
Comment thread
nbrieussel marked this conversation as resolved.
ws="${TFVARS%.tfvars}"
terraform -chdir="$ROOT" init -input=false
terraform -chdir="$ROOT" workspace select -or-create "$ws"
if [ "$APPLY" = "true" ]; then
terraform -chdir="$ROOT" apply -auto-approve -input=false -var-file="workspace/$ws.tfvars"
# apply may target a brand-new env whose workspace doesn't exist yet;
# read paths (plan/destroy) select an existing one.
if [ "$COMMAND" = "apply" ]; then
terraform -chdir="$ROOT" workspace select -or-create "$ws"
else
terraform -chdir="$ROOT" plan -input=false -var-file="workspace/$ws.tfvars"
terraform -chdir="$ROOT" workspace select "$ws"
fi
case "$COMMAND" in
plan) terraform -chdir="$ROOT" plan -input=false -var-file="env/${TFVARS}" ;;
apply) terraform -chdir="$ROOT" apply -input=false -auto-approve -var-file="env/${TFVARS}" ;;
destroy) terraform -chdir="$ROOT" destroy -input=false -auto-approve -var-file="env/${TFVARS}" ;;
*) echo "::error::unknown command '$COMMAND' (expected plan|apply|destroy)"; exit 1 ;;
esac
60 changes: 21 additions & 39 deletions .github/workflows/iam_terraform-backend-role.yml
Original file line number Diff line number Diff line change
@@ -1,75 +1,57 @@
name: Apply Terraform Backend Role

# Terraform GitOps for the 00-remote_state/1-iam/ root: the org-wide S3 state
# access role is created/synchronised from remote. Plans on PR, applies on push
# to main. Credentials come from the identity/00-ci-trust CI role assumed via
# OIDC — no static AWS keys.
# Terraform GitOps for the 00-remote_state/1-iam/ root: the org-wide state-access
# role is created/synchronised from remote via the .github/actions/terraform
# composite action. Plans on PR, applies on push to main. Credentials come from
# the identity/00-ci-trust CI role assumed via OIDC — no static AWS keys.

on:
push:
branches: [main]
paths:
- '00-remote_state/1-iam/**'
- '.github/actions/terraform/**'
- '.github/workflows/iam_terraform-backend-role.yml'
pull_request:
paths:
- '00-remote_state/1-iam/**'
- '.github/actions/terraform/**'
- '.github/workflows/iam_terraform-backend-role.yml'

concurrency:
group: tf-backend-role-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
terraform:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
id-token: write # mint the OIDC token exchanged for temporary AWS creds
contents: read
steps:
# Fail fast and clearly if creds aren't set, instead of letting the
# tooling emit a confusing assume-role error.
- name: Assert credentials are present
- name: Assert state role ARN is present
env:
ROLE_ARN: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}
run: |
if [ -z "$ROLE_ARN" ]; then
echo "::error::vars.AWS_GITHUB_ACTIONS_ROLE_ARN is not set. Set it to the github-actions-terraform role ARN (provisioned by identity/00-ci-trust/)."
echo "::error::vars.AWS_GITHUB_ACTIONS_ROLE_ARN is not set (provisioned by identity/00-ci-trust/)."
exit 1
fi

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 1

- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
# Keep in sync with mise.toml [tools] terraform version.
terraform_version: "1.14"

- name: Assume the CI role via OIDC
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
- uses: ./.github/actions/terraform
with:
role-to-assume: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}
aws-region: eu-west-3

- name: Terraform plan
if: github.event_name == 'pull_request'
env:
TF_CHDIR: "00-remote_state/1-iam"
TF_ENVIRONMENT: "00-remote-state-iam.tfvars"
run: |
set -euo pipefail
terraform -chdir="${TF_CHDIR}" init -input=false
terraform -chdir="${TF_CHDIR}" workspace select "${TF_ENVIRONMENT%.*}"
terraform -chdir="${TF_CHDIR}" plan -input=false -var-file="env/${TF_ENVIRONMENT}"

- name: Terraform apply
if: github.event_name == 'push'
env:
TF_CHDIR: "00-remote_state/1-iam"
TF_ENVIRONMENT: "00-remote-state-iam.tfvars"
run: |
set -euo pipefail
terraform -chdir="${TF_CHDIR}" init -input=false
terraform -chdir="${TF_CHDIR}" workspace select "${TF_ENVIRONMENT%.*}"
terraform -chdir="${TF_CHDIR}" apply -auto-approve -input=false -var-file="env/${TF_ENVIRONMENT}"
root: 00-remote_state/1-iam
tfvars-file: 00-remote-state-iam.tfvars
aws-role-arn: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}
command: ${{ github.event_name == 'push' && 'apply' || 'plan' }}
# This root touches neither Infisical nor Scaleway — skip the OIDC mint.
infisical-oidc-audience: ""
108 changes: 0 additions & 108 deletions .github/workflows/scaleway-plan.yml

This file was deleted.

Loading
Loading