State bucket and access resources for managing OpenTofu infrastructure-as-code via GitHub Actions.
Use this module as any other OpenTofu/TF module. As it tracks resources for its own backend and access configuration, initial provisioning requires manual apply via personal authentication and local state (see Initalial provisioning). After that, recommended usage is via Terragrunt (see Terragrunt usage).
Recommended usage of this module is via Terragrunt. Basic Terragrunt usage of the module:
terraform {
source = "github.com/rcwbr/gha-gcp-opentofu?ref=0.1.0"
}
inputs = {
gcp_project = "my-project"
gcp_region = "us-west-1"
github_repo = "my-repo"
state_bucket_name = "my-repo-state-bucket"
}
As an example, this project uses .infra/terragrunt.hcl
to configure the bucket as the state backend, as it could be used for resources for any provider, and .infra/gcp-gha-gcp-opentofu/terragrunt.hcl
to configure the GCP provider and the module itself.
The .infra/terragrunt.hcl
file configures the state backend:
// .infra/terragrunt.hcl
locals {
gcp_project = "gha-gcp-opentofu"
gcp_region = "US-WEST1"
github_repo = "rcwbr/gha-gcp-opentofu"
// Replace any slashes in github_repo with dashes
state_bucket_name = replace("${local.github_repo}-opentofu-state", "/", "-")
}
remote_state {
backend = "gcs"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = local.state_bucket_name
prefix = "${path_relative_to_include()}"
project = local.gcp_project
location = local.gcp_region
access_token = get_env("GOOGLE_OAUTH_ACCESS_TOKEN", "")
}
}
inputs = {
github_repo = local.github_repo
state_bucket_name = local.state_bucket_name
}
The .infra/gcp-gha-gcp-opentofu/terragrunt.hcl
file defines the GCP provider and wraps the module:
// .infra/gcp-gha-gcp-opentofu/terragrunt.hcl
include {
path = find_in_parent_folders()
}
locals {
gcp_project = read_terragrunt_config(find_in_parent_folders("terragrunt.hcl")).locals.gcp_project
gcp_region = lower(read_terragrunt_config(find_in_parent_folders("terragrunt.hcl")).locals.gcp_region)
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "google" {
project = "${local.gcp_project}"
region = "${local.gcp_region}"
// Token comes from GOOGLE_OAUTH_ACCESS_TOKEN env var
}
EOF
}
terraform {
source = "../../" // Path to the repository root
}
inputs = {
gcp_project = local.gcp_project
gcp_region = local.gcp_region
}
To automate management of the module resources via GitHub Actions, include Terragrunt actions (assuming module usage via Terragrunt as recommended) in your workflow. For example, to include a terragrunt plan
using the module:
jobs:
plan:
runs-on: ubuntu-24.04
steps:
- name: Terragrunt plan
uses: gruntwork-io/[email protected]
with:
tofu_version: 1.8.2
tg_version: 0.67.10
tg_dir: .infra
tg_command: plan
Terragrunt configuration as per the recommended usage expects the GOOGLE_OAUTH_ACCESS_TOKEN
environment variable in order to authenticate to GCP. To use the resources defined in the module to authenticate the workflow, first define a step to authenticate to GCP using the auth action. workload_identity_provider
for the auth action must be set to the github_actions_wif_provider_id
output, and service_account
to github_actions_plan_sa_email
(outputs can be read from the module Initial provisioning). For example:
jobs:
plan:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/[email protected]
uses: google-github-actions/[email protected]
with:
export_environment_variables: false
create_credentials_file: false
token_format: access_token
workload_identity_provider: projects/918666231212/locations/global/workloadIdentityPools/github-actions/providers/github-actions
service_account: [email protected]
- name: Terragrunt plan
...
env:
GOOGLE_OAUTH_ACCESS_TOKEN: ${{ steps.auth.outputs.access_token }}
The module reads the following variables as input:
Variable | Required | Default | Effect |
---|---|---|---|
gcp_project |
✓ | N/A | The GCP project name |
gcp_region |
✓ | N/A | The GCP region for all resources managed within the project |
github_repo |
✓ | N/A | The fully-qualified name of the GitHub repo to which the state access will be granted |
apply_action_project_roles |
✗ | [ "roles/iam.serviceAccountAdmin", "roles/iam.roleAdmin", "roles/iam.workloadIdentityPoolAdmin", "roles/resourcemanager.projectIamAdmin", "roles/serviceusage.serviceUsageAdmin", "roles/storage.admin" ] |
The list of project-wide roles to grant apply actions |
github_default_branch_name |
✗ | "main" |
The default/mainline branch name for the GitHub repo, workflows for which have OpenTofu apply (vs. plan) access |
state_bucket_name |
✗ | "${var.gcp_project}-opentofu-state" |
The name of the bucket used for OpenTofu state |
Initial provisioning of resources to enable infrastructue-as-code automation requires the following steps:
-
Prepare a GCS project
-
export
the project name toGCP_PROJECT_NAME
-
export
the path to the directory that contains your Terragrunt configuration for this module asGHA_GCP_TERRAGRUNT_DIR
-
Temporarily grant your personal account the Storage Admin for access to the state bucket after
apply
:docker run --rm -it -e GCP_PROJECT_NAME --entrypoint bash gcr.io/google.com/cloudsdktool/google-cloud-cli -c 'gcloud auth login && gcloud projects add-iam-policy-binding "$GCP_PROJECT_NAME" --member="user:[email protected]" --role="roles/storage.admin"'
- Follow the instructions provided by the prompts to authenticate the action
-
Retrieve a GCP access token:
docker run --rm -it --entrypoint bash -v gcp_application_default_token:/token_vol gcr.io/google.com/cloudsdktool/google-cloud-cli -c 'gcloud auth application-default login && gcloud auth application-default print-access-token > /token_vol/gcp_application_default_token'
- Similarly, follow the prompts to authenticate the environment
-
Plan and apply the provisioning resources from the infrastructure-as-code config:
docker run -it --rm -v gcp_application_default_token:/token_vol -v $(pwd):/gha-gcp-opentofu -w "/gha-gcp-opentofu/$GHA_GCP_TERRAGRUNT_DIR" --entrypoint bash devopsinfra/docker-terragrunt:ot-1.8.2-tg-0.67.10 -c 'export GOOGLE_OAUTH_ACCESS_TOKEN=$(cat /token_vol/gcp_application_default_token) && terragrunt plan -target="google_iam_workload_identity_pool.github_actions" -target="google_project_service.iam" -target="google_project_service.iam_creds" -target="google_project_service.crm" -target="google_iam_workload_identity_pool_provider.github_actions" -target="google_service_account.github_actions_plan" -target="google_service_account_iam_policy.github_actions_plan" -target="google_service_account.github_actions_apply" -target="google_service_account_iam_policy.github_actions_apply" -target="google_project_iam_member.github_actions_apply_sa_admin" -target="google_storage_bucket_iam_policy.state_bucket_policy" -target="google_project_iam_custom_role.plan_project_role" -target="google_project_iam_member.github_actions_plan_sa_custom" -target="google_project_iam_member.github_actions_plan_sa_viewer" && terragrunt apply -target="google_project_service.iam" -target="google_project_service.iam_creds" -target="google_project_service.crm" -target="google_iam_workload_identity_pool.github_actions" -target="google_iam_workload_identity_pool_provider.github_actions" -target="google_service_account.github_actions_plan" -target="google_service_account_iam_policy.github_actions_plan" -target="google_service_account.github_actions_apply" -target="google_service_account_iam_policy.github_actions_apply" -target="google_project_iam_member.github_actions_apply_sa_admin" -target="google_storage_bucket_iam_policy.state_bucket_policy" -target="google_project_iam_custom_role.plan_project_role" -target="google_project_iam_member.github_actions_plan_sa_custom" -target="google_project_iam_member.github_actions_plan_sa_viewer"'
- This will prompt with
Remote state GCS bucket opentofu-state does not exist or you don't have permissions to access it. Would you like Terragrunt to create it? (y/n)
. Entery
- It will then prompt with
Do you want to perform these actions? OpenTofu will perform the actions described above. Only 'yes' will be accepted to approve.
. Enteryes
- Note the value of the
github_actions_wif_provider_id
,github_actions_apply_sa_email
, andgithub_actions_plan_sa_email
outputs provided by logs from this command in theOutputs:
block (see GitHub Actions usage) - Clean up the volume storing the GCP auth token:
docker volume rm gcp_application_default_token
- This will prompt with
-
Clean up the temporary personal account Storage Admin role binding:
docker run --rm -it -e GCP_PROJECT_NAME --entrypoint bash gcr.io/google.com/cloudsdktool/google-cloud-cli -c 'gcloud auth login && gcloud projects remove-iam-policy-binding "$GCP_PROJECT_NAME" --member="user:[email protected]" --role="roles/storage.admin"'
- Follow the instructions provided by the prompts to authenticate the action
-
Trigger a
main
branch workflow to apply the remaining resources via GitHub Actions
The GitHub repo settings for this repo are defined as code using the Probot settings GitHub App. Settings values are defined in the .github/settings.yml
file. Enabling automation of settings via this file requires installing the app.
The settings applied are as recommended in the release-it-gh-workflow usage, including tag and branch protections, GitHub App and environment authentication, and required checks.
This repo uses the release-it-gh-workflow, with the conventional-changelog image defined at any given ref, as its automation.
Forks of the repository will not have access to the state bucket via CI/CD, thanks to the google_iam_workload_identity_pool_provider attribute_condition (defined in main.tf
). Forks should test a plan
and apply
(via Actions) against a new project, then open a PR for which CI/CD will fail. Repo contributors may then reconfigure the PR to target an unprotected branch to bring the changes into the repo, from which the final PR to main
may be opened.