2025/4/12 update:
...
stage('Configure GCP Authentication') {
steps {
withCredentials([file(credentialsId: 'gcp-sa-dev', variable: 'GOOGLE_APPLICATION_CREDENTIALS')]) { // Ensure 'gcp-sa-dev' is your Credential ID in Jenkins
sh 'gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS'
sh 'gcloud config set project $GCP_PROJECT_ID'
echo "GCP Authentication Configured as: ${SERVICE_ACCOUNT_EMAIL}"
sh 'terraform init -backend-config="bucket=${TF_STATE_BUCKET}" -migrate-state'
sh 'terraform validate'
sh 'terraform plan -out=tfplan'
archiveArtifacts artifacts: 'tfplan'
input message: 'Approve Terraform Apply to Production?', ok: 'Proceed with Apply'
sh 'terraform apply tfplan'
}
}
}
...
2025/4/11 update:
+ gcloud config set account jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com
Updated property [core/account].
+ terraform init -backend-config=bucket=terraform-state-bucket-project-data-sharing -migrate-state
[0m[1mInitializing the backend...[0m
[31m╷[0m[0m
[31m│[0m [0m[1m[31mError: [0m[0m[1mError loading state: writing "gs://terraform-state-bucket-project-data-sharing/terraform/state/default.tflock" failed: googleapi: Error 403: Access denied., forbidden
[31m│[0m [0mstorage: object doesn't exist[0m
[31m│[0m [0m
[31m│[0m [0m[0m
[31m╵[0m[0m
Your Cloud Platform project in this session is set to open-data-v2-cicd.
Use `gcloud config set project [PROJECT_ID]` to change to a different project.
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ gcloud auth activate-service-account jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com \
--key-file=sa-key-3.json
Activated service account credentials for: [jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com]
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ gcloud auth list
Credentialed Accounts
ACTIVE: *
ACCOUNT: jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com
ACTIVE:
ACCOUNT: xxx@gmail.com
To set the active account, run:
$ gcloud config set account `ACCOUNT`
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ gsutil ls gs://terraform-state-bucket-project-data-sharing/
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ echo "This is a test file from Jenkins SA." > dummy.txt
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ gcloud storage cp dummy.txt gs://terraform-state-bucket-project-data-sharing/terraform/state/test-upload.txt
Copying file://dummy.txt to gs://terraform-state-bucket-project-data-sharing/terraform/state/test-upload.txt
Completed files 1/1 | 37.0B/37.0B
Original post
I am trying to set up a datapipline which:
What I have done:
Issue I encountered:
403 forbidden
for the service account I used.+ terraform init -backend-config=bucket=terraform-state-bucket--data-sharing
[0m[1mInitializing the backend...[0m
[31m╷[0m[0m
[31m│[0m [0m[1m[31mError: [0m[0m[1mError loading state: writing "gs://terraform-state-bucket--data-sharing/terraform/state/default.tflock" failed: googleapi: Error 403: Access denied., forbidden
[31m│[0m [0mstorage: object doesn't exist[0m
[31m│[0m [0m
[31m│[0m [0m[0m
[31m╵[0m[0m
+ gcloud auth list
Credentialed Accounts
ACTIVE ACCOUNT
443305306906-compute@developer.gserviceaccount.com
* data-sharing-jenkins-tf-dev@open-data-v2-cicd.iam.gserviceaccount.com
jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com
To set the active account, run:
$ gcloud config set account `ACCOUNT`
[Pipeline] sh
+ gcloud config list
[core]
account = data-sharing-jenkins-tf-dev@open-data-v2-cicd.iam.gserviceaccount.com
disable_usage_reporting = True
project = open-data-v2-cicd
Your active configuration is: [default]
main.tf
file that created the bucket terraform {
backend "gcs" {
bucket = "terraform-state-bucket--data-sharing"
prefix = "terraform/state"
region = "asia-east1"
}
}
resource "google_storage_bucket" "my-bucket" {
name = "test-githubdemo-bucket-001"
project = "open-data-v2-cicd"
location = "asia-east1"
force_destroy = true
public_access_prevention = "enforced"
}
pipeline {
agent any
tools {
terraform 'Terraform-v1.11.3'
}
environment {
GCP_PROJECT_ID = 'open-data-v2-cicd'
TF_STATE_BUCKET = 'terraform-state-bucket--data-sharing'
GCP_REGION = 'asia-east1'
SERVICE_ACCOUNT_EMAIL = 'jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com'
}
stages {
stage('Checkout Code') {
steps {
checkout scm
}
}
stage('Configure GCP Authentication') {
steps {
withCredentials([file(credentialsId: 'gcp-sa-dev', variable: 'GOOGLE_APPLICATION_CREDENTIALS')]) {
sh 'gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS'
sh 'gcloud config set project $GCP_PROJECT_ID'
sh 'gcloud auth list'
sh 'gcloud config list'
}
}
}
stage('Terraform Init') {
steps {
sh 'terraform init -backend-config="bucket=${TF_STATE_BUCKET}"'
}
}
stage('Terraform Validate') {
steps {
sh 'terraform validate'
}
}
stage('Terraform Plan') {
steps {
sh 'terraform plan -out=tfplan'
// Optionally, archive the plan file as an artifact for review
archiveArtifacts artifacts: 'tfplan'
}
}
stage('Terraform Apply') {
steps {
// For automated apply (e.g., for dev/staging)
// sh 'terraform apply tfplan'
// For main/prod pipeline, consider manual approval step before apply
input message: 'Approve Terraform Apply to Production?', ok: 'Proceed with Apply'
// Check if Jenkins is using correct SA
sh 'gcloud auth list'
sh 'gcloud config list'
sh 'terraform apply tfplan'
}
}
}
post {
failure {
script {
echo "Terraform Pipeline Failed!"
// Add notifications (e.g., email, Slack) here if needed
}
}
success {
script {
echo "Terraform Pipeline Succeeded!"
// Add notifications here if needed
}
}
}
}
The issue solved after adding following in the environment, the credential can be used in all stages automatically
GOOGLE_APPLICATION_CREDENTIALS = credentials('gcp-sa-dev')
pipeline {
agent any
tools {
terraform 'Terraform-v1.11.3'
}
environment {
GCP_PROJECT_ID = 'open-data-v2-cicd'
TF_STATE_BUCKET = 'terraform-state-bucket-project-data-sharing'
GCP_REGION = 'asia-east1'
SERVICE_ACCOUNT_EMAIL = 'jenkins-tf-dev@open-data-v2-cicd.iam.gserviceaccount.com'
GOOGLE_APPLICATION_CREDENTIALS = credentials('gcp-sa-dev') //<-------
}
...