You can use this project bootstrapping script to start managing your Google Cloud project via Terraform. Bootstrapping a project leverages the Google principal you are locally authenticated as to provision the minimum amount of resources required for Terraform to take over the project's management and generate Terraform code which you can use as the basis for all further project management.
Bootstrapping a project is idempotent. You can run it multiple times without worrying about bricking the project.
Warning
The bootstrapping functionality is designed to be executed on freshly created
Google Cloud projects. However, you can also run it on projects that were
previously managed by hand or a different Terraform setup. In this case, we
recommend you to run the bootstrap script with the -i
parameter which
ensures that the generated code is not automatically applied and can be
reviewed.
Make sure that you have installed the following dependencies on your machine.
-
Ensure
gcloud
is configured and authenticated:As the bootstrap script uses your locally authenticated Google principal, ensure that you are authenticated as a user that is inside the manager group of the project that you want to bootstrap. Your application-default credentials must also be valid.
gcloud auth login --update-adc
-
Clone the repository:
Make sure to replace
<LATEST RELEASE TAG>
with the latest release tag which can be found here.git clone https://github.com/metro-digital/terraform-google-cf-projectcfg.git terraform-projectcfg \ --depth 1 --branch <LATEST RELEASE TAG> cd terraform-projectcfg/bootstrap
-
Bootstrap your project:
As absolute minimum only one parameter is required with the ID of the Google Cloud project that should be bootstrapped. Make sure to replace
<GOOGLE CLOUD PROJECT ID>
with your project ID../bootstrap.sh -p <GOOGLE CLOUD PROJECT ID>
Your generated and applied Terraform code can now be found in the
iac-output
directory. This is also a good time to set up your own Git repository and copy the outputs of theiac-output
directory to it. It doesn't contain any sensitive values.
There are other, optional parameters that can alter the behaviour of the
bootstrap script. Run the script with the -h
flag for more information:
./bootstrap.sh -h
The bootstrap.sh
script is using Terraform to
execute set of actions on a given
GCP project
in order to prepare it for further usage with Terraform and
Service Account impersonation
in a standardised way, as expected and recommended by Cloud Foundation.
The whole bootstrapping process is happening in two stages:
-
Terraform code from the
terraform
directory is run using a user who is a member of the manager group of the given Google Cloud project. The code does the following (in a nutshell):- Grants the manager group additional roles needed to perform the whole process.
- Creates a service account intended for future use with Terraform IaC (also used in second stage).
- Creates a GCP storage bucket for Terraform state file.
- Generates Terraform code in output directory (by default:
iac-output
) to be used in second stage and further by the project users. - Sleeps for a given duration (by default: 5m) to give GCP time to synchronize the IAM changes to the groups, service accounts, etc. This is unfortunately required because Google heavily caches IAM policy changes.
-
Terraform code from the
iac-output
(or a different one, if the-o
parameter was used) is run using the service account created in the first stage and does the following (in a nutshell):- Sets up Terraform backend to use the GCP bucket created in the first stage.
- Migrates the state to the GCP bucket.
- Imports the existing resources
in the project (created in the first stage) into the remote stage to match
the code generated in the
iac-output
directory. - Sets up the project (using the
metro-digital/cf-projectcfg/google
module).
Once the script exited without any errors the project is ready to use and the
iac-output
directory can be used as a base for the Terraform IaC for the given
project. The file imports.tf
is no longer needed and can be deleted from the
repository.
The script can be run subsequently and should cause no issues that way, but given the way the first stage is working, one can observe a minor 'flapping' in the Terraform execution outputs, where first stage grants additional roles to the manager group and the second stage removes them:
(output stripped)
# google_project_iam_member.manager_group["roles/iam.serviceAccountAdmin"] will be created
+ resource "google_project_iam_member" "manager_group" {
+ etag = (known after apply)
+ id = (known after apply)
+ member = "group:[email protected]"
+ project = "cf-customer-example-18"
+ role = "roles/iam.serviceAccountAdmin"
}
# google_project_iam_member.manager_group["roles/serviceusage.serviceUsageAdmin"] will be created
+ resource "google_project_iam_member" "manager_group" {
+ etag = (known after apply)
+ id = (known after apply)
+ member = "group:[email protected]"
+ project = "cf-customer-example-18"
+ role = "roles/serviceusage.serviceUsageAdmin"
}
# google_project_iam_member.manager_group["roles/storage.admin"] will be created
+ resource "google_project_iam_member" "manager_group" {
+ etag = (known after apply)
+ id = (known after apply)
+ member = "group:[email protected]"
+ project = "cf-customer-example-18"
+ role = "roles/storage.admin"
}
(output stripped)
And second stage execution:
(output stripped)
# module.projectcfg.google_project_iam_policy.this will be updated in-place
~ resource "google_project_iam_policy" "this" {
id = "cf-customer-example-18"
~ policy_data = jsonencode(
~ {
~ bindings = [
~ {
~ members = [
- "group:[email protected]",
"serviceAccount:terraform-iac-pipeline@cf-customer-example-18.iam.gserviceaccount.com",
]
# (1 unchanged attribute hidden)
},
~ {
~ members = [
- "group:[email protected]",
"serviceAccount:terraform-iac-pipeline@cf-customer-example-18.iam.gserviceaccount.com",
]
# (1 unchanged attribute hidden)
},
~ {
~ members = [
- "group:[email protected]",
"serviceAccount:terraform-iac-pipeline@cf-customer-example-18.iam.gserviceaccount.com",
]
# (1 unchanged attribute hidden)
},
(output stripped)
This is expected behaviour and should not cause any issues.