From e3471e96533db4dc6317a7dda9bec868ac72c928 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Tue, 12 Nov 2019 13:11:32 +1100 Subject: [PATCH 01/27] Add initial cut of org bootstrap --- .gitignore | 51 ++-- .pre-commit-config.yaml | 19 ++ README.md | 91 +++--- examples/cloudbuild_enabled/README.md | 26 ++ .../cloudbuild-tf-apply.yaml | 38 +++ .../cloudbuild-tf-plan.yaml | 31 +++ examples/cloudbuild_enabled/main.tf | 42 +++ examples/cloudbuild_enabled/outputs.tf | 51 ++++ examples/cloudbuild_enabled/variables.tf | 19 ++ examples/simple/README.md | 20 ++ .../versions.tf => simple/main.tf} | 16 +- .../variables.tf => simple/outputs.tf} | 17 +- examples/simple/variables.tf | 19 ++ examples/simple_example/README.md | 25 -- examples/simple_example/outputs.tf | 20 -- main.tf | 152 +++++++++- modules/cloudbuild/README.md | 78 ++++++ .../cloudbuild/cloudbuild_builder/Dockerfile | 22 ++ .../cloudbuild_builder/README.markdown | 14 + .../cloudbuild_builder/cloudbuild.yaml | 8 + .../cloudbuild_builder/entrypoint.bash | 53 ++++ modules/cloudbuild/main.tf | 259 ++++++++++++++++++ modules/cloudbuild/outputs.tf | 37 +++ modules/cloudbuild/variables.tf | 105 +++++++ outputs.tf | 34 ++- .../main.tf => terraform.example.tfvars | 17 +- variables.tf | 94 ++++++- versions.tf | 2 +- 28 files changed, 1205 insertions(+), 155 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 examples/cloudbuild_enabled/README.md create mode 100644 examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml create mode 100644 examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml create mode 100644 examples/cloudbuild_enabled/main.tf create mode 100644 examples/cloudbuild_enabled/outputs.tf create mode 100644 examples/cloudbuild_enabled/variables.tf create mode 100644 examples/simple/README.md rename examples/{simple_example/versions.tf => simple/main.tf} (61%) rename examples/{simple_example/variables.tf => simple/outputs.tf} (67%) create mode 100644 examples/simple/variables.tf delete mode 100644 examples/simple_example/README.md delete mode 100644 examples/simple_example/outputs.tf create mode 100644 modules/cloudbuild/README.md create mode 100644 modules/cloudbuild/cloudbuild_builder/Dockerfile create mode 100644 modules/cloudbuild/cloudbuild_builder/README.markdown create mode 100644 modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml create mode 100755 modules/cloudbuild/cloudbuild_builder/entrypoint.bash create mode 100644 modules/cloudbuild/main.tf create mode 100644 modules/cloudbuild/outputs.tf create mode 100644 modules/cloudbuild/variables.tf rename examples/simple_example/main.tf => terraform.example.tfvars (70%) diff --git a/.gitignore b/.gitignore index 9ce300b1..4b69ee4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,8 @@ -# OSX leaves these everywhere on SMB shares -._* -# OSX trash -.DS_Store - -# Python -*.pyc - -# Emacs save files -*~ -\#*\# -.\#* - -# Vim-related files -[._]*.s[a-w][a-z] -[._]s[a-w][a-z] -*.un~ -Session.vim -.netrwhist - -### https://raw.github.com/github/gitignore/90f149de451a5433aebd94d02d11b0e28843a1af/Terraform.gitignore +# Created by https://www.gitignore.io/api/terraform +# Edit at https://www.gitignore.io/?templates=terraform +### Terraform ### # Local .terraform directories **/.terraform/* @@ -31,15 +13,26 @@ Session.vim # Crash log files crash.log -# Kitchen files -**/inspec.lock -**/.kitchen -**/kitchen.local.yml -**/Gemfile.lock - # Ignore any .tfvars files that are generated automatically for each Terraform run. Most # .tfvars files are managed as part of configuration and so should be included in # version control. -**/*.tfvars - +# +# example.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json credentials.json +bootstrap/credentials.enc +.idea/ + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# End of https://www.gitignore.io/api/terraform diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..c3d025cc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +repos: +- repo: git://github.com/antonbabenko/pre-commit-terraform + rev: v1.17.0 + hooks: + - id: terraform_fmt + - id: terraform_docs +- repo: git://github.com/pre-commit/pre-commit-hooks + rev: v2.2.3 + hooks: + - id: check-executables-have-shebangs + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: detect-private-key + - id: pretty-format-json + args: [--autofix] + - id: sort-simple-yaml + - id: trailing-whitespace diff --git a/README.md b/README.md index 32f51cca..476bbecf 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # terraform-google-bootstrap -This module was generated from [terraform-google-module-template](https://github.com/terraform-google-modules/terraform-google-module-template/), which by default generates a module that simply creates a GCS bucket. As the module develops, this README should be updated. - -The resources/services/activations/deletions that this module will create/trigger are: - -- Create a GCS bucket with the provided name +The purpose of this module is to help bootstrap a GCP organization, creating all the required GCP resources & permissions to start using the Cloud Foundation Toolkit (CFT). For users who want to use Cloud Build & Cloud Source Repos for foundations code, there is also a submodule to help bootstrap all the required resources to do this. ## Usage @@ -15,68 +11,69 @@ module "bootstrap" { source = "terraform-google-modules/bootstrap/google" version = "~> 0.1" - project_id = "" - bucket_name = "gcs-test-bucket" + organization_id = "" + billing_account = "" + group_org_admins = "gcp-organization-admins@example.com" + group_billing_admins = "gcp-billing-admins@example.com" + default_region = "australia-southeast1" } ``` Functional examples are included in the [examples](./examples/) directory. +## Features + +The Organization Bootstrap module will take the following actions: + +1. Create a new GCP seed project using `project_prefix`. +1. Enable APIs in the seed project using `activate_apis` +1. Create a new service account for terraform in seed project +1. Create GCS bucket for Terraform state and grant access to service account +1. Grant IAM permissions required for CFT modules & Organization setup + 1. Overwrite organization wide project creator and billing account creator roles + 1. Grant Organization permissions to service account using `sa_org_iam_permissions` + 1. Grant access to billing account for service account + 1. Grant Organization permissions to `group_org_admins` using `org_admins_org_iam_permissions` + 1. Grant billing permissions to `group_billing_admins` + 1. (optional) Permissions required for service account impersonation using `sa_enable_impersonation` + +For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). + + ## Inputs | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| bucket\_name | The name of the bucket to create | string | n/a | yes | -| project\_id | The project ID to deploy to | string | n/a | yes | +| activate\_apis | List of APIs to enable in the seed project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | +| billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | +| credentials\_file\_path | Service account key path with default to Application Default Credentials path | string | `"~/.config/gcloud/application_default_credentials.json"` | no | +| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | +| group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator" ]` | no | +| organization\_id | GCP Organization ID | string | n/a | yes | +| project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | +| sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | +| sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/compute.networkAdmin", "roles/compute.xpnAdmin", "roles/iam.serviceAccountAdmin", "roles/logging.configWriter", "roles/orgpolicy.policyAdmin", "roles/resourcemanager.folderCreator", "roles/resourcemanager.folderViewer", "roles/resourcemanager.organizationViewer" ]` | no | ## Outputs | Name | Description | |------|-------------| -| bucket\_name | | +| gcs\_bucket\_tfstate | Bucket used for storing terraform state for foundations pipelines in seed project. | +| seed\_project\_id | Project where service accounts and core APIs will be enabled. | +| terraform\_sa\_email | Email for privileged service account. | +| terraform\_sa\_name | Fully qualified name for privileged service account. | ## Requirements -These sections describe requirements for using this module. - ### Software -The following dependencies must be available: - -- [Terraform][terraform] v0.12 -- [Terraform Provider for GCP][terraform-provider-gcp] plugin v2.0 - -### Service Account - -A service account with the following roles must be used to provision -the resources of this module: - -- Storage Admin: `roles/storage.admin` - -The [Project Factory module][project-factory-module] and the -[IAM module][iam-module] may be used in combination to provision a -service account with the necessary roles applied. - -### APIs - -A project with the following APIs enabled must be used to host the -resources of this module: - -- Google Cloud Storage JSON API: `storage-api.googleapis.com` - -The [Project Factory module][project-factory-module] can be used to -provision a project with the necessary APIs enabled. - -## Contributing - -Refer to the [contribution guidelines](./CONTRIBUTING.md) for -information on contributing to this module. - -[iam-module]: https://registry.terraform.io/modules/terraform-google-modules/iam/google -[project-factory-module]: https://registry.terraform.io/modules/terraform-google-modules/project-factory/google -[terraform-provider-gcp]: https://www.terraform.io/docs/providers/google/index.html -[terraform]: https://www.terraform.io/downloads.html +- [gcloud sdk](https://cloud.google.com/sdk/install) >= 206.0.0 +- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.6 +- [terraform-provider-google] plugin 2.1.x +- [terraform-provider-google-beta] plugin 2.1.x \ No newline at end of file diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md new file mode 100644 index 00000000..915a1ed5 --- /dev/null +++ b/examples/cloudbuild_enabled/README.md @@ -0,0 +1,26 @@ + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | +| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | +| group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| organization\_id | GCP Organization ID | string | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| cloudbuild\_project\_id | | +| csr\_repos | | +| gcs\_bucket\_cloudbuild\_artifacts | | +| gcs\_bucket\_tfstate | | +| kms\_crypto\_key | | +| kms\_keyring | | +| seed\_project\_id | | +| terraform\_sa\_email | | +| terraform\_sa\_name | | + + \ No newline at end of file diff --git a/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml b/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml new file mode 100644 index 00000000..9aae09c9 --- /dev/null +++ b/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml @@ -0,0 +1,38 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +timeout: 1200s +steps: +# terraform init -input=false +- name: gcr.io/$PROJECT_ID/terraform + args: + - init + - -input=false +# terraform plan -input=false -out=tfplan +- name: gcr.io/$PROJECT_ID/terraform + args: + - plan + - -input=false + - -out=tfplan +# terraform apply -auto-approve -input=false tfplan +- name: gcr.io/$PROJECT_ID/terraform + args: + - apply + - -auto-approve + - -input=false + - tfplan +artifacts: + objects: + location: 'gs://${_ARTIFACT_BUCKET_NAME}/terraform/cloudbuild/apply/$BUILD_ID' + paths: ['cloudbuild-tf-apply.yaml', 'tfplan'] \ No newline at end of file diff --git a/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml b/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml new file mode 100644 index 00000000..a73ad47a --- /dev/null +++ b/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml @@ -0,0 +1,31 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +timeout: 1200s +steps: +# terraform init -input=false +- name: gcr.io/$PROJECT_ID/terraform + args: + - init + - -input=false +# terraform plan -input=false -out=tfplan +- name: gcr.io/$PROJECT_ID/terraform + args: + - plan + - -input=false + - -out=tfplan +artifacts: + objects: + location: 'gs://${_ARTIFACT_BUCKET_NAME}/terraform/cloudbuild/plan/$BUILD_ID' + paths: ['cloudbuild-tf-plan.yaml', 'tfplan'] \ No newline at end of file diff --git a/examples/cloudbuild_enabled/main.tf b/examples/cloudbuild_enabled/main.tf new file mode 100644 index 00000000..736c3809 --- /dev/null +++ b/examples/cloudbuild_enabled/main.tf @@ -0,0 +1,42 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +provider "google" { + version = "~> 2.0" +} + +module "seed_bootstrap" { + source = "../.." + organization_id = var.organization_id + billing_account = var.billing_account + group_org_admins = var.group_org_admins + group_billing_admins = var.group_billing_admins + default_region = var.default_region + sa_enable_impersonation = true +} + +module "cloudbuild_bootstrap" { + source = "../../modules/cloudbuild" + organization_id = var.organization_id + billing_account = var.billing_account + group_org_admins = var.group_org_admins + default_region = var.default_region + sa_enable_impersonation = true + terraform_sa_email = module.seed_bootstrap.terraform_sa_email + terraform_sa_name = module.seed_bootstrap.terraform_sa_name + terraform_state_bucket = module.seed_bootstrap.gcs_bucket_tfstate +} \ No newline at end of file diff --git a/examples/cloudbuild_enabled/outputs.tf b/examples/cloudbuild_enabled/outputs.tf new file mode 100644 index 00000000..1afdb894 --- /dev/null +++ b/examples/cloudbuild_enabled/outputs.tf @@ -0,0 +1,51 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "seed_project_id" { + value = module.seed_bootstrap.seed_project_id +} + +output "terraform_sa_email" { + value = module.seed_bootstrap.terraform_sa_email +} + +output "terraform_sa_name" { + value = module.seed_bootstrap.terraform_sa_name +} + +output "gcs_bucket_tfstate" { + value = module.seed_bootstrap.gcs_bucket_tfstate +} + +output "cloudbuild_project_id" { + value = module.cloudbuild_bootstrap.cloudbuild_project_id +} + +output "gcs_bucket_cloudbuild_artifacts" { + value = module.cloudbuild_bootstrap.gcs_bucket_cloudbuild_artifacts +} + +output "csr_repos" { + value = module.cloudbuild_bootstrap.csr_repos +} + +output "kms_keyring" { + value = module.cloudbuild_bootstrap.kms_keyring +} + +output "kms_crypto_key" { + value = module.cloudbuild_bootstrap.kms_crypto_key +} \ No newline at end of file diff --git a/examples/cloudbuild_enabled/variables.tf b/examples/cloudbuild_enabled/variables.tf new file mode 100644 index 00000000..179f2c41 --- /dev/null +++ b/examples/cloudbuild_enabled/variables.tf @@ -0,0 +1,19 @@ +variable "organization_id" { + description = "GCP Organization ID" +} + +variable "billing_account" { + description = "The ID of the billing account to associate projects with." +} + +variable "group_org_admins" { + description = "Google Group for GCP Organization Administrators" +} + +variable "group_billing_admins" { + description = "Google Group for GCP Billing Administrators" +} + +variable "default_region" { + description = "Default region to create resources where applicable." +} \ No newline at end of file diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 00000000..e938e8a0 --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,20 @@ + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | +| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | +| group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| organization\_id | GCP Organization ID | string | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| gcs\_bucket\_tfstate | | +| org\_terraform\_sa\_email | | +| seed\_project\_id | | + + \ No newline at end of file diff --git a/examples/simple_example/versions.tf b/examples/simple/main.tf similarity index 61% rename from examples/simple_example/versions.tf rename to examples/simple/main.tf index 832ec1df..a0835714 100644 --- a/examples/simple_example/versions.tf +++ b/examples/simple/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,16 @@ * limitations under the License. */ -terraform { - required_version = ">= 0.12" + +provider "google" { + version = "~> 2.0" } + +module "seed_bootstrap" { + source = "../.." + organization_id = var.organization_id + billing_account = var.billing_account + group_org_admins = var.group_org_admins + group_billing_admins = var.group_billing_admins + default_region = var.default_region +} \ No newline at end of file diff --git a/examples/simple_example/variables.tf b/examples/simple/outputs.tf similarity index 67% rename from examples/simple_example/variables.tf rename to examples/simple/outputs.tf index eeadd9d7..1f93b399 100644 --- a/examples/simple_example/variables.tf +++ b/examples/simple/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,15 @@ * limitations under the License. */ -variable "project_id" { - description = "The ID of the project in which to provision resources." - type = string + +output "seed_project_id" { + value = module.seed_bootstrap.seed_project_id } -variable "bucket_name" { - description = "The name of the bucket to create." - type = string +output "org_terraform_sa_email" { + value = module.seed_bootstrap.org_terraform_sa_email } + +output "gcs_bucket_tfstate" { + value = module.seed_bootstrap.gcs_bucket_tfstate +} \ No newline at end of file diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf new file mode 100644 index 00000000..179f2c41 --- /dev/null +++ b/examples/simple/variables.tf @@ -0,0 +1,19 @@ +variable "organization_id" { + description = "GCP Organization ID" +} + +variable "billing_account" { + description = "The ID of the billing account to associate projects with." +} + +variable "group_org_admins" { + description = "Google Group for GCP Organization Administrators" +} + +variable "group_billing_admins" { + description = "Google Group for GCP Billing Administrators" +} + +variable "default_region" { + description = "Default region to create resources where applicable." +} \ No newline at end of file diff --git a/examples/simple_example/README.md b/examples/simple_example/README.md deleted file mode 100644 index ef4f2f96..00000000 --- a/examples/simple_example/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Simple Example - -This example illustrates how to use the `bootstrap` module. - - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|:----:|:-----:|:-----:| -| bucket\_name | The name of the bucket to create. | string | n/a | yes | -| project\_id | The ID of the project in which to provision resources. | string | n/a | yes | - -## Outputs - -| Name | Description | -|------|-------------| -| bucket\_name | The name of the bucket. | - - - -To provision this example, run the following from within this directory: -- `terraform init` to get the plugins -- `terraform plan` to see the infrastructure plan -- `terraform apply` to apply the infrastructure build -- `terraform destroy` to destroy the built infrastructure diff --git a/examples/simple_example/outputs.tf b/examples/simple_example/outputs.tf deleted file mode 100644 index 7e303657..00000000 --- a/examples/simple_example/outputs.tf +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -output "bucket_name" { - description = "The name of the bucket." - value = module.bootstrap.bucket_name -} diff --git a/main.tf b/main.tf index 5693fda0..137ab14a 100644 --- a/main.tf +++ b/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,151 @@ * limitations under the License. */ -terraform { - required_version = "~> 0.12.0" +locals { + seed_project_id = format("%s-%s-%s", var.project_prefix, "seed", random_id.suffix.hex) + impersonation_apis = distinct(concat(var.activate_apis, ["serviceusage.googleapis.com", "iamcredentials.googleapis.com"])) + impersonation_enabled_count = var.sa_enable_impersonation == true ? 1 : 0 + activate_apis = var.sa_enable_impersonation == true ? local.impersonation_apis : var.activate_apis } -resource "google_storage_bucket" "main" { - project = var.project_id - name = var.bucket_name +resource "random_id" "suffix" { + byte_length = 2 } + +data "google_organization" "org" { + organization = var.organization_id +} + +/****************************************** + Create IaC Project +*******************************************/ + +resource "google_project" "seed_project" { + name = local.seed_project_id + project_id = local.seed_project_id + org_id = var.organization_id + billing_account = var.billing_account + auto_create_network = false + depends_on = [ + google_organization_iam_member.org_admins_group, + ] +} + +resource "google_project_service" "seed_project_api" { + count = length(local.activate_apis) + project = google_project.seed_project.id + service = local.activate_apis[count.index] + disable_dependent_services = true +} + +/****************************************** + Service Account - Terraform for Org +*******************************************/ + +resource "google_service_account" "org_terraform" { + project = google_project.seed_project.id + account_id = "org-terraform" + display_name = "CFT Organization Terraform Account" +} + +/*********************************************** + GCS Bucket - Terraform State + ***********************************************/ + +resource "google_storage_bucket" "org_terraform_state" { + project = google_project.seed_project.id + name = format("%s-%s-%s", var.project_prefix, "tfstate", random_id.suffix.hex) + location = var.default_region + force_destroy = true +} + +/*********************************************** + Authorative permissions at org. Required to + remove default org wide permissions + granting billing account and project creation. + ***********************************************/ + +resource "google_organization_iam_binding" "billing_creator" { + org_id = var.organization_id + role = "roles/billing.creator" + + members = [ + "group:${var.group_billing_admins}", + ] +} + +resource "google_organization_iam_binding" "project_creator" { + org_id = var.organization_id + role = "roles/resourcemanager.projectCreator" + + members = [ + "serviceAccount:${google_service_account.org_terraform.email}", + "group:${var.group_org_admins}" + ] +} + +/*********************************************** + Organization permissions for org admins. + ***********************************************/ + +resource "google_organization_iam_member" "org_admins_group" { + count = length(var.org_admins_org_iam_permissions) + org_id = var.organization_id + role = var.org_admins_org_iam_permissions[count.index] + member = "group:${var.group_org_admins}" +} + +/*********************************************** + Organization permissions for billing admins. + ***********************************************/ + +resource "google_organization_iam_member" "org_billing_admin" { + org_id = var.organization_id + role = "roles/billing.admin" + member = "group:${var.group_billing_admins}" +} + +/*********************************************** + Organization permissions for Terraform. + ***********************************************/ + +resource "google_organization_iam_member" "tf_sa_org_perms" { + count = length(var.sa_org_iam_permissions) + org_id = var.organization_id + role = var.sa_org_iam_permissions[count.index] + member = "serviceAccount:${google_service_account.org_terraform.email}" +} + +resource "google_billing_account_iam_member" "tf_billing_user" { + billing_account_id = var.billing_account + role = "roles/billing.user" + member = "serviceAccount:${google_service_account.org_terraform.email}" +} + +resource "google_storage_bucket_iam_member" "org_terraform_state_iam" { + bucket = google_storage_bucket.org_terraform_state.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${google_service_account.org_terraform.email}" +} + +/*********************************************** + IAM - Impersonation permissions to run terraform + as org admin. + ***********************************************/ + +resource "google_service_account_iam_member" "org_admin_sa_impersonate_permissions" { + count = local.impersonation_enabled_count + + service_account_id = google_service_account.org_terraform.name + role = "roles/iam.serviceAccountTokenCreator" + member = "group:${var.group_org_admins}" +} + +resource "google_organization_iam_member" "org_admin_serviceusage_consumer" { + count = local.impersonation_enabled_count + + org_id = var.organization_id + role = "roles/serviceusage.serviceUsageConsumer" + member = "group:${var.group_org_admins}" + depends_on = [google_project_service.seed_project_api] +} \ No newline at end of file diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md new file mode 100644 index 00000000..733f84b8 --- /dev/null +++ b/modules/cloudbuild/README.md @@ -0,0 +1,78 @@ +## Overview + + +## Usage + +Basic usage of this module is as follows: + +```hcl +module "bootstrap" { + source = "terraform-google-modules/bootstrap/google//modules/cloudbuild" + version = "~> 0.1" + + organization_id = "" + billing_account = "" + group_org_admins = "gcp-organization-admins@example.com" + group_billing_admins = "gcp-billing-admins@example.com" + default_region = "australia-southeast1" + sa_enable_impersonation = true + terraform_sa_email = "" + terraform_sa_name = "" + terraform_state_bucket = "" +} +``` + +Functional examples and sample Cloud Build definitions are included in the [examples](../../examples/) directory. + +## Features + +1. Create a new GCP cloud build project using `project_prefix` +1. Enable APIs in the cloud build project using `activate_apis` +1. Build a Terraform docker image for Cloud Build +1. Create a GCS bucket for Cloud Build Artifacts using `project_prefix` +1. Create Cloud Source Repos for pipelines using list of repos in `cloud_source_repos` + 1. Create Cloud Build trigger for terraform apply on master branch + 1. Create Cloud Build trigger for terrafor plan on all other branches +1. Create KMS Keyring and key for encryption + 1. Grant access to decrypt to Cloud Build service account and `terraform_sa_email` + 1. Grant access to encrypt to `group_org_admins` +1. Optionally give Cloud Build service account permissions to impersonate terraform service account using `sa_enable_impersonation` and supplied value for `terraform_sa_name` + + + +## Resources created + +- KMS Keyring and key for secrets, including IAM for Cloudbuild, Org Admins and Terraform service acocunt +- (optional) Cloudbuild impersonation permissions for a service account +- (optional) Cloud Source Repos, with triggers for terraform plan (all other branches) & terraform apply (master) + + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | +| billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | +| cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `[ "gcp-org", "gcp-networks", "gcp-projects" ]` | no | +| credentials\_file\_path | Service account key path with default to Application Default Credentials path | string | `"~/.config/gcloud/application_default_credentials.json"` | no | +| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| organization\_id | GCP Organization ID | string | n/a | yes | +| project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | +| sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | +| terraform\_sa\_email | Email for terraform service account. | string | n/a | yes | +| terraform\_sa\_name | Fully-qualified name of the terraform service account. | string | n/a | yes | +| terraform\_state\_bucket | Default state bucket, used in Cloud Build substitutions. | string | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| cloudbuild\_project\_id | Project where CloudBuild configuration and terraform container image will reside. | +| csr\_repos | | +| gcs\_bucket\_cloudbuild\_artifacts | Bucket used to store Cloud/Build artefacts in CloudBuild project. | +| kms\_crypto\_key | | +| kms\_keyring | | + + \ No newline at end of file diff --git a/modules/cloudbuild/cloudbuild_builder/Dockerfile b/modules/cloudbuild/cloudbuild_builder/Dockerfile new file mode 100644 index 00000000..3fb2f22b --- /dev/null +++ b/modules/cloudbuild/cloudbuild_builder/Dockerfile @@ -0,0 +1,22 @@ +FROM gcr.io/cloud-builders/gcloud-slim + +ENV TERRAFORM_VERSION=0.12.6 +ENV TERRAFORM_VERSION_SHA256SUM=6544eb55b3e916affeea0a46fe785329c36de1ba1bdb51ca5239d3567101876f + +RUN apt-get update && \ + /builder/google-cloud-sdk/bin/gcloud -q components install alpha beta && \ + apt-get -y install curl jq unzip ca-certificates && \ + curl https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip \ + > terraform_linux_amd64.zip && \ + echo "${TERRAFORM_VERSION_SHA256SUM} terraform_linux_amd64.zip" > terraform_SHA256SUMS && \ + sha256sum -c terraform_SHA256SUMS --status && \ + unzip terraform_linux_amd64.zip -d /builder/terraform && \ + rm -f terraform_linux_amd64.zip && \ + apt-get remove --purge -y curl unzip && \ + apt-get --purge -y autoremove && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +ENV PATH=/builder/terraform/:$PATH +COPY entrypoint.bash /builder/entrypoint.bash +ENTRYPOINT ["/builder/entrypoint.bash"] diff --git a/modules/cloudbuild/cloudbuild_builder/README.markdown b/modules/cloudbuild/cloudbuild_builder/README.markdown new file mode 100644 index 00000000..656a43a7 --- /dev/null +++ b/modules/cloudbuild/cloudbuild_builder/README.markdown @@ -0,0 +1,14 @@ +# [Terraform](https://www.terraform.io/docs) cloud builder + +## Terraform cloud builder +This builder can be used to run the terraform tool in the GCE. From the Hashicorp Terraform [product page](https://www.terraform.io/): + +> HashiCorp Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source +> tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, +> edited, reviewed, and versioned. + +### Building this builder +To build this builder, run the following command in this directory. +```sh +$ gcloud builds submit --config=cloudbuild.yaml +``` diff --git a/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml b/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml new file mode 100644 index 00000000..774bde7d --- /dev/null +++ b/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml @@ -0,0 +1,8 @@ +# In this directory, run the following command to build this builder. +# $ gcloud builds submit . --config=cloudbuild.yaml +steps: +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '--tag=gcr.io/${PROJECT_ID}/terraform', '.'] +- name: 'gcr.io/${PROJECT_ID}/terraform' + args: ['version'] +images: ['gcr.io/${PROJECT_ID}/terraform'] \ No newline at end of file diff --git a/modules/cloudbuild/cloudbuild_builder/entrypoint.bash b/modules/cloudbuild/cloudbuild_builder/entrypoint.bash new file mode 100755 index 00000000..f557a37e --- /dev/null +++ b/modules/cloudbuild/cloudbuild_builder/entrypoint.bash @@ -0,0 +1,53 @@ +#!/bin/bash +set -e + +active_account="" +function get-active-account() { + active_account=$(gcloud auth list --filter=status:ACTIVE --format="value(account)" 2> /dev/null) +} + +function activate-service-key() { + rootdir=/root/.config/gcloud-config + mkdir -p $rootdir + tmpdir=$(mktemp -d "$rootdir/servicekey.XXXXXXXX") + trap "rm -rf $tmpdir" EXIT + echo ${GCLOUD_SERVICE_KEY} | base64 --decode -i > ${tmpdir}/gcloud-service-key.json + gcloud auth activate-service-account --key-file ${tmpdir}/gcloud-service-key.json --quiet + get-active-account +} + +function service-account-usage() { + cat < +EOF + exit 1 +} + +function account-active-warning() { + cat < Date: Tue, 12 Nov 2019 13:11:32 +1100 Subject: [PATCH 02/27] Add initial cut of org bootstrap --- .gitignore | 8 + .pre-commit-config.yaml | 19 ++ README.md | 91 +++--- examples/cloudbuild_enabled/README.md | 26 ++ .../cloudbuild-tf-apply.yaml | 38 +++ .../cloudbuild-tf-plan.yaml | 31 +++ examples/cloudbuild_enabled/main.tf | 42 +++ examples/cloudbuild_enabled/outputs.tf | 51 ++++ examples/cloudbuild_enabled/variables.tf | 19 ++ examples/simple/README.md | 20 ++ .../versions.tf => simple/main.tf} | 16 +- .../variables.tf => simple/outputs.tf} | 17 +- examples/simple/variables.tf | 19 ++ examples/simple_example/README.md | 25 -- examples/simple_example/outputs.tf | 20 -- main.tf | 152 +++++++++- modules/cloudbuild/README.md | 78 ++++++ .../cloudbuild/cloudbuild_builder/Dockerfile | 22 ++ .../cloudbuild_builder/README.markdown | 14 + .../cloudbuild_builder/cloudbuild.yaml | 8 + .../cloudbuild_builder/entrypoint.bash | 53 ++++ modules/cloudbuild/main.tf | 259 ++++++++++++++++++ modules/cloudbuild/outputs.tf | 37 +++ modules/cloudbuild/variables.tf | 105 +++++++ outputs.tf | 34 ++- .../main.tf => terraform.example.tfvars | 17 +- variables.tf | 94 ++++++- versions.tf | 2 +- 28 files changed, 1191 insertions(+), 126 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 examples/cloudbuild_enabled/README.md create mode 100644 examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml create mode 100644 examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml create mode 100644 examples/cloudbuild_enabled/main.tf create mode 100644 examples/cloudbuild_enabled/outputs.tf create mode 100644 examples/cloudbuild_enabled/variables.tf create mode 100644 examples/simple/README.md rename examples/{simple_example/versions.tf => simple/main.tf} (61%) rename examples/{simple_example/variables.tf => simple/outputs.tf} (67%) create mode 100644 examples/simple/variables.tf delete mode 100644 examples/simple_example/README.md delete mode 100644 examples/simple_example/outputs.tf create mode 100644 modules/cloudbuild/README.md create mode 100644 modules/cloudbuild/cloudbuild_builder/Dockerfile create mode 100644 modules/cloudbuild/cloudbuild_builder/README.markdown create mode 100644 modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml create mode 100755 modules/cloudbuild/cloudbuild_builder/entrypoint.bash create mode 100644 modules/cloudbuild/main.tf create mode 100644 modules/cloudbuild/outputs.tf create mode 100644 modules/cloudbuild/variables.tf rename examples/simple_example/main.tf => terraform.example.tfvars (70%) diff --git a/.gitignore b/.gitignore index 9ce300b1..cbf19e2a 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,11 @@ crash.log **/*.tfvars credentials.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.idea/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..c3d025cc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +repos: +- repo: git://github.com/antonbabenko/pre-commit-terraform + rev: v1.17.0 + hooks: + - id: terraform_fmt + - id: terraform_docs +- repo: git://github.com/pre-commit/pre-commit-hooks + rev: v2.2.3 + hooks: + - id: check-executables-have-shebangs + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: detect-private-key + - id: pretty-format-json + args: [--autofix] + - id: sort-simple-yaml + - id: trailing-whitespace diff --git a/README.md b/README.md index 32f51cca..476bbecf 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # terraform-google-bootstrap -This module was generated from [terraform-google-module-template](https://github.com/terraform-google-modules/terraform-google-module-template/), which by default generates a module that simply creates a GCS bucket. As the module develops, this README should be updated. - -The resources/services/activations/deletions that this module will create/trigger are: - -- Create a GCS bucket with the provided name +The purpose of this module is to help bootstrap a GCP organization, creating all the required GCP resources & permissions to start using the Cloud Foundation Toolkit (CFT). For users who want to use Cloud Build & Cloud Source Repos for foundations code, there is also a submodule to help bootstrap all the required resources to do this. ## Usage @@ -15,68 +11,69 @@ module "bootstrap" { source = "terraform-google-modules/bootstrap/google" version = "~> 0.1" - project_id = "" - bucket_name = "gcs-test-bucket" + organization_id = "" + billing_account = "" + group_org_admins = "gcp-organization-admins@example.com" + group_billing_admins = "gcp-billing-admins@example.com" + default_region = "australia-southeast1" } ``` Functional examples are included in the [examples](./examples/) directory. +## Features + +The Organization Bootstrap module will take the following actions: + +1. Create a new GCP seed project using `project_prefix`. +1. Enable APIs in the seed project using `activate_apis` +1. Create a new service account for terraform in seed project +1. Create GCS bucket for Terraform state and grant access to service account +1. Grant IAM permissions required for CFT modules & Organization setup + 1. Overwrite organization wide project creator and billing account creator roles + 1. Grant Organization permissions to service account using `sa_org_iam_permissions` + 1. Grant access to billing account for service account + 1. Grant Organization permissions to `group_org_admins` using `org_admins_org_iam_permissions` + 1. Grant billing permissions to `group_billing_admins` + 1. (optional) Permissions required for service account impersonation using `sa_enable_impersonation` + +For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). + + ## Inputs | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| bucket\_name | The name of the bucket to create | string | n/a | yes | -| project\_id | The project ID to deploy to | string | n/a | yes | +| activate\_apis | List of APIs to enable in the seed project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | +| billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | +| credentials\_file\_path | Service account key path with default to Application Default Credentials path | string | `"~/.config/gcloud/application_default_credentials.json"` | no | +| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | +| group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator" ]` | no | +| organization\_id | GCP Organization ID | string | n/a | yes | +| project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | +| sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | +| sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/compute.networkAdmin", "roles/compute.xpnAdmin", "roles/iam.serviceAccountAdmin", "roles/logging.configWriter", "roles/orgpolicy.policyAdmin", "roles/resourcemanager.folderCreator", "roles/resourcemanager.folderViewer", "roles/resourcemanager.organizationViewer" ]` | no | ## Outputs | Name | Description | |------|-------------| -| bucket\_name | | +| gcs\_bucket\_tfstate | Bucket used for storing terraform state for foundations pipelines in seed project. | +| seed\_project\_id | Project where service accounts and core APIs will be enabled. | +| terraform\_sa\_email | Email for privileged service account. | +| terraform\_sa\_name | Fully qualified name for privileged service account. | ## Requirements -These sections describe requirements for using this module. - ### Software -The following dependencies must be available: - -- [Terraform][terraform] v0.12 -- [Terraform Provider for GCP][terraform-provider-gcp] plugin v2.0 - -### Service Account - -A service account with the following roles must be used to provision -the resources of this module: - -- Storage Admin: `roles/storage.admin` - -The [Project Factory module][project-factory-module] and the -[IAM module][iam-module] may be used in combination to provision a -service account with the necessary roles applied. - -### APIs - -A project with the following APIs enabled must be used to host the -resources of this module: - -- Google Cloud Storage JSON API: `storage-api.googleapis.com` - -The [Project Factory module][project-factory-module] can be used to -provision a project with the necessary APIs enabled. - -## Contributing - -Refer to the [contribution guidelines](./CONTRIBUTING.md) for -information on contributing to this module. - -[iam-module]: https://registry.terraform.io/modules/terraform-google-modules/iam/google -[project-factory-module]: https://registry.terraform.io/modules/terraform-google-modules/project-factory/google -[terraform-provider-gcp]: https://www.terraform.io/docs/providers/google/index.html -[terraform]: https://www.terraform.io/downloads.html +- [gcloud sdk](https://cloud.google.com/sdk/install) >= 206.0.0 +- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.6 +- [terraform-provider-google] plugin 2.1.x +- [terraform-provider-google-beta] plugin 2.1.x \ No newline at end of file diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md new file mode 100644 index 00000000..915a1ed5 --- /dev/null +++ b/examples/cloudbuild_enabled/README.md @@ -0,0 +1,26 @@ + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | +| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | +| group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| organization\_id | GCP Organization ID | string | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| cloudbuild\_project\_id | | +| csr\_repos | | +| gcs\_bucket\_cloudbuild\_artifacts | | +| gcs\_bucket\_tfstate | | +| kms\_crypto\_key | | +| kms\_keyring | | +| seed\_project\_id | | +| terraform\_sa\_email | | +| terraform\_sa\_name | | + + \ No newline at end of file diff --git a/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml b/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml new file mode 100644 index 00000000..9aae09c9 --- /dev/null +++ b/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml @@ -0,0 +1,38 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +timeout: 1200s +steps: +# terraform init -input=false +- name: gcr.io/$PROJECT_ID/terraform + args: + - init + - -input=false +# terraform plan -input=false -out=tfplan +- name: gcr.io/$PROJECT_ID/terraform + args: + - plan + - -input=false + - -out=tfplan +# terraform apply -auto-approve -input=false tfplan +- name: gcr.io/$PROJECT_ID/terraform + args: + - apply + - -auto-approve + - -input=false + - tfplan +artifacts: + objects: + location: 'gs://${_ARTIFACT_BUCKET_NAME}/terraform/cloudbuild/apply/$BUILD_ID' + paths: ['cloudbuild-tf-apply.yaml', 'tfplan'] \ No newline at end of file diff --git a/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml b/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml new file mode 100644 index 00000000..a73ad47a --- /dev/null +++ b/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml @@ -0,0 +1,31 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +timeout: 1200s +steps: +# terraform init -input=false +- name: gcr.io/$PROJECT_ID/terraform + args: + - init + - -input=false +# terraform plan -input=false -out=tfplan +- name: gcr.io/$PROJECT_ID/terraform + args: + - plan + - -input=false + - -out=tfplan +artifacts: + objects: + location: 'gs://${_ARTIFACT_BUCKET_NAME}/terraform/cloudbuild/plan/$BUILD_ID' + paths: ['cloudbuild-tf-plan.yaml', 'tfplan'] \ No newline at end of file diff --git a/examples/cloudbuild_enabled/main.tf b/examples/cloudbuild_enabled/main.tf new file mode 100644 index 00000000..736c3809 --- /dev/null +++ b/examples/cloudbuild_enabled/main.tf @@ -0,0 +1,42 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +provider "google" { + version = "~> 2.0" +} + +module "seed_bootstrap" { + source = "../.." + organization_id = var.organization_id + billing_account = var.billing_account + group_org_admins = var.group_org_admins + group_billing_admins = var.group_billing_admins + default_region = var.default_region + sa_enable_impersonation = true +} + +module "cloudbuild_bootstrap" { + source = "../../modules/cloudbuild" + organization_id = var.organization_id + billing_account = var.billing_account + group_org_admins = var.group_org_admins + default_region = var.default_region + sa_enable_impersonation = true + terraform_sa_email = module.seed_bootstrap.terraform_sa_email + terraform_sa_name = module.seed_bootstrap.terraform_sa_name + terraform_state_bucket = module.seed_bootstrap.gcs_bucket_tfstate +} \ No newline at end of file diff --git a/examples/cloudbuild_enabled/outputs.tf b/examples/cloudbuild_enabled/outputs.tf new file mode 100644 index 00000000..1afdb894 --- /dev/null +++ b/examples/cloudbuild_enabled/outputs.tf @@ -0,0 +1,51 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "seed_project_id" { + value = module.seed_bootstrap.seed_project_id +} + +output "terraform_sa_email" { + value = module.seed_bootstrap.terraform_sa_email +} + +output "terraform_sa_name" { + value = module.seed_bootstrap.terraform_sa_name +} + +output "gcs_bucket_tfstate" { + value = module.seed_bootstrap.gcs_bucket_tfstate +} + +output "cloudbuild_project_id" { + value = module.cloudbuild_bootstrap.cloudbuild_project_id +} + +output "gcs_bucket_cloudbuild_artifacts" { + value = module.cloudbuild_bootstrap.gcs_bucket_cloudbuild_artifacts +} + +output "csr_repos" { + value = module.cloudbuild_bootstrap.csr_repos +} + +output "kms_keyring" { + value = module.cloudbuild_bootstrap.kms_keyring +} + +output "kms_crypto_key" { + value = module.cloudbuild_bootstrap.kms_crypto_key +} \ No newline at end of file diff --git a/examples/cloudbuild_enabled/variables.tf b/examples/cloudbuild_enabled/variables.tf new file mode 100644 index 00000000..179f2c41 --- /dev/null +++ b/examples/cloudbuild_enabled/variables.tf @@ -0,0 +1,19 @@ +variable "organization_id" { + description = "GCP Organization ID" +} + +variable "billing_account" { + description = "The ID of the billing account to associate projects with." +} + +variable "group_org_admins" { + description = "Google Group for GCP Organization Administrators" +} + +variable "group_billing_admins" { + description = "Google Group for GCP Billing Administrators" +} + +variable "default_region" { + description = "Default region to create resources where applicable." +} \ No newline at end of file diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 00000000..e938e8a0 --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,20 @@ + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | +| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | +| group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| organization\_id | GCP Organization ID | string | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| gcs\_bucket\_tfstate | | +| org\_terraform\_sa\_email | | +| seed\_project\_id | | + + \ No newline at end of file diff --git a/examples/simple_example/versions.tf b/examples/simple/main.tf similarity index 61% rename from examples/simple_example/versions.tf rename to examples/simple/main.tf index 832ec1df..a0835714 100644 --- a/examples/simple_example/versions.tf +++ b/examples/simple/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,16 @@ * limitations under the License. */ -terraform { - required_version = ">= 0.12" + +provider "google" { + version = "~> 2.0" } + +module "seed_bootstrap" { + source = "../.." + organization_id = var.organization_id + billing_account = var.billing_account + group_org_admins = var.group_org_admins + group_billing_admins = var.group_billing_admins + default_region = var.default_region +} \ No newline at end of file diff --git a/examples/simple_example/variables.tf b/examples/simple/outputs.tf similarity index 67% rename from examples/simple_example/variables.tf rename to examples/simple/outputs.tf index eeadd9d7..1f93b399 100644 --- a/examples/simple_example/variables.tf +++ b/examples/simple/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,15 @@ * limitations under the License. */ -variable "project_id" { - description = "The ID of the project in which to provision resources." - type = string + +output "seed_project_id" { + value = module.seed_bootstrap.seed_project_id } -variable "bucket_name" { - description = "The name of the bucket to create." - type = string +output "org_terraform_sa_email" { + value = module.seed_bootstrap.org_terraform_sa_email } + +output "gcs_bucket_tfstate" { + value = module.seed_bootstrap.gcs_bucket_tfstate +} \ No newline at end of file diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf new file mode 100644 index 00000000..179f2c41 --- /dev/null +++ b/examples/simple/variables.tf @@ -0,0 +1,19 @@ +variable "organization_id" { + description = "GCP Organization ID" +} + +variable "billing_account" { + description = "The ID of the billing account to associate projects with." +} + +variable "group_org_admins" { + description = "Google Group for GCP Organization Administrators" +} + +variable "group_billing_admins" { + description = "Google Group for GCP Billing Administrators" +} + +variable "default_region" { + description = "Default region to create resources where applicable." +} \ No newline at end of file diff --git a/examples/simple_example/README.md b/examples/simple_example/README.md deleted file mode 100644 index ef4f2f96..00000000 --- a/examples/simple_example/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Simple Example - -This example illustrates how to use the `bootstrap` module. - - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|:----:|:-----:|:-----:| -| bucket\_name | The name of the bucket to create. | string | n/a | yes | -| project\_id | The ID of the project in which to provision resources. | string | n/a | yes | - -## Outputs - -| Name | Description | -|------|-------------| -| bucket\_name | The name of the bucket. | - - - -To provision this example, run the following from within this directory: -- `terraform init` to get the plugins -- `terraform plan` to see the infrastructure plan -- `terraform apply` to apply the infrastructure build -- `terraform destroy` to destroy the built infrastructure diff --git a/examples/simple_example/outputs.tf b/examples/simple_example/outputs.tf deleted file mode 100644 index 7e303657..00000000 --- a/examples/simple_example/outputs.tf +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -output "bucket_name" { - description = "The name of the bucket." - value = module.bootstrap.bucket_name -} diff --git a/main.tf b/main.tf index 5693fda0..137ab14a 100644 --- a/main.tf +++ b/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,151 @@ * limitations under the License. */ -terraform { - required_version = "~> 0.12.0" +locals { + seed_project_id = format("%s-%s-%s", var.project_prefix, "seed", random_id.suffix.hex) + impersonation_apis = distinct(concat(var.activate_apis, ["serviceusage.googleapis.com", "iamcredentials.googleapis.com"])) + impersonation_enabled_count = var.sa_enable_impersonation == true ? 1 : 0 + activate_apis = var.sa_enable_impersonation == true ? local.impersonation_apis : var.activate_apis } -resource "google_storage_bucket" "main" { - project = var.project_id - name = var.bucket_name +resource "random_id" "suffix" { + byte_length = 2 } + +data "google_organization" "org" { + organization = var.organization_id +} + +/****************************************** + Create IaC Project +*******************************************/ + +resource "google_project" "seed_project" { + name = local.seed_project_id + project_id = local.seed_project_id + org_id = var.organization_id + billing_account = var.billing_account + auto_create_network = false + depends_on = [ + google_organization_iam_member.org_admins_group, + ] +} + +resource "google_project_service" "seed_project_api" { + count = length(local.activate_apis) + project = google_project.seed_project.id + service = local.activate_apis[count.index] + disable_dependent_services = true +} + +/****************************************** + Service Account - Terraform for Org +*******************************************/ + +resource "google_service_account" "org_terraform" { + project = google_project.seed_project.id + account_id = "org-terraform" + display_name = "CFT Organization Terraform Account" +} + +/*********************************************** + GCS Bucket - Terraform State + ***********************************************/ + +resource "google_storage_bucket" "org_terraform_state" { + project = google_project.seed_project.id + name = format("%s-%s-%s", var.project_prefix, "tfstate", random_id.suffix.hex) + location = var.default_region + force_destroy = true +} + +/*********************************************** + Authorative permissions at org. Required to + remove default org wide permissions + granting billing account and project creation. + ***********************************************/ + +resource "google_organization_iam_binding" "billing_creator" { + org_id = var.organization_id + role = "roles/billing.creator" + + members = [ + "group:${var.group_billing_admins}", + ] +} + +resource "google_organization_iam_binding" "project_creator" { + org_id = var.organization_id + role = "roles/resourcemanager.projectCreator" + + members = [ + "serviceAccount:${google_service_account.org_terraform.email}", + "group:${var.group_org_admins}" + ] +} + +/*********************************************** + Organization permissions for org admins. + ***********************************************/ + +resource "google_organization_iam_member" "org_admins_group" { + count = length(var.org_admins_org_iam_permissions) + org_id = var.organization_id + role = var.org_admins_org_iam_permissions[count.index] + member = "group:${var.group_org_admins}" +} + +/*********************************************** + Organization permissions for billing admins. + ***********************************************/ + +resource "google_organization_iam_member" "org_billing_admin" { + org_id = var.organization_id + role = "roles/billing.admin" + member = "group:${var.group_billing_admins}" +} + +/*********************************************** + Organization permissions for Terraform. + ***********************************************/ + +resource "google_organization_iam_member" "tf_sa_org_perms" { + count = length(var.sa_org_iam_permissions) + org_id = var.organization_id + role = var.sa_org_iam_permissions[count.index] + member = "serviceAccount:${google_service_account.org_terraform.email}" +} + +resource "google_billing_account_iam_member" "tf_billing_user" { + billing_account_id = var.billing_account + role = "roles/billing.user" + member = "serviceAccount:${google_service_account.org_terraform.email}" +} + +resource "google_storage_bucket_iam_member" "org_terraform_state_iam" { + bucket = google_storage_bucket.org_terraform_state.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${google_service_account.org_terraform.email}" +} + +/*********************************************** + IAM - Impersonation permissions to run terraform + as org admin. + ***********************************************/ + +resource "google_service_account_iam_member" "org_admin_sa_impersonate_permissions" { + count = local.impersonation_enabled_count + + service_account_id = google_service_account.org_terraform.name + role = "roles/iam.serviceAccountTokenCreator" + member = "group:${var.group_org_admins}" +} + +resource "google_organization_iam_member" "org_admin_serviceusage_consumer" { + count = local.impersonation_enabled_count + + org_id = var.organization_id + role = "roles/serviceusage.serviceUsageConsumer" + member = "group:${var.group_org_admins}" + depends_on = [google_project_service.seed_project_api] +} \ No newline at end of file diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md new file mode 100644 index 00000000..733f84b8 --- /dev/null +++ b/modules/cloudbuild/README.md @@ -0,0 +1,78 @@ +## Overview + + +## Usage + +Basic usage of this module is as follows: + +```hcl +module "bootstrap" { + source = "terraform-google-modules/bootstrap/google//modules/cloudbuild" + version = "~> 0.1" + + organization_id = "" + billing_account = "" + group_org_admins = "gcp-organization-admins@example.com" + group_billing_admins = "gcp-billing-admins@example.com" + default_region = "australia-southeast1" + sa_enable_impersonation = true + terraform_sa_email = "" + terraform_sa_name = "" + terraform_state_bucket = "" +} +``` + +Functional examples and sample Cloud Build definitions are included in the [examples](../../examples/) directory. + +## Features + +1. Create a new GCP cloud build project using `project_prefix` +1. Enable APIs in the cloud build project using `activate_apis` +1. Build a Terraform docker image for Cloud Build +1. Create a GCS bucket for Cloud Build Artifacts using `project_prefix` +1. Create Cloud Source Repos for pipelines using list of repos in `cloud_source_repos` + 1. Create Cloud Build trigger for terraform apply on master branch + 1. Create Cloud Build trigger for terrafor plan on all other branches +1. Create KMS Keyring and key for encryption + 1. Grant access to decrypt to Cloud Build service account and `terraform_sa_email` + 1. Grant access to encrypt to `group_org_admins` +1. Optionally give Cloud Build service account permissions to impersonate terraform service account using `sa_enable_impersonation` and supplied value for `terraform_sa_name` + + + +## Resources created + +- KMS Keyring and key for secrets, including IAM for Cloudbuild, Org Admins and Terraform service acocunt +- (optional) Cloudbuild impersonation permissions for a service account +- (optional) Cloud Source Repos, with triggers for terraform plan (all other branches) & terraform apply (master) + + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | +| billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | +| cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `[ "gcp-org", "gcp-networks", "gcp-projects" ]` | no | +| credentials\_file\_path | Service account key path with default to Application Default Credentials path | string | `"~/.config/gcloud/application_default_credentials.json"` | no | +| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| organization\_id | GCP Organization ID | string | n/a | yes | +| project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | +| sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | +| terraform\_sa\_email | Email for terraform service account. | string | n/a | yes | +| terraform\_sa\_name | Fully-qualified name of the terraform service account. | string | n/a | yes | +| terraform\_state\_bucket | Default state bucket, used in Cloud Build substitutions. | string | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| cloudbuild\_project\_id | Project where CloudBuild configuration and terraform container image will reside. | +| csr\_repos | | +| gcs\_bucket\_cloudbuild\_artifacts | Bucket used to store Cloud/Build artefacts in CloudBuild project. | +| kms\_crypto\_key | | +| kms\_keyring | | + + \ No newline at end of file diff --git a/modules/cloudbuild/cloudbuild_builder/Dockerfile b/modules/cloudbuild/cloudbuild_builder/Dockerfile new file mode 100644 index 00000000..3fb2f22b --- /dev/null +++ b/modules/cloudbuild/cloudbuild_builder/Dockerfile @@ -0,0 +1,22 @@ +FROM gcr.io/cloud-builders/gcloud-slim + +ENV TERRAFORM_VERSION=0.12.6 +ENV TERRAFORM_VERSION_SHA256SUM=6544eb55b3e916affeea0a46fe785329c36de1ba1bdb51ca5239d3567101876f + +RUN apt-get update && \ + /builder/google-cloud-sdk/bin/gcloud -q components install alpha beta && \ + apt-get -y install curl jq unzip ca-certificates && \ + curl https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip \ + > terraform_linux_amd64.zip && \ + echo "${TERRAFORM_VERSION_SHA256SUM} terraform_linux_amd64.zip" > terraform_SHA256SUMS && \ + sha256sum -c terraform_SHA256SUMS --status && \ + unzip terraform_linux_amd64.zip -d /builder/terraform && \ + rm -f terraform_linux_amd64.zip && \ + apt-get remove --purge -y curl unzip && \ + apt-get --purge -y autoremove && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +ENV PATH=/builder/terraform/:$PATH +COPY entrypoint.bash /builder/entrypoint.bash +ENTRYPOINT ["/builder/entrypoint.bash"] diff --git a/modules/cloudbuild/cloudbuild_builder/README.markdown b/modules/cloudbuild/cloudbuild_builder/README.markdown new file mode 100644 index 00000000..656a43a7 --- /dev/null +++ b/modules/cloudbuild/cloudbuild_builder/README.markdown @@ -0,0 +1,14 @@ +# [Terraform](https://www.terraform.io/docs) cloud builder + +## Terraform cloud builder +This builder can be used to run the terraform tool in the GCE. From the Hashicorp Terraform [product page](https://www.terraform.io/): + +> HashiCorp Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source +> tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, +> edited, reviewed, and versioned. + +### Building this builder +To build this builder, run the following command in this directory. +```sh +$ gcloud builds submit --config=cloudbuild.yaml +``` diff --git a/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml b/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml new file mode 100644 index 00000000..774bde7d --- /dev/null +++ b/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml @@ -0,0 +1,8 @@ +# In this directory, run the following command to build this builder. +# $ gcloud builds submit . --config=cloudbuild.yaml +steps: +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '--tag=gcr.io/${PROJECT_ID}/terraform', '.'] +- name: 'gcr.io/${PROJECT_ID}/terraform' + args: ['version'] +images: ['gcr.io/${PROJECT_ID}/terraform'] \ No newline at end of file diff --git a/modules/cloudbuild/cloudbuild_builder/entrypoint.bash b/modules/cloudbuild/cloudbuild_builder/entrypoint.bash new file mode 100755 index 00000000..f557a37e --- /dev/null +++ b/modules/cloudbuild/cloudbuild_builder/entrypoint.bash @@ -0,0 +1,53 @@ +#!/bin/bash +set -e + +active_account="" +function get-active-account() { + active_account=$(gcloud auth list --filter=status:ACTIVE --format="value(account)" 2> /dev/null) +} + +function activate-service-key() { + rootdir=/root/.config/gcloud-config + mkdir -p $rootdir + tmpdir=$(mktemp -d "$rootdir/servicekey.XXXXXXXX") + trap "rm -rf $tmpdir" EXIT + echo ${GCLOUD_SERVICE_KEY} | base64 --decode -i > ${tmpdir}/gcloud-service-key.json + gcloud auth activate-service-account --key-file ${tmpdir}/gcloud-service-key.json --quiet + get-active-account +} + +function service-account-usage() { + cat < +EOF + exit 1 +} + +function account-active-warning() { + cat < Date: Tue, 12 Nov 2019 15:55:32 +1100 Subject: [PATCH 03/27] Upgrade terraform version & include permissions for state and cloudbuild logs --- .../cloudbuild/cloudbuild_builder/Dockerfile | 6 +++--- modules/cloudbuild/main.tf | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/modules/cloudbuild/cloudbuild_builder/Dockerfile b/modules/cloudbuild/cloudbuild_builder/Dockerfile index 3fb2f22b..a20a26f2 100644 --- a/modules/cloudbuild/cloudbuild_builder/Dockerfile +++ b/modules/cloudbuild/cloudbuild_builder/Dockerfile @@ -1,10 +1,10 @@ FROM gcr.io/cloud-builders/gcloud-slim -ENV TERRAFORM_VERSION=0.12.6 -ENV TERRAFORM_VERSION_SHA256SUM=6544eb55b3e916affeea0a46fe785329c36de1ba1bdb51ca5239d3567101876f +ENV TERRAFORM_VERSION=0.12.13 +ENV TERRAFORM_VERSION_SHA256SUM=63f765a3f83987b67b046a9c31acff1ec9ee618990d0eab4db34eca6c0d861ec RUN apt-get update && \ - /builder/google-cloud-sdk/bin/gcloud -q components install alpha beta && \ + /builder/google-cloud-sdk/bin/gcloud -q components install alpha beta && \ apt-get -y install curl jq unzip ca-certificates && \ curl https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip \ > terraform_linux_amd64.zip && \ diff --git a/modules/cloudbuild/main.tf b/modules/cloudbuild/main.tf index 72d3994b..3fa9bbe5 100644 --- a/modules/cloudbuild/main.tf +++ b/modules/cloudbuild/main.tf @@ -64,6 +64,12 @@ resource "google_project_iam_member" "org_admins_cloudbuild_editor" { member = "group:${var.group_org_admins}" } +resource "google_project_iam_member" "org_admins_cloudbuild_viewer" { + project = google_project.cloudbuild_project.id + role = "roles/viewer" + member = "group:${var.group_org_admins}" +} + /****************************************** Cloudbuild Artifact bucket *******************************************/ @@ -257,3 +263,14 @@ resource "google_storage_bucket_iam_member" "cloudbuild_artifacts_iam" { google_project_service.cloudbuild_project_api ] } + +# Required to allow cloud build to access state with impersonation. +resource "google_storage_bucket_iam_member" "cloudbuild_state_iam" { + count = local.impersonation_enabled_count + bucket = var.terraform_state_bucket + role = "roles/storage.objectAdmin" + member = "serviceAccount:${google_project.cloudbuild_project.number}@cloudbuild.gserviceaccount.com" + depends_on = [ + google_project_service.cloudbuild_project_api + ] +} \ No newline at end of file From 8fce22f7e7ab6d5bbf302afb79bacca311102230 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Tue, 12 Nov 2019 20:06:51 +1100 Subject: [PATCH 04/27] Fix outputs for simple example --- examples/simple/README.md | 2 +- examples/simple/outputs.tf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/simple/README.md b/examples/simple/README.md index e938e8a0..d60f0659 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -14,7 +14,7 @@ | Name | Description | |------|-------------| | gcs\_bucket\_tfstate | | -| org\_terraform\_sa\_email | | | seed\_project\_id | | +| terraform\_sa\_email | | \ No newline at end of file diff --git a/examples/simple/outputs.tf b/examples/simple/outputs.tf index 1f93b399..63d3a31a 100644 --- a/examples/simple/outputs.tf +++ b/examples/simple/outputs.tf @@ -19,8 +19,8 @@ output "seed_project_id" { value = module.seed_bootstrap.seed_project_id } -output "org_terraform_sa_email" { - value = module.seed_bootstrap.org_terraform_sa_email +output "terraform_sa_email" { + value = module.seed_bootstrap.terraform_sa_email } output "gcs_bucket_tfstate" { From 2767fd62a7a45c47e79798674c2afe78c9bebb35 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Tue, 12 Nov 2019 20:47:38 +1100 Subject: [PATCH 05/27] Update documentation --- README.md | 27 ++++++++++++++++++++++++++- examples/cloudbuild_enabled/README.md | 4 ++++ examples/simple/README.md | 4 ++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 476bbecf..b83b9ded 100644 --- a/README.md +++ b/README.md @@ -76,4 +76,29 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). - [gcloud sdk](https://cloud.google.com/sdk/install) >= 206.0.0 - [Terraform](https://www.terraform.io/downloads.html) >= 0.12.6 - [terraform-provider-google] plugin 2.1.x -- [terraform-provider-google-beta] plugin 2.1.x \ No newline at end of file +- [terraform-provider-google-beta] plugin 2.1.x + +### Permissions + +- `roles/resourcemanager.organizationAdmin` +- Account running terraform should be a member of group provided in `group_org_admins` variable. + +### Credentials + +If you are not customizing the credentials used in the variable `credentials_file_path`, please ensure you run `gcloud auth application-default login` prior to running your code to ensure the module can retreive credentials. + +For users interested in using service account impersonation which this module helps enable with `sa_enable_impersonation`, please see this [blog post](https://medium.com/google-cloud/terraform-assume-role-and-service-account-impersonation-on-google-cloud-ffc553863e72) which explains how it works. + +### APIs + +A project with the following APIs enabled must be used to host the +resources of this module: + +- Google Cloud Resource Manager API: `cloudresourcemanager.googleapis.com` + +This API can be enabled in the default project created during establishing an organization. + +## Contributing + +Refer to the [contribution guidelines](./CONTRIBUTING.md) for +information on contributing to this module. \ No newline at end of file diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index 915a1ed5..30f16457 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -1,3 +1,7 @@ +## Overview + +This example combines the Organization bootstrap module with the Cloud Build submodule, to setup everything that is required to run subsequent infrastructure as code using cloud native tooling and limited external dependencies. For more details on what the Cloud Build module is doing, see the [readme](../../modules/cloudbuild). + ## Inputs diff --git a/examples/simple/README.md b/examples/simple/README.md index d60f0659..3c278aa2 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -1,3 +1,7 @@ +## Overview + +This example demonstrates the simplest usage of the GCP organization bootstrap module, accepting default values for the module variables. + ## Inputs From 4425fc54ed1d1b07f735b87960b7643d14f654d5 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Tue, 12 Nov 2019 20:47:38 +1100 Subject: [PATCH 06/27] Update documentation --- README.md | 28 ++++++++++++++++++++++++++- examples/cloudbuild_enabled/README.md | 4 ++++ examples/simple/README.md | 4 ++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 476bbecf..a4bf9017 100644 --- a/README.md +++ b/README.md @@ -76,4 +76,30 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). - [gcloud sdk](https://cloud.google.com/sdk/install) >= 206.0.0 - [Terraform](https://www.terraform.io/downloads.html) >= 0.12.6 - [terraform-provider-google] plugin 2.1.x -- [terraform-provider-google-beta] plugin 2.1.x \ No newline at end of file +- [terraform-provider-google-beta] plugin 2.1.x + +### Permissions + +- `roles/resourcemanager.organizationAdmin` on GCP Organization +- `roles/billing.admin` on supplied billing account +- Account running terraform should be a member of group provided in `group_org_admins` variable, to ensure that project creation and other API calls do not fail. + +### Credentials + +If you are not customizing the credentials used in the variable `credentials_file_path`, please ensure you run `gcloud auth application-default login` prior to running your code to ensure the module can retreive credentials. + +For users interested in using service account impersonation which this module helps enable with `sa_enable_impersonation`, please see this [blog post](https://medium.com/google-cloud/terraform-assume-role-and-service-account-impersonation-on-google-cloud-ffc553863e72) which explains how it works. + +### APIs + +A project with the following APIs enabled must be used to host the +resources of this module: + +- Google Cloud Resource Manager API: `cloudresourcemanager.googleapis.com` + +This API can be enabled in the default project created during establishing an organization. + +## Contributing + +Refer to the [contribution guidelines](./CONTRIBUTING.md) for +information on contributing to this module. \ No newline at end of file diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index 915a1ed5..30f16457 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -1,3 +1,7 @@ +## Overview + +This example combines the Organization bootstrap module with the Cloud Build submodule, to setup everything that is required to run subsequent infrastructure as code using cloud native tooling and limited external dependencies. For more details on what the Cloud Build module is doing, see the [readme](../../modules/cloudbuild). + ## Inputs diff --git a/examples/simple/README.md b/examples/simple/README.md index d60f0659..3c278aa2 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -1,3 +1,7 @@ +## Overview + +This example demonstrates the simplest usage of the GCP organization bootstrap module, accepting default values for the module variables. + ## Inputs From b79d8ac3549a0b8709618b16de3e858ddc8393fe Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 13 Nov 2019 13:39:42 +1100 Subject: [PATCH 07/27] Apply suggestions from code review Co-Authored-By: Aaron Lane --- main.tf | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/main.tf b/main.tf index 137ab14a..916df2a2 100644 --- a/main.tf +++ b/main.tf @@ -45,9 +45,10 @@ resource "google_project" "seed_project" { } resource "google_project_service" "seed_project_api" { - count = length(local.activate_apis) + for_each = toset(local.activate_apis) + project = google_project.seed_project.id - service = local.activate_apis[count.index] + service = local.activate_apis[each.value] disable_dependent_services = true } @@ -102,9 +103,10 @@ resource "google_organization_iam_binding" "project_creator" { ***********************************************/ resource "google_organization_iam_member" "org_admins_group" { - count = length(var.org_admins_org_iam_permissions) + for_each = toset(var.org_admins_org_iam_permissions) + org_id = var.organization_id - role = var.org_admins_org_iam_permissions[count.index] + role = var.org_admins_org_iam_permissions[each.value] member = "group:${var.group_org_admins}" } @@ -123,9 +125,10 @@ resource "google_organization_iam_member" "org_billing_admin" { ***********************************************/ resource "google_organization_iam_member" "tf_sa_org_perms" { - count = length(var.sa_org_iam_permissions) +for_each = toset(var.sa_org_iam_permissions) + org_id = var.organization_id - role = var.sa_org_iam_permissions[count.index] + role = var.sa_org_iam_permissions[each.value] member = "serviceAccount:${google_service_account.org_terraform.email}" } @@ -161,4 +164,4 @@ resource "google_organization_iam_member" "org_admin_serviceusage_consumer" { role = "roles/serviceusage.serviceUsageConsumer" member = "group:${var.group_org_admins}" depends_on = [google_project_service.seed_project_api] -} \ No newline at end of file +} From 63e037fb2d6133600a42a69c64a1aaae8ae3d60f Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 13 Nov 2019 13:39:42 +1100 Subject: [PATCH 08/27] Apply suggestions from code review Co-Authored-By: Aaron Lane --- README.md | 3 --- main.tf | 17 ++++++++++------- modules/cloudbuild/README.md | 1 - modules/cloudbuild/variables.tf | 6 ------ modules/cloudbuild/versions.tf | 19 +++++++++++++++++++ variables.tf | 6 +----- versions.tf | 2 +- 7 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 modules/cloudbuild/versions.tf diff --git a/README.md b/README.md index a4bf9017..f385022d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). |------|-------------|:----:|:-----:|:-----:| | activate\_apis | List of APIs to enable in the seed project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | -| credentials\_file\_path | Service account key path with default to Application Default Credentials path | string | `"~/.config/gcloud/application_default_credentials.json"` | no | | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | @@ -86,8 +85,6 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). ### Credentials -If you are not customizing the credentials used in the variable `credentials_file_path`, please ensure you run `gcloud auth application-default login` prior to running your code to ensure the module can retreive credentials. - For users interested in using service account impersonation which this module helps enable with `sa_enable_impersonation`, please see this [blog post](https://medium.com/google-cloud/terraform-assume-role-and-service-account-impersonation-on-google-cloud-ffc553863e72) which explains how it works. ### APIs diff --git a/main.tf b/main.tf index 137ab14a..1462bfea 100644 --- a/main.tf +++ b/main.tf @@ -45,9 +45,10 @@ resource "google_project" "seed_project" { } resource "google_project_service" "seed_project_api" { - count = length(local.activate_apis) + for_each = toset(local.activate_apis) + project = google_project.seed_project.id - service = local.activate_apis[count.index] + service = local.activate_apis[each.value] disable_dependent_services = true } @@ -102,9 +103,10 @@ resource "google_organization_iam_binding" "project_creator" { ***********************************************/ resource "google_organization_iam_member" "org_admins_group" { - count = length(var.org_admins_org_iam_permissions) + for_each = toset(var.org_admins_org_iam_permissions) + org_id = var.organization_id - role = var.org_admins_org_iam_permissions[count.index] + role = var.org_admins_org_iam_permissions[each.value] member = "group:${var.group_org_admins}" } @@ -123,9 +125,10 @@ resource "google_organization_iam_member" "org_billing_admin" { ***********************************************/ resource "google_organization_iam_member" "tf_sa_org_perms" { - count = length(var.sa_org_iam_permissions) + for_each = toset(var.sa_org_iam_permissions) + org_id = var.organization_id - role = var.sa_org_iam_permissions[count.index] + role = var.sa_org_iam_permissions[each.value] member = "serviceAccount:${google_service_account.org_terraform.email}" } @@ -161,4 +164,4 @@ resource "google_organization_iam_member" "org_admin_serviceusage_consumer" { role = "roles/serviceusage.serviceUsageConsumer" member = "group:${var.group_org_admins}" depends_on = [google_project_service.seed_project_api] -} \ No newline at end of file +} diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index 733f84b8..121ede73 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -55,7 +55,6 @@ Functional examples and sample Cloud Build definitions are included in the [exam | activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | | cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `[ "gcp-org", "gcp-networks", "gcp-projects" ]` | no | -| credentials\_file\_path | Service account key path with default to Application Default Credentials path | string | `"~/.config/gcloud/application_default_credentials.json"` | no | | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | organization\_id | GCP Organization ID | string | n/a | yes | diff --git a/modules/cloudbuild/variables.tf b/modules/cloudbuild/variables.tf index c7fc86fc..dff1fc33 100644 --- a/modules/cloudbuild/variables.tf +++ b/modules/cloudbuild/variables.tf @@ -58,12 +58,6 @@ variable "terraform_state_bucket" { Optional variables *******************************************/ -variable "credentials_file_path" { - description = "Service account key path with default to Application Default Credentials path" - type = string - default = "~/.config/gcloud/application_default_credentials.json" -} - variable "project_prefix" { description = "Name prefix to use for projects created." type = string diff --git a/modules/cloudbuild/versions.tf b/modules/cloudbuild/versions.tf new file mode 100644 index 00000000..27ba8fc1 --- /dev/null +++ b/modules/cloudbuild/versions.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = "~> 0.12.6" +} diff --git a/variables.tf b/variables.tf index 1e413afe..5bbcfd45 100644 --- a/variables.tf +++ b/variables.tf @@ -47,14 +47,10 @@ variable "default_region" { Optional variables *******************************************/ -variable "credentials_file_path" { - description = "Service account key path with default to Application Default Credentials path" - default = "~/.config/gcloud/application_default_credentials.json" -} - variable "project_prefix" { description = "Name prefix to use for projects created." default = "cft" + type = string } variable "activate_apis" { diff --git a/versions.tf b/versions.tf index 29704272..27ba8fc1 100644 --- a/versions.tf +++ b/versions.tf @@ -15,5 +15,5 @@ */ terraform { - required_version = ">= 0.12" + required_version = "~> 0.12.6" } From 81b35342b340e75d675ea8110f4dcc8f75a0117e Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Thu, 14 Nov 2019 12:17:08 +1100 Subject: [PATCH 09/27] Add simple test --- .gitignore | 2 + CONTRIBUTING.md | 3 + Makefile | 8 +- README.md | 6 +- examples/simple/README.md | 1 + examples/simple/outputs.tf | 4 + kitchen.yml | 11 +-- modules/cloudbuild/README.md | 4 +- .../{simple_example => simple}/main.tf | 19 ++--- .../{simple_example => simple}/outputs.tf | 18 +++-- .../{simple_example => simple}/variables.tf | 30 ++++++++ .../{simple_example => simple}/versions.tf | 0 test/integration/simple/controls/gcp.rb | 77 +++++++++++++++++++ test/integration/simple/inspec.yml | 18 +++++ .../simple_example/controls/gcloud.rb | 23 ------ .../simple_example/controls/gcp.rb | 21 ----- .../simple_example/controls/gsutil.rb | 23 ------ test/integration/simple_example/inspec.yml | 12 --- test/setup/iam.tf | 22 +++++- test/setup/main.tf | 2 + test/setup/make_source.sh | 11 ++- test/setup/outputs.tf | 24 ++++++ test/setup/variables.tf | 12 +++ 23 files changed, 234 insertions(+), 117 deletions(-) rename test/fixtures/{simple_example => simple}/main.tf (70%) rename test/fixtures/{simple_example => simple}/outputs.tf (67%) rename test/fixtures/{simple_example => simple}/variables.tf (50%) rename test/fixtures/{simple_example => simple}/versions.tf (100%) create mode 100644 test/integration/simple/controls/gcp.rb create mode 100644 test/integration/simple/inspec.yml delete mode 100644 test/integration/simple_example/controls/gcloud.rb delete mode 100644 test/integration/simple_example/controls/gcp.rb delete mode 100644 test/integration/simple_example/controls/gsutil.rb delete mode 100644 test/integration/simple_example/inspec.yml diff --git a/.gitignore b/.gitignore index 9e89203c..92701728 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ override.tf.json *_override.tf *_override.tf.json .idea/ + +setup-tests.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a350db59..5ffae759 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,9 @@ You will also need to set a few environment variables: export TF_VAR_org_id="your_org_id" export TF_VAR_folder_id="your_folder_id" export TF_VAR_billing_account="your_billing_account_id" +export TF_VAR_group_org_admins="gcp-organization-admins@example.com" +export TF_VAR_group_billing_admins="gcp-billing-admins@example.com" +export TF_VAR_default_region="your-default-region1" ``` With these settings in place, you can prepare a test project using Docker: diff --git a/Makefile b/Makefile index 6a107952..c70ce448 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ # Make will use bash instead of sh SHELL := /usr/bin/env bash -DOCKER_TAG_VERSION_DEVELOPER_TOOLS := 0.1.0 +DOCKER_TAG_VERSION_DEVELOPER_TOOLS := 0 DOCKER_IMAGE_DEVELOPER_TOOLS := cft/developer-tools REGISTRY_URL := gcr.io/cloud-foundation-cicd @@ -39,6 +39,9 @@ docker_test_prepare: -e TF_VAR_org_id \ -e TF_VAR_folder_id \ -e TF_VAR_billing_account \ + -e TF_VAR_group_org_admins \ + -e TF_VAR_group_billing_admins \ + -e TF_VAR_default_region \ -v $(CURDIR):/workspace \ $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ /usr/local/bin/execute_with_credentials.sh prepare_environment @@ -51,6 +54,9 @@ docker_test_cleanup: -e TF_VAR_org_id \ -e TF_VAR_folder_id \ -e TF_VAR_billing_account \ + -e TF_VAR_group_org_admins \ + -e TF_VAR_group_billing_admins \ + -e TF_VAR_default_region \ -v $(CURDIR):/workspace \ $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ /usr/local/bin/execute_with_credentials.sh cleanup_environment diff --git a/README.md b/README.md index f385022d..177a9ba9 100644 --- a/README.md +++ b/README.md @@ -46,16 +46,16 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| activate\_apis | List of APIs to enable in the seed project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | +| activate\_apis | List of APIs to enable in the seed project. | list(string) | `` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | -| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator" ]` | no | +| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `` | no | | organization\_id | GCP Organization ID | string | n/a | yes | | project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | | sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | -| sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/compute.networkAdmin", "roles/compute.xpnAdmin", "roles/iam.serviceAccountAdmin", "roles/logging.configWriter", "roles/orgpolicy.policyAdmin", "roles/resourcemanager.folderCreator", "roles/resourcemanager.folderViewer", "roles/resourcemanager.organizationViewer" ]` | no | +| sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `` | no | ## Outputs diff --git a/examples/simple/README.md b/examples/simple/README.md index 3c278aa2..500a0d81 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -20,5 +20,6 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | gcs\_bucket\_tfstate | | | seed\_project\_id | | | terraform\_sa\_email | | +| terraform\_sa\_name | | \ No newline at end of file diff --git a/examples/simple/outputs.tf b/examples/simple/outputs.tf index 63d3a31a..cc4eda46 100644 --- a/examples/simple/outputs.tf +++ b/examples/simple/outputs.tf @@ -23,6 +23,10 @@ output "terraform_sa_email" { value = module.seed_bootstrap.terraform_sa_email } +output "terraform_sa_name" { + value = module.seed_bootstrap.terraform_sa_name +} + output "gcs_bucket_tfstate" { value = module.seed_bootstrap.gcs_bucket_tfstate } \ No newline at end of file diff --git a/kitchen.yml b/kitchen.yml index f3c9c354..b30e97a7 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -26,18 +26,13 @@ platforms: - name: default suites: - - name: simple_example + - name: simple driver: - root_module_directory: test/fixtures/simple_example/ + root_module_directory: test/fixtures/simple/ verifier: color: false systems: - - name: simple_example local - backend: local - controls: - - gcloud - - gsutil - - name: simple_example gcp + - name: simple gcp backend: gcp controls: - gcp diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index 121ede73..3b4a215e 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -52,9 +52,9 @@ Functional examples and sample Cloud Build definitions are included in the [exam | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | +| activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | -| cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `[ "gcp-org", "gcp-networks", "gcp-projects" ]` | no | +| cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `` | no | | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | organization\_id | GCP Organization ID | string | n/a | yes | diff --git a/test/fixtures/simple_example/main.tf b/test/fixtures/simple/main.tf similarity index 70% rename from test/fixtures/simple_example/main.tf rename to test/fixtures/simple/main.tf index 7b799c26..3d4e8b41 100644 --- a/test/fixtures/simple_example/main.tf +++ b/test/fixtures/simple/main.tf @@ -14,19 +14,12 @@ * limitations under the License. */ -provider "random" { - version = "~> 2.0" -} - -resource "random_pet" "main" { - length = 1 - prefix = "simple-example" - separator = "-" -} - module "example" { - source = "../../../examples/simple_example" + source = "../../../examples/simple" - project_id = var.project_id - bucket_name = random_pet.main.id + organization_id = var.org_id + billing_account = var.billing_account + group_org_admins = var.group_org_admins + group_billing_admins = var.group_billing_admins + default_region = var.default_region } diff --git a/test/fixtures/simple_example/outputs.tf b/test/fixtures/simple/outputs.tf similarity index 67% rename from test/fixtures/simple_example/outputs.tf rename to test/fixtures/simple/outputs.tf index 2e413540..9d2ae9cb 100644 --- a/test/fixtures/simple_example/outputs.tf +++ b/test/fixtures/simple/outputs.tf @@ -14,12 +14,18 @@ * limitations under the License. */ -output "bucket_name" { - description = "The name of the bucket." - value = module.example.bucket_name +output "seed_project_id" { + value = module.example.seed_project_id } -output "project_id" { - description = "The ID of the project in which resources are provisioned." - value = var.project_id +output "terraform_sa_email" { + value = module.example.terraform_sa_email +} + +output "terraform_sa_name" { + value = module.example.terraform_sa_name +} + +output "gcs_bucket_tfstate" { + value = module.example.gcs_bucket_tfstate } diff --git a/test/fixtures/simple_example/variables.tf b/test/fixtures/simple/variables.tf similarity index 50% rename from test/fixtures/simple_example/variables.tf rename to test/fixtures/simple/variables.tf index c1a5c773..c6431f1a 100644 --- a/test/fixtures/simple_example/variables.tf +++ b/test/fixtures/simple/variables.tf @@ -18,3 +18,33 @@ variable "project_id" { description = "The ID of the project in which to provision resources." type = string } + +variable "org_id" { + description = "The numeric organization id" + type = string +} + +variable "folder_id" { + description = "The folder to deploy in" + type = string +} + +variable "billing_account" { + description = "The billing account id associated with the project, e.g. XXXXXX-YYYYYY-ZZZZZZ" + type = string +} + +variable "group_org_admins" { + description = "Google Group for GCP Organization Administrators" + type = string +} + +variable "group_billing_admins" { + description = "Google Group for GCP Billing Administrators" + type = string +} + +variable "default_region" { + description = "Default region to create resources where applicable." + type = string +} diff --git a/test/fixtures/simple_example/versions.tf b/test/fixtures/simple/versions.tf similarity index 100% rename from test/fixtures/simple_example/versions.tf rename to test/fixtures/simple/versions.tf diff --git a/test/integration/simple/controls/gcp.rb b/test/integration/simple/controls/gcp.rb new file mode 100644 index 00000000..ea6641b6 --- /dev/null +++ b/test/integration/simple/controls/gcp.rb @@ -0,0 +1,77 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +control "gcp" do + title "GCP Resources" + + describe google_project(project_id: attribute("seed_project_id")) do + it { should exist } + end + + describe google_storage_bucket(name: attribute("gcs_bucket_tfstate")) do + it { should exist } + its('storage_class') { should eq 'STANDARD' } + end + + describe google_service_account(name: attribute("terraform_sa_name")) do + it { should exist } + its('has_user_managed_keys?') {should cmp false } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'servicenetworking.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'compute.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'logging.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'bigquery-json.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'cloudresourcemanager.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'cloudbilling.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'iam.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'admin.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + + describe google_project_service(project: attribute("seed_project_id"), name: 'appengine.googleapis.com') do + it { should exist } + its('state') { should cmp "ENABLED" } + end + +end \ No newline at end of file diff --git a/test/integration/simple/inspec.yml b/test/integration/simple/inspec.yml new file mode 100644 index 00000000..b8ceeff2 --- /dev/null +++ b/test/integration/simple/inspec.yml @@ -0,0 +1,18 @@ +name: simple +depends: + - name: inspec-gcp + git: https://github.com/inspec/inspec-gcp.git + tag: v0.20.0 +attributes: + - name: seed_project_id + required: true + type: string + - name: terraform_sa_email + required: true + type: string + - name: terraform_sa_name + required: true + type: string + - name: gcs_bucket_tfstate + required: true + type: string \ No newline at end of file diff --git a/test/integration/simple_example/controls/gcloud.rb b/test/integration/simple_example/controls/gcloud.rb deleted file mode 100644 index d2a2609c..00000000 --- a/test/integration/simple_example/controls/gcloud.rb +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -control "gcloud" do - title "gcloud" - - describe command("gcloud --project=#{attribute("project_id")} services list --enabled") do - its(:exit_status) { should eq 0 } - its(:stderr) { should eq "" } - its(:stdout) { should match "storage-api.googleapis.com" } - end -end diff --git a/test/integration/simple_example/controls/gcp.rb b/test/integration/simple_example/controls/gcp.rb deleted file mode 100644 index 3b5382bd..00000000 --- a/test/integration/simple_example/controls/gcp.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -control "gcp" do - title "GCP Resources" - - describe google_storage_bucket(name: attribute("bucket_name")) do - it { should exist } - end -end diff --git a/test/integration/simple_example/controls/gsutil.rb b/test/integration/simple_example/controls/gsutil.rb deleted file mode 100644 index 692309f0..00000000 --- a/test/integration/simple_example/controls/gsutil.rb +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -control "gsutil" do - title "gsutil" - - describe command("gsutil ls -p #{attribute("project_id")}") do - its(:exit_status) { should eq 0 } - its(:stderr) { should eq "" } - its(:stdout) { should match "gs://#{attribute("bucket_name")}" } - end -end diff --git a/test/integration/simple_example/inspec.yml b/test/integration/simple_example/inspec.yml deleted file mode 100644 index 2fd718c0..00000000 --- a/test/integration/simple_example/inspec.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: simple_example -depends: - - name: inspec-gcp - git: https://github.com/inspec/inspec-gcp.git - tag: v0.10.0 -attributes: - - name: project_id - required: true - type: string - - name: bucket_name - required: true - type: string diff --git a/test/setup/iam.tf b/test/setup/iam.tf index 9255735b..d8b3b216 100644 --- a/test/setup/iam.tf +++ b/test/setup/iam.tf @@ -18,6 +18,24 @@ locals { int_required_roles = [ "roles/owner" ] + int_org_required_roles = [ + "roles/billing.user", + "roles/resourcemanager.organizationAdmin", + "roles/resourcemanager.projectCreator" + ] +} + +resource "google_organization_iam_member" "org_admins_group" { + for_each = toset(local.int_org_required_roles) + org_id = var.org_id + role = each.value + member = "serviceAccount:${google_service_account.int_test.email}" +} + +resource "google_billing_account_iam_member" "tf_billing_user" { + billing_account_id = var.billing_account + role = "roles/billing.admin" + member = "serviceAccount:${google_service_account.int_test.email}" } resource "google_service_account" "int_test" { @@ -27,10 +45,10 @@ resource "google_service_account" "int_test" { } resource "google_project_iam_member" "int_test" { - count = length(local.int_required_roles) + for_each = toset(local.int_required_roles) project = module.project.project_id - role = local.int_required_roles[count.index] + role = each.value member = "serviceAccount:${google_service_account.int_test.email}" } diff --git a/test/setup/main.tf b/test/setup/main.tf index b47846f0..cfedfd36 100644 --- a/test/setup/main.tf +++ b/test/setup/main.tf @@ -26,6 +26,8 @@ module "project" { activate_apis = [ "cloudresourcemanager.googleapis.com", + "cloudbilling.googleapis.com", + "iam.googleapis.com", "storage-api.googleapis.com", "serviceusage.googleapis.com" ] diff --git a/test/setup/make_source.sh b/test/setup/make_source.sh index ffdc48e1..36a3a87e 100755 --- a/test/setup/make_source.sh +++ b/test/setup/make_source.sh @@ -16,9 +16,14 @@ echo "#!/usr/bin/env bash" > ../source.sh -project_id=$(terraform output project_id) -echo "export TF_VAR_project_id='$project_id'" >> ../source.sh +TF_VARS="project_id org_id folder_id billing_account group_org_admins group_billing_admins default_region" + +for TF_VAR in $TF_VARS +do + TF_VAR_VAL=$(terraform output "${TF_VAR}") + echo "export TF_VAR_${TF_VAR}='$TF_VAR_VAL'" >> ../source.sh +done sa_json=$(terraform output sa_key) # shellcheck disable=SC2086 -echo "export SERVICE_ACCOUNT_JSON='$(echo $sa_json | base64 --decode)'" >> ../source.sh +echo "export SERVICE_ACCOUNT_JSON='$(echo $sa_json | base64 --decode)'" >> ../source.sh \ No newline at end of file diff --git a/test/setup/outputs.tf b/test/setup/outputs.tf index 357bb1e4..c1217dc8 100644 --- a/test/setup/outputs.tf +++ b/test/setup/outputs.tf @@ -22,3 +22,27 @@ output "sa_key" { value = google_service_account_key.int_test.private_key sensitive = true } + +output "org_id" { + value = var.org_id +} + +output "folder_id" { + value = var.folder_id +} + +output "billing_account" { + value = var.billing_account +} + +output "group_org_admins" { + value = var.group_org_admins +} + +output "group_billing_admins" { + value = var.group_billing_admins +} + +output "default_region" { + value = var.default_region +} diff --git a/test/setup/variables.tf b/test/setup/variables.tf index 6d80b898..1e1576f1 100644 --- a/test/setup/variables.tf +++ b/test/setup/variables.tf @@ -24,3 +24,15 @@ variable "folder_id" { variable "billing_account" { description = "The billing account id associated with the project, e.g. XXXXXX-YYYYYY-ZZZZZZ" } + +variable "group_org_admins" { + description = "Google Group for GCP Organization Administrators" +} + +variable "group_billing_admins" { + description = "Google Group for GCP Billing Administrators" +} + +variable "default_region" { + description = "Default region to create resources where applicable." +} From f8324247701c6b1afcbce0054a4fd19190a9ad3d Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Thu, 14 Nov 2019 12:47:00 +1100 Subject: [PATCH 10/27] Adding additional required APIS and updating docs --- README.md | 10 +++++++--- modules/cloudbuild/README.md | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 177a9ba9..40521200 100644 --- a/README.md +++ b/README.md @@ -46,16 +46,16 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| activate\_apis | List of APIs to enable in the seed project. | list(string) | `` | no | +| activate\_apis | List of APIs to enable in the seed project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | -| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `` | no | +| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator" ]` | no | | organization\_id | GCP Organization ID | string | n/a | yes | | project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | | sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | -| sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `` | no | +| sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/compute.networkAdmin", "roles/compute.xpnAdmin", "roles/iam.serviceAccountAdmin", "roles/logging.configWriter", "roles/orgpolicy.policyAdmin", "roles/resourcemanager.folderCreator", "roles/resourcemanager.folderViewer", "roles/resourcemanager.organizationViewer" ]` | no | ## Outputs @@ -93,6 +93,10 @@ A project with the following APIs enabled must be used to host the resources of this module: - Google Cloud Resource Manager API: `cloudresourcemanager.googleapis.com` +- Google Cloud Billing API: `cloudbilling.googleapis.com` +- Google Cloud IAM API: `iam.googleapis.com` +- Google Cloud Storage API `storage-api.googleapis.com` +- Google Cloud Service Usage API: `serviceusage.googleapis.com` This API can be enabled in the default project created during establishing an organization. diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index 3b4a215e..121ede73 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -52,9 +52,9 @@ Functional examples and sample Cloud Build definitions are included in the [exam | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `` | no | +| activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | -| cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `` | no | +| cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `[ "gcp-org", "gcp-networks", "gcp-projects" ]` | no | | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | organization\_id | GCP Organization ID | string | n/a | yes | From fa8ef8cf2ca73221581e3ddefeb7e39165895603 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Thu, 14 Nov 2019 15:57:46 +1100 Subject: [PATCH 11/27] Add tests for cloudbuild module --- kitchen.yml | 13 ++- test/fixtures/cloudbuild_enabled/main.tf | 25 ++++++ test/fixtures/cloudbuild_enabled/outputs.tf | 51 ++++++++++++ test/fixtures/cloudbuild_enabled/variables.tf | 50 ++++++++++++ test/fixtures/cloudbuild_enabled/versions.tf | 19 +++++ test/fixtures/simple/main.tf | 2 +- test/fixtures/simple/outputs.tf | 8 +- .../cloudbuild_enabled/controls/gcp.rb | 79 +++++++++++++++++++ .../integration/cloudbuild_enabled/inspec.yml | 33 ++++++++ test/integration/simple/controls/gcp.rb | 65 +++++---------- test/setup/main.tf | 5 +- 11 files changed, 297 insertions(+), 53 deletions(-) create mode 100644 test/fixtures/cloudbuild_enabled/main.tf create mode 100644 test/fixtures/cloudbuild_enabled/outputs.tf create mode 100644 test/fixtures/cloudbuild_enabled/variables.tf create mode 100644 test/fixtures/cloudbuild_enabled/versions.tf create mode 100644 test/integration/cloudbuild_enabled/controls/gcp.rb create mode 100644 test/integration/cloudbuild_enabled/inspec.yml diff --git a/kitchen.yml b/kitchen.yml index b30e97a7..4bf92216 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -35,4 +35,15 @@ suites: - name: simple gcp backend: gcp controls: - - gcp + - bootstrap + - name: cloudbuild_enabled + driver: + root_module_directory: test/fixtures/cloudbuild_enabled/ + verifier: + color: false + systems: + - name: cloudbuild_enabled gcp + backend: gcp + controls: + - bootstrap + - cloudbuild diff --git a/test/fixtures/cloudbuild_enabled/main.tf b/test/fixtures/cloudbuild_enabled/main.tf new file mode 100644 index 00000000..3ac24154 --- /dev/null +++ b/test/fixtures/cloudbuild_enabled/main.tf @@ -0,0 +1,25 @@ +/** + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module "cloudbuild_enabled" { + source = "../../../examples/cloudbuild_enabled" + + organization_id = var.org_id + billing_account = var.billing_account + group_org_admins = var.group_org_admins + group_billing_admins = var.group_billing_admins + default_region = var.default_region +} diff --git a/test/fixtures/cloudbuild_enabled/outputs.tf b/test/fixtures/cloudbuild_enabled/outputs.tf new file mode 100644 index 00000000..2c9f86ef --- /dev/null +++ b/test/fixtures/cloudbuild_enabled/outputs.tf @@ -0,0 +1,51 @@ +/** + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "seed_project_id" { + value = module.cloudbuild_enabled.seed_project_id +} + +output "terraform_sa_email" { + value = module.cloudbuild_enabled.terraform_sa_email +} + +output "terraform_sa_name" { + value = module.cloudbuild_enabled.terraform_sa_name +} + +output "gcs_bucket_tfstate" { + value = module.cloudbuild_enabled.gcs_bucket_tfstate +} + +output "cloudbuild_project_id" { + value = module.cloudbuild_enabled.cloudbuild_project_id +} + +output "gcs_bucket_cloudbuild_artifacts" { + value = module.cloudbuild_enabled.gcs_bucket_cloudbuild_artifacts +} + +output "csr_repos" { + value = module.cloudbuild_enabled.csr_repos +} + +output "kms_keyring" { + value = module.cloudbuild_enabled.kms_keyring +} + +output "kms_crypto_key" { + value = module.cloudbuild_enabled.kms_crypto_key +} \ No newline at end of file diff --git a/test/fixtures/cloudbuild_enabled/variables.tf b/test/fixtures/cloudbuild_enabled/variables.tf new file mode 100644 index 00000000..c6431f1a --- /dev/null +++ b/test/fixtures/cloudbuild_enabled/variables.tf @@ -0,0 +1,50 @@ +/** + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The ID of the project in which to provision resources." + type = string +} + +variable "org_id" { + description = "The numeric organization id" + type = string +} + +variable "folder_id" { + description = "The folder to deploy in" + type = string +} + +variable "billing_account" { + description = "The billing account id associated with the project, e.g. XXXXXX-YYYYYY-ZZZZZZ" + type = string +} + +variable "group_org_admins" { + description = "Google Group for GCP Organization Administrators" + type = string +} + +variable "group_billing_admins" { + description = "Google Group for GCP Billing Administrators" + type = string +} + +variable "default_region" { + description = "Default region to create resources where applicable." + type = string +} diff --git a/test/fixtures/cloudbuild_enabled/versions.tf b/test/fixtures/cloudbuild_enabled/versions.tf new file mode 100644 index 00000000..1a9363a3 --- /dev/null +++ b/test/fixtures/cloudbuild_enabled/versions.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = "~> 0.12.6" +} diff --git a/test/fixtures/simple/main.tf b/test/fixtures/simple/main.tf index 3d4e8b41..2d242d66 100644 --- a/test/fixtures/simple/main.tf +++ b/test/fixtures/simple/main.tf @@ -14,7 +14,7 @@ * limitations under the License. */ -module "example" { +module "simple" { source = "../../../examples/simple" organization_id = var.org_id diff --git a/test/fixtures/simple/outputs.tf b/test/fixtures/simple/outputs.tf index 9d2ae9cb..bee7de1a 100644 --- a/test/fixtures/simple/outputs.tf +++ b/test/fixtures/simple/outputs.tf @@ -15,17 +15,17 @@ */ output "seed_project_id" { - value = module.example.seed_project_id + value = module.simple.seed_project_id } output "terraform_sa_email" { - value = module.example.terraform_sa_email + value = module.simple.terraform_sa_email } output "terraform_sa_name" { - value = module.example.terraform_sa_name + value = module.simple.terraform_sa_name } output "gcs_bucket_tfstate" { - value = module.example.gcs_bucket_tfstate + value = module.simple.gcs_bucket_tfstate } diff --git a/test/integration/cloudbuild_enabled/controls/gcp.rb b/test/integration/cloudbuild_enabled/controls/gcp.rb new file mode 100644 index 00000000..ecf1ba6b --- /dev/null +++ b/test/integration/cloudbuild_enabled/controls/gcp.rb @@ -0,0 +1,79 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +default_apis = [ + "servicenetworking.googleapis.com", + "compute.googleapis.com", + "logging.googleapis.com", + "bigquery-json.googleapis.com", + "cloudresourcemanager.googleapis.com", + "cloudbilling.googleapis.com", + "iam.googleapis.com", + "admin.googleapis.com", + "appengine.googleapis.com" +] + +cloudbuild_apis = ["cloudbuild.googleapis.com", "sourcerepo.googleapis.com", "cloudkms.googleapis.com"] + +control "bootstrap" do + title "Bootstrap module GCP resources" + + describe google_project(project_id: attribute("seed_project_id")) do + it { should exist } + end + + describe google_storage_bucket(name: attribute("gcs_bucket_tfstate")) do + it { should exist } + end + + describe google_service_account(name: attribute("terraform_sa_name")) do + it { should exist } + its('has_user_managed_keys?') {should cmp false } + end + + default_apis.each do |api| + describe google_project_service(project: attribute("seed_project_id"), name: api) do + it { should exist } + its('state') { should cmp "ENABLED" } + end + end + +end + +control "cloudbuild" do + title "Cloudbuild sub-module GCP Resources" + + describe google_project(project_id: attribute("cloudbuild_project_id")) do + it { should exist } + end + + describe google_storage_bucket(name: attribute("gcs_bucket_cloudbuild_artifacts")) do + it { should exist } + end + + default_apis.each do |api| + describe google_project_service(project: attribute("cloudbuild_project_id"), name: api) do + it { should exist } + its('state') { should cmp "ENABLED" } + end + end + + cloudbuild_apis.each do |api| + describe google_project_service(project: attribute("cloudbuild_project_id"), name: api) do + it { should exist } + its('state') { should cmp "ENABLED" } + end + end + +end \ No newline at end of file diff --git a/test/integration/cloudbuild_enabled/inspec.yml b/test/integration/cloudbuild_enabled/inspec.yml new file mode 100644 index 00000000..d939486a --- /dev/null +++ b/test/integration/cloudbuild_enabled/inspec.yml @@ -0,0 +1,33 @@ +name: simple +depends: + - name: inspec-gcp + git: https://github.com/inspec/inspec-gcp.git + tag: v0.20.0 +attributes: + - name: seed_project_id + required: true + type: string + - name: terraform_sa_email + required: true + type: string + - name: terraform_sa_name + required: true + type: string + - name: gcs_bucket_tfstate + required: true + type: string + - name: cloudbuild_project_id + required: true + type: string + - name: gcs_bucket_cloudbuild_artifacts + required: true + type: string + # - name: csr_repos + # required: true + # type: string + # - name: kms_keyring + # required: true + # type: string + # - name: kms_crypto_key + # required: true + # type: string diff --git a/test/integration/simple/controls/gcp.rb b/test/integration/simple/controls/gcp.rb index ea6641b6..4fc09905 100644 --- a/test/integration/simple/controls/gcp.rb +++ b/test/integration/simple/controls/gcp.rb @@ -12,8 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -control "gcp" do - title "GCP Resources" +default_apis = [ + "servicenetworking.googleapis.com", + "compute.googleapis.com", + "logging.googleapis.com", + "bigquery-json.googleapis.com", + "cloudresourcemanager.googleapis.com", + "cloudbilling.googleapis.com", + "iam.googleapis.com", + "admin.googleapis.com", + "appengine.googleapis.com" +] + +control "bootstrap" do + title "Bootstrap module GCP resources" describe google_project(project_id: attribute("seed_project_id")) do it { should exist } @@ -21,7 +33,6 @@ describe google_storage_bucket(name: attribute("gcs_bucket_tfstate")) do it { should exist } - its('storage_class') { should eq 'STANDARD' } end describe google_service_account(name: attribute("terraform_sa_name")) do @@ -29,49 +40,11 @@ its('has_user_managed_keys?') {should cmp false } end - describe google_project_service(project: attribute("seed_project_id"), name: 'servicenetworking.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } - end - - describe google_project_service(project: attribute("seed_project_id"), name: 'compute.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } - end - - describe google_project_service(project: attribute("seed_project_id"), name: 'logging.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } - end - - describe google_project_service(project: attribute("seed_project_id"), name: 'bigquery-json.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } - end - - describe google_project_service(project: attribute("seed_project_id"), name: 'cloudresourcemanager.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } - end - - describe google_project_service(project: attribute("seed_project_id"), name: 'cloudbilling.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } - end - - describe google_project_service(project: attribute("seed_project_id"), name: 'iam.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } - end - - describe google_project_service(project: attribute("seed_project_id"), name: 'admin.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } - end - - describe google_project_service(project: attribute("seed_project_id"), name: 'appengine.googleapis.com') do - it { should exist } - its('state') { should cmp "ENABLED" } + default_apis.each do |api| + describe google_project_service(project: attribute("seed_project_id"), name: api) do + it { should exist } + its('state') { should cmp "ENABLED" } + end end end \ No newline at end of file diff --git a/test/setup/main.tf b/test/setup/main.tf index cfedfd36..3601d626 100644 --- a/test/setup/main.tf +++ b/test/setup/main.tf @@ -29,6 +29,9 @@ module "project" { "cloudbilling.googleapis.com", "iam.googleapis.com", "storage-api.googleapis.com", - "serviceusage.googleapis.com" + "serviceusage.googleapis.com", + "cloudbuild.googleapis.com", + "sourcerepo.googleapis.com", + "cloudkms.googleapis.com" ] } From ea699bafda0325a5660de8c6be4e77e484308d82 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Thu, 14 Nov 2019 18:45:09 +1100 Subject: [PATCH 12/27] Use project factory module and add support for parent folder --- README.md | 5 +- examples/cloudbuild_enabled/README.md | 1 + examples/cloudbuild_enabled/main.tf | 1 + examples/cloudbuild_enabled/variables.tf | 8 +- examples/simple/README.md | 1 + examples/simple/main.tf | 1 + examples/simple/variables.tf | 6 ++ main.tf | 44 ++++------ modules/cloudbuild/README.md | 1 + modules/cloudbuild/main.tf | 103 +++++++++-------------- modules/cloudbuild/outputs.tf | 2 +- modules/cloudbuild/variables.tf | 6 ++ outputs.tf | 2 +- test/setup/make_source.sh | 2 +- test/setup/outputs.tf | 4 + variables.tf | 14 ++- 16 files changed, 103 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 40521200..e2ab9873 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,11 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). | activate\_apis | List of APIs to enable in the seed project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | | default\_region | Default region to create resources where applicable. | string | n/a | yes | +| folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator" ]` | no | +| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `[]` | no | | organization\_id | GCP Organization ID | string | n/a | yes | | project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | | sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | @@ -80,8 +82,9 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). ### Permissions - `roles/resourcemanager.organizationAdmin` on GCP Organization +- `roles/resourcemanager.projectCreator` on GCP Organization - `roles/billing.admin` on supplied billing account -- Account running terraform should be a member of group provided in `group_org_admins` variable, to ensure that project creation and other API calls do not fail. +- Account running terraform should be a member of group provided in `group_org_admins` variable, otherwise they will loose `roles/resourcemanager.projectCreator` access. Additional members can be added by using the `org_project_creators` variable. ### Credentials diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index 30f16457..7d5ed375 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -11,6 +11,7 @@ This example combines the Organization bootstrap module with the Cloud Build sub | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `[]` | no | | organization\_id | GCP Organization ID | string | n/a | yes | ## Outputs diff --git a/examples/cloudbuild_enabled/main.tf b/examples/cloudbuild_enabled/main.tf index 736c3809..ea4e9323 100644 --- a/examples/cloudbuild_enabled/main.tf +++ b/examples/cloudbuild_enabled/main.tf @@ -26,6 +26,7 @@ module "seed_bootstrap" { group_org_admins = var.group_org_admins group_billing_admins = var.group_billing_admins default_region = var.default_region + org_project_creators = var.org_project_creators sa_enable_impersonation = true } diff --git a/examples/cloudbuild_enabled/variables.tf b/examples/cloudbuild_enabled/variables.tf index 179f2c41..280e8e41 100644 --- a/examples/cloudbuild_enabled/variables.tf +++ b/examples/cloudbuild_enabled/variables.tf @@ -16,4 +16,10 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." -} \ No newline at end of file +} + +variable "org_project_creators" { + description = "Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required." + type = list(string) + default = [] +} diff --git a/examples/simple/README.md b/examples/simple/README.md index 500a0d81..1f0cac9a 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -11,6 +11,7 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `[]` | no | | organization\_id | GCP Organization ID | string | n/a | yes | ## Outputs diff --git a/examples/simple/main.tf b/examples/simple/main.tf index a0835714..b9a96e54 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -26,4 +26,5 @@ module "seed_bootstrap" { group_org_admins = var.group_org_admins group_billing_admins = var.group_billing_admins default_region = var.default_region + org_project_creators = var.org_project_creators } \ No newline at end of file diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf index 179f2c41..32700fd0 100644 --- a/examples/simple/variables.tf +++ b/examples/simple/variables.tf @@ -16,4 +16,10 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." +} + +variable "org_project_creators" { + description = "Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required." + type = list(string) + default = [] } \ No newline at end of file diff --git a/main.tf b/main.tf index 0fb0b84a..0a0d84a5 100644 --- a/main.tf +++ b/main.tf @@ -15,10 +15,11 @@ */ locals { - seed_project_id = format("%s-%s-%s", var.project_prefix, "seed", random_id.suffix.hex) + seed_project_id = format("%s-%s", var.project_prefix, "seed") impersonation_apis = distinct(concat(var.activate_apis, ["serviceusage.googleapis.com", "iamcredentials.googleapis.com"])) impersonation_enabled_count = var.sa_enable_impersonation == true ? 1 : 0 activate_apis = var.sa_enable_impersonation == true ? local.impersonation_apis : var.activate_apis + org_project_creators = distinct(concat(var.org_project_creators, ["serviceAccount:${google_service_account.org_terraform.email}", "group:${var.group_org_admins}"])) } resource "random_id" "suffix" { @@ -33,22 +34,15 @@ data "google_organization" "org" { Create IaC Project *******************************************/ -resource "google_project" "seed_project" { - name = local.seed_project_id - project_id = local.seed_project_id - org_id = var.organization_id - billing_account = var.billing_account - auto_create_network = false - depends_on = [ - google_organization_iam_member.org_admins_group, - ] -} - -resource "google_project_service" "seed_project_api" { - for_each = toset(local.activate_apis) - project = google_project.seed_project.id - service = each.value - disable_dependent_services = true +module "seed_project" { + source = "terraform-google-modules/project-factory/google" + version = "~> 5.0" + name = local.seed_project_id + random_project_id = "true" + folder_id = var.folder_id + org_id = var.organization_id + billing_account = var.billing_account + activate_apis = local.activate_apis } /****************************************** @@ -56,7 +50,7 @@ resource "google_project_service" "seed_project_api" { *******************************************/ resource "google_service_account" "org_terraform" { - project = google_project.seed_project.id + project = module.seed_project.project_id account_id = "org-terraform" display_name = "CFT Organization Terraform Account" } @@ -66,7 +60,7 @@ resource "google_service_account" "org_terraform" { ***********************************************/ resource "google_storage_bucket" "org_terraform_state" { - project = google_project.seed_project.id + project = module.seed_project.project_id name = format("%s-%s-%s", var.project_prefix, "tfstate", random_id.suffix.hex) location = var.default_region force_destroy = true @@ -91,10 +85,7 @@ resource "google_organization_iam_binding" "project_creator" { org_id = var.organization_id role = "roles/resourcemanager.projectCreator" - members = [ - "serviceAccount:${google_service_account.org_terraform.email}", - "group:${var.group_org_admins}" - ] + members = local.org_project_creators } /*********************************************** @@ -158,8 +149,7 @@ resource "google_service_account_iam_member" "org_admin_sa_impersonate_permissio resource "google_organization_iam_member" "org_admin_serviceusage_consumer" { count = local.impersonation_enabled_count - org_id = var.organization_id - role = "roles/serviceusage.serviceUsageConsumer" - member = "group:${var.group_org_admins}" - depends_on = [google_project_service.seed_project_api] + org_id = var.organization_id + role = "roles/serviceusage.serviceUsageConsumer" + member = "group:${var.group_org_admins}" } diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index 121ede73..a70b9c49 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -56,6 +56,7 @@ Functional examples and sample Cloud Build definitions are included in the [exam | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | | cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `[ "gcp-org", "gcp-networks", "gcp-projects" ]` | no | | default\_region | Default region to create resources where applicable. | string | n/a | yes | +| folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | organization\_id | GCP Organization ID | string | n/a | yes | | project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | diff --git a/modules/cloudbuild/main.tf b/modules/cloudbuild/main.tf index a7fc4c71..9a6ba455 100644 --- a/modules/cloudbuild/main.tf +++ b/modules/cloudbuild/main.tf @@ -15,7 +15,7 @@ */ locals { - cloudbuild_project_id = format("%s-%s-%s", var.project_prefix, "cloudbuild", random_id.suffix.hex) + cloudbuild_project_id = format("%s-%s", var.project_prefix, "cloudbuild") cloudbuild_apis = ["cloudbuild.googleapis.com", "sourcerepo.googleapis.com", "cloudkms.googleapis.com"] impersonation_enabled_count = var.sa_enable_impersonation == true ? 1 : 0 activate_apis = distinct(concat(var.activate_apis, local.cloudbuild_apis)) @@ -35,23 +35,15 @@ data "google_organization" "org" { Cloudbuild project *******************************************/ -resource "google_project" "cloudbuild_project" { - name = local.cloudbuild_project_id - project_id = local.cloudbuild_project_id - org_id = var.organization_id - billing_account = var.billing_account - auto_create_network = false -} - -/****************************************** - Cloudbuild APIs -*******************************************/ - -resource "google_project_service" "cloudbuild_project_api" { - for_each = toset(local.activate_apis) - project = google_project.cloudbuild_project.id - service = each.value - disable_dependent_services = true +module "cloudbuild_project" { + source = "terraform-google-modules/project-factory/google" + version = "~> 5.0" + name = local.cloudbuild_project_id + random_project_id = "true" + folder_id = var.folder_id + org_id = var.organization_id + billing_account = var.billing_account + activate_apis = local.activate_apis } /****************************************** @@ -59,13 +51,13 @@ resource "google_project_service" "cloudbuild_project_api" { *******************************************/ resource "google_project_iam_member" "org_admins_cloudbuild_editor" { - project = google_project.cloudbuild_project.id + project = module.cloudbuild_project.project_id role = "roles/cloudbuild.builds.editor" member = "group:${var.group_org_admins}" } resource "google_project_iam_member" "org_admins_cloudbuild_viewer" { - project = google_project.cloudbuild_project.id + project = module.cloudbuild_project.project_id role = "roles/viewer" member = "group:${var.group_org_admins}" } @@ -75,7 +67,7 @@ resource "google_project_iam_member" "org_admins_cloudbuild_viewer" { *******************************************/ resource "google_storage_bucket" "cloudbuild_artifacts" { - project = google_project.cloudbuild_project.id + project = module.cloudbuild_project.project_id name = format("%s-%s-%s", var.project_prefix, "cloudbuild-artifacts", random_id.suffix.hex) location = var.default_region force_destroy = true @@ -86,12 +78,9 @@ resource "google_storage_bucket" "cloudbuild_artifacts" { *****************************************/ resource "google_kms_key_ring" "tf_keyring" { - project = google_project.cloudbuild_project.id + project = module.cloudbuild_project.project_id name = "tf-keyring" location = var.default_region - depends_on = [ - google_project_service.cloudbuild_project_api - ] } /****************************************** @@ -112,7 +101,7 @@ resource "google_kms_crypto_key_iam_binding" "cloudbuild_crypto_key_decrypter" { role = "roles/cloudkms.cryptoKeyDecrypter" members = [ - "serviceAccount:${google_project.cloudbuild_project.number}@cloudbuild.gserviceaccount.com", + "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com", "serviceAccount:${var.terraform_sa_email}" ] } @@ -136,11 +125,8 @@ resource "google_kms_crypto_key_iam_binding" "cloud_build_crypto_key_encrypter" resource "google_sourcerepo_repository" "gcp_repo" { for_each = toset(var.cloud_source_repos) - project = google_project.cloudbuild_project.id + project = module.cloudbuild_project.project_id name = each.value - depends_on = [ - google_project_service.cloudbuild_project_api - ] } /****************************************** @@ -148,7 +134,7 @@ resource "google_sourcerepo_repository" "gcp_repo" { *******************************************/ resource "google_project_iam_member" "org_admins_source_repo_admin" { - project = google_project.cloudbuild_project.id + project = module.cloudbuild_project.project_id role = "roles/source.admin" member = "group:${var.group_org_admins}" } @@ -159,7 +145,7 @@ resource "google_project_iam_member" "org_admins_source_repo_admin" { resource "google_cloudbuild_trigger" "master_trigger" { for_each = toset(var.cloud_source_repos) - project = google_project.cloudbuild_project.id + project = module.cloudbuild_project.project_id description = "${each.value} - terraform apply on push to master." trigger_template { @@ -174,7 +160,7 @@ resource "google_cloudbuild_trigger" "master_trigger" { _TF_SA_EMAIL = var.terraform_sa_email _STATE_BUCKET_NAME = var.terraform_state_bucket _ARTIFACT_BUCKET_NAME = google_storage_bucket.cloudbuild_artifacts.name - _SEED_PROJECT_ID = google_project.cloudbuild_project.id + _SEED_PROJECT_ID = module.cloudbuild_project.project_id } filename = "cloudbuild-tf-apply.yaml" @@ -189,7 +175,7 @@ resource "google_cloudbuild_trigger" "master_trigger" { resource "google_cloudbuild_trigger" "non_master_trigger" { for_each = toset(var.cloud_source_repos) - project = google_project.cloudbuild_project.id + project = module.cloudbuild_project.project_id description = "${each.value} - terraform plan on all branches except master." trigger_template { @@ -204,7 +190,7 @@ resource "google_cloudbuild_trigger" "non_master_trigger" { _TF_SA_EMAIL = var.terraform_sa_email _STATE_BUCKET_NAME = var.terraform_state_bucket _ARTIFACT_BUCKET_NAME = google_storage_bucket.cloudbuild_artifacts.name - _SEED_PROJECT_ID = google_project.cloudbuild_project.id + _SEED_PROJECT_ID = module.cloudbuild_project.project_id } filename = "cloudbuild-tf-plan.yaml" @@ -219,58 +205,45 @@ resource "google_cloudbuild_trigger" "non_master_trigger" { resource "null_resource" "cloudbuild_terraform_builder" { triggers = { - project_id_seed_project = google_project.cloudbuild_project.id + project_id_seed_project = module.cloudbuild_project.project_id } provisioner "local-exec" { - command = "gcloud builds submit ${path.module}/cloudbuild_builder/ --project ${google_project.cloudbuild_project.id} --config=${path.module}/cloudbuild_builder/cloudbuild.yaml" + command = "gcloud builds submit ${path.module}/cloudbuild_builder/ --project ${module.cloudbuild_project.project_id} --config=${path.module}/cloudbuild_builder/cloudbuild.yaml" } - - depends_on = [ - google_project_service.cloudbuild_project_api - ] } /*********************************************** Cloud Build - IAM ***********************************************/ +resource "google_storage_bucket_iam_member" "cloudbuild_artifacts_iam" { + bucket = google_storage_bucket.cloudbuild_artifacts.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" +} + resource "google_service_account_iam_member" "cloudbuild_terraform_sa_impersonate_permissions" { - count = local.impersonation_enabled_count + count = local.impersonation_enabled_count + service_account_id = var.terraform_sa_name role = "roles/iam.serviceAccountTokenCreator" - member = "serviceAccount:${google_project.cloudbuild_project.number}@cloudbuild.gserviceaccount.com" - depends_on = [ - google_project_service.cloudbuild_project_api - ] + member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" } resource "google_organization_iam_member" "cloudbuild_serviceusage_consumer" { - count = local.impersonation_enabled_count + count = local.impersonation_enabled_count + org_id = var.organization_id role = "roles/serviceusage.serviceUsageConsumer" - member = "serviceAccount:${google_project.cloudbuild_project.number}@cloudbuild.gserviceaccount.com" - depends_on = [ - google_project_service.cloudbuild_project_api - ] -} - -resource "google_storage_bucket_iam_member" "cloudbuild_artifacts_iam" { - bucket = google_storage_bucket.cloudbuild_artifacts.name - role = "roles/storage.objectAdmin" - member = "serviceAccount:${google_project.cloudbuild_project.number}@cloudbuild.gserviceaccount.com" - depends_on = [ - google_project_service.cloudbuild_project_api - ] + member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" } # Required to allow cloud build to access state with impersonation. resource "google_storage_bucket_iam_member" "cloudbuild_state_iam" { - count = local.impersonation_enabled_count + count = local.impersonation_enabled_count + bucket = var.terraform_state_bucket role = "roles/storage.objectAdmin" - member = "serviceAccount:${google_project.cloudbuild_project.number}@cloudbuild.gserviceaccount.com" - depends_on = [ - google_project_service.cloudbuild_project_api - ] + member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" } \ No newline at end of file diff --git a/modules/cloudbuild/outputs.tf b/modules/cloudbuild/outputs.tf index 41d2c0a1..07f8a2d3 100644 --- a/modules/cloudbuild/outputs.tf +++ b/modules/cloudbuild/outputs.tf @@ -16,7 +16,7 @@ output "cloudbuild_project_id" { description = "Project where CloudBuild configuration and terraform container image will reside." - value = google_project.cloudbuild_project.project_id + value = module.cloudbuild_project.project_id } output "gcs_bucket_cloudbuild_artifacts" { diff --git a/modules/cloudbuild/variables.tf b/modules/cloudbuild/variables.tf index dff1fc33..72aff8d9 100644 --- a/modules/cloudbuild/variables.tf +++ b/modules/cloudbuild/variables.tf @@ -96,4 +96,10 @@ variable "cloud_source_repos" { "gcp-networks", "gcp-projects", ] +} + +variable "folder_id" { + description = "The ID of a folder to host this project" + type = string + default = "" } \ No newline at end of file diff --git a/outputs.tf b/outputs.tf index 2560f91a..186cd041 100644 --- a/outputs.tf +++ b/outputs.tf @@ -20,7 +20,7 @@ output "seed_project_id" { description = "Project where service accounts and core APIs will be enabled." - value = google_project.seed_project.project_id + value = module.seed_project.project_id } /****************************************** diff --git a/test/setup/make_source.sh b/test/setup/make_source.sh index 36a3a87e..be0e4d85 100755 --- a/test/setup/make_source.sh +++ b/test/setup/make_source.sh @@ -16,7 +16,7 @@ echo "#!/usr/bin/env bash" > ../source.sh -TF_VARS="project_id org_id folder_id billing_account group_org_admins group_billing_admins default_region" +TF_VARS="project_id org_id folder_id billing_account group_org_admins group_billing_admins default_region org_project_creators" for TF_VAR in $TF_VARS do diff --git a/test/setup/outputs.tf b/test/setup/outputs.tf index c1217dc8..f7286275 100644 --- a/test/setup/outputs.tf +++ b/test/setup/outputs.tf @@ -46,3 +46,7 @@ output "group_billing_admins" { output "default_region" { value = var.default_region } + +output "org_project_creators" { + value = ["serviceAccount:${google_service_account.int_test.email}"] +} diff --git a/variables.tf b/variables.tf index 5bbcfd45..c4566bc5 100644 --- a/variables.tf +++ b/variables.tf @@ -100,4 +100,16 @@ variable "org_admins_org_iam_permissions" { "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator" ] -} \ No newline at end of file +} + +variable "folder_id" { + description = "The ID of a folder to host this project" + type = string + default = "" +} + +variable "org_project_creators" { + description = "Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required." + type = list(string) + default = [] +} From 88b8e0171a55c5ab15a6c4ef631ed928d80bb5c7 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Fri, 15 Nov 2019 12:33:22 +1100 Subject: [PATCH 13/27] Adressing pull request feedback --- README.md | 4 ++-- main.tf | 6 ++---- .../cloudbuild_builder/{README.markdown => README.md} | 0 modules/cloudbuild/main.tf | 1 - test/fixtures/cloudbuild_enabled/variables.tf | 6 ++++++ test/fixtures/simple/variables.tf | 6 ++++++ test/setup/main.tf | 2 +- variables.tf | 3 +-- 8 files changed, 18 insertions(+), 10 deletions(-) rename modules/cloudbuild/cloudbuild_builder/{README.markdown => README.md} (100%) diff --git a/README.md b/README.md index e2ab9873..f9880a86 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). | folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | -| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator" ]` | no | +| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/resourcemanager.organizationAdmin" ]` | no | | org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `[]` | no | | organization\_id | GCP Organization ID | string | n/a | yes | | project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | @@ -82,8 +82,8 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). ### Permissions - `roles/resourcemanager.organizationAdmin` on GCP Organization -- `roles/resourcemanager.projectCreator` on GCP Organization - `roles/billing.admin` on supplied billing account +- `roles/resourcemanager.projectCreator` on GCP Organization for `group_org_admins` group. - Account running terraform should be a member of group provided in `group_org_admins` variable, otherwise they will loose `roles/resourcemanager.projectCreator` access. Additional members can be added by using the `org_project_creators` variable. ### Credentials diff --git a/main.tf b/main.tf index 0a0d84a5..8a27c051 100644 --- a/main.tf +++ b/main.tf @@ -75,16 +75,14 @@ resource "google_storage_bucket" "org_terraform_state" { resource "google_organization_iam_binding" "billing_creator" { org_id = var.organization_id role = "roles/billing.creator" - members = [ "group:${var.group_billing_admins}", ] } resource "google_organization_iam_binding" "project_creator" { - org_id = var.organization_id - role = "roles/resourcemanager.projectCreator" - + org_id = var.organization_id + role = "roles/resourcemanager.projectCreator" members = local.org_project_creators } diff --git a/modules/cloudbuild/cloudbuild_builder/README.markdown b/modules/cloudbuild/cloudbuild_builder/README.md similarity index 100% rename from modules/cloudbuild/cloudbuild_builder/README.markdown rename to modules/cloudbuild/cloudbuild_builder/README.md diff --git a/modules/cloudbuild/main.tf b/modules/cloudbuild/main.tf index 9a6ba455..236ff160 100644 --- a/modules/cloudbuild/main.tf +++ b/modules/cloudbuild/main.tf @@ -19,7 +19,6 @@ locals { cloudbuild_apis = ["cloudbuild.googleapis.com", "sourcerepo.googleapis.com", "cloudkms.googleapis.com"] impersonation_enabled_count = var.sa_enable_impersonation == true ? 1 : 0 activate_apis = distinct(concat(var.activate_apis, local.cloudbuild_apis)) - csr_repos = var.cloud_source_repos } resource "random_id" "suffix" { diff --git a/test/fixtures/cloudbuild_enabled/variables.tf b/test/fixtures/cloudbuild_enabled/variables.tf index c6431f1a..11cf07cd 100644 --- a/test/fixtures/cloudbuild_enabled/variables.tf +++ b/test/fixtures/cloudbuild_enabled/variables.tf @@ -48,3 +48,9 @@ variable "default_region" { description = "Default region to create resources where applicable." type = string } + +variable "org_project_creators" { + description = "Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required." + type = list(string) + default = [] +} \ No newline at end of file diff --git a/test/fixtures/simple/variables.tf b/test/fixtures/simple/variables.tf index c6431f1a..2425ce04 100644 --- a/test/fixtures/simple/variables.tf +++ b/test/fixtures/simple/variables.tf @@ -48,3 +48,9 @@ variable "default_region" { description = "Default region to create resources where applicable." type = string } + +variable "org_project_creators" { + description = "Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required." + type = list(string) + default = [] +} diff --git a/test/setup/main.tf b/test/setup/main.tf index 3601d626..c4664eb1 100644 --- a/test/setup/main.tf +++ b/test/setup/main.tf @@ -16,7 +16,7 @@ module "project" { source = "terraform-google-modules/project-factory/google" - version = "~> 3.0" + version = "~> 5.0" name = "ci-bootstrap" random_project_id = "true" diff --git a/variables.tf b/variables.tf index c4566bc5..d51ccb56 100644 --- a/variables.tf +++ b/variables.tf @@ -97,8 +97,7 @@ variable "org_admins_org_iam_permissions" { type = list(string) default = [ "roles/billing.user", - "roles/resourcemanager.organizationAdmin", - "roles/resourcemanager.projectCreator" + "roles/resourcemanager.organizationAdmin" ] } From ed423cde48ec666077a3e760a079a6392537ccc9 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Fri, 15 Nov 2019 15:07:56 +1100 Subject: [PATCH 14/27] Add tests for bucket permissions and project permissions --- examples/cloudbuild_enabled/README.md | 2 ++ examples/cloudbuild_enabled/outputs.tf | 8 ++++++ examples/simple/README.md | 2 ++ examples/simple/outputs.tf | 7 +++++ test/fixtures/cloudbuild_enabled/outputs.tf | 4 +++ .../cloudbuild_enabled/controls/gcp.rb | 28 +++++++++++++++++++ .../integration/cloudbuild_enabled/inspec.yml | 20 +++++++------ test/integration/simple/controls/gcp.rb | 4 +++ test/integration/simple/inspec.yml | 2 ++ 9 files changed, 68 insertions(+), 9 deletions(-) diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index 7d5ed375..e2bda401 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -22,6 +22,8 @@ This example combines the Organization bootstrap module with the Cloud Build sub | csr\_repos | | | gcs\_bucket\_cloudbuild\_artifacts | | | gcs\_bucket\_tfstate | | +| group\_billing\_admins | | +| group\_org\_admins | | | kms\_crypto\_key | | | kms\_keyring | | | seed\_project\_id | | diff --git a/examples/cloudbuild_enabled/outputs.tf b/examples/cloudbuild_enabled/outputs.tf index 1afdb894..db06a69b 100644 --- a/examples/cloudbuild_enabled/outputs.tf +++ b/examples/cloudbuild_enabled/outputs.tf @@ -14,6 +14,14 @@ * limitations under the License. */ +output "group_org_admins" { + value = var.group_org_admins +} + +output "group_billing_admins" { + value = var.group_billing_admins +} + output "seed_project_id" { value = module.seed_bootstrap.seed_project_id } diff --git a/examples/simple/README.md b/examples/simple/README.md index 1f0cac9a..5bb41ff1 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -19,6 +19,8 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | Name | Description | |------|-------------| | gcs\_bucket\_tfstate | | +| group\_billing\_admins | | +| group\_org\_admins | | | seed\_project\_id | | | terraform\_sa\_email | | | terraform\_sa\_name | | diff --git a/examples/simple/outputs.tf b/examples/simple/outputs.tf index cc4eda46..30312bda 100644 --- a/examples/simple/outputs.tf +++ b/examples/simple/outputs.tf @@ -14,6 +14,13 @@ * limitations under the License. */ +output "group_org_admins" { + value = var.group_org_admins +} + +output "group_billing_admins" { + value = var.group_billing_admins +} output "seed_project_id" { value = module.seed_bootstrap.seed_project_id diff --git a/test/fixtures/cloudbuild_enabled/outputs.tf b/test/fixtures/cloudbuild_enabled/outputs.tf index 2c9f86ef..0f939008 100644 --- a/test/fixtures/cloudbuild_enabled/outputs.tf +++ b/test/fixtures/cloudbuild_enabled/outputs.tf @@ -14,6 +14,10 @@ * limitations under the License. */ +output "group_org_admins" { + value = module.cloudbuild_enabled.group_org_admins +} + output "seed_project_id" { value = module.cloudbuild_enabled.seed_project_id } diff --git a/test/integration/cloudbuild_enabled/controls/gcp.rb b/test/integration/cloudbuild_enabled/controls/gcp.rb index ecf1ba6b..efb09e0f 100644 --- a/test/integration/cloudbuild_enabled/controls/gcp.rb +++ b/test/integration/cloudbuild_enabled/controls/gcp.rb @@ -26,6 +26,8 @@ cloudbuild_apis = ["cloudbuild.googleapis.com", "sourcerepo.googleapis.com", "cloudkms.googleapis.com"] +cloudbuild_project_admin_roles = ["roles/cloudbuild.builds.editor", "roles/viewer", "roles/source.admin"] + control "bootstrap" do title "Bootstrap module GCP resources" @@ -58,6 +60,13 @@ it { should exist } end + cloudbuild_project_admin_roles.each do |role| + describe google_project_iam_binding(project: attribute("cloudbuild_project_id"), role: role) do + it { should exist } + its('members') {should include 'group:' + attribute("group_org_admins")} + end + end + describe google_storage_bucket(name: attribute("gcs_bucket_cloudbuild_artifacts")) do it { should exist } end @@ -76,4 +85,23 @@ end end + google_projects.where(project_id: attribute("cloudbuild_project_id")).project_numbers.each do |project_number| + describe google_storage_bucket_iam_binding(bucket: attribute("gcs_bucket_tfstate"), role: 'roles/storage.objectAdmin') do + it { should exist } + its('members') {should include 'serviceAccount:' + attribute("terraform_sa_email")} + its('members') {should include 'serviceAccount:' + project_number.to_s + '@cloudbuild.gserviceaccount.com'} + end + + describe google_kms_crypto_key_iam_binding(crypto_key_url: attribute("kms_crypto_key")['self_link'], role: "roles/cloudkms.cryptoKeyDecrypter") do + it { should exist } + its('members') {should include 'serviceAccount:' + attribute("terraform_sa_email")} + its('members') {should include 'serviceAccount:' + project_number.to_s + '@cloudbuild.gserviceaccount.com'} + end + end + + describe google_kms_crypto_key_iam_binding(crypto_key_url: attribute("kms_crypto_key")['self_link'], role: "roles/cloudkms.cryptoKeyEncrypter") do + it { should exist } + its('members') {should include 'group:' + attribute("group_org_admins")} + end + end \ No newline at end of file diff --git a/test/integration/cloudbuild_enabled/inspec.yml b/test/integration/cloudbuild_enabled/inspec.yml index d939486a..e4018766 100644 --- a/test/integration/cloudbuild_enabled/inspec.yml +++ b/test/integration/cloudbuild_enabled/inspec.yml @@ -4,6 +4,8 @@ depends: git: https://github.com/inspec/inspec-gcp.git tag: v0.20.0 attributes: + - name: group_org_admins + required: true - name: seed_project_id required: true type: string @@ -22,12 +24,12 @@ attributes: - name: gcs_bucket_cloudbuild_artifacts required: true type: string - # - name: csr_repos - # required: true - # type: string - # - name: kms_keyring - # required: true - # type: string - # - name: kms_crypto_key - # required: true - # type: string + - name: csr_repos + required: true + type: hash + - name: kms_keyring + required: true + type: hash + - name: kms_crypto_key + required: true + type: hash diff --git a/test/integration/simple/controls/gcp.rb b/test/integration/simple/controls/gcp.rb index 4fc09905..aeb50301 100644 --- a/test/integration/simple/controls/gcp.rb +++ b/test/integration/simple/controls/gcp.rb @@ -35,6 +35,10 @@ it { should exist } end + describe google_storage_bucket_iam_binding(bucket: attribute("gcs_bucket_tfstate"), role: 'roles/storage.objectAdmin') do + its('members') {should include 'serviceAccount:' + attribute("terraform_sa_email")} + end + describe google_service_account(name: attribute("terraform_sa_name")) do it { should exist } its('has_user_managed_keys?') {should cmp false } diff --git a/test/integration/simple/inspec.yml b/test/integration/simple/inspec.yml index b8ceeff2..0f8cc27b 100644 --- a/test/integration/simple/inspec.yml +++ b/test/integration/simple/inspec.yml @@ -4,6 +4,8 @@ depends: git: https://github.com/inspec/inspec-gcp.git tag: v0.20.0 attributes: + - name: group_org_admins + required: true - name: seed_project_id required: true type: string From f4e80df9546f83fa5d361ca7ae86e5f5e8f90694 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Fri, 15 Nov 2019 15:21:47 +1100 Subject: [PATCH 15/27] Update cloudbuild definition --- build/int.cloudbuild.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index acdecf83..d8301a79 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -21,6 +21,9 @@ steps: - 'TF_VAR_org_id=$_ORG_ID' - 'TF_VAR_folder_id=$_FOLDER_ID' - 'TF_VAR_billing_account=$_BILLING_ACCOUNT' + - 'TF_VAR_group_org_admins=$_GROUP_ORG_ADMINS' + - 'TF_VAR_group_billing_admins=$_GROUP_BILLING_ADMINS' + - 'TF_VAR_default_region=$_DEFAULT_REGION' - id: create name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create'] From ea11e41e2a620dca5c5d82cedba67ca364f0b7d8 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Mon, 18 Nov 2019 15:46:16 +1100 Subject: [PATCH 16/27] Update cloudbuild submodule documentation --- modules/cloudbuild/README.md | 38 +++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index a70b9c49..2c4843e7 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -75,4 +75,40 @@ Functional examples and sample Cloud Build definitions are included in the [exam | kms\_crypto\_key | | | kms\_keyring | | - \ No newline at end of file + + +## Requirements + +### Software + +- [gcloud sdk](https://cloud.google.com/sdk/install) >= 206.0.0 +- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.6 +- [terraform-provider-google] plugin 2.1.x +- [terraform-provider-google-beta] plugin 2.1.x + +### Permissions + +- `roles/billing.user` on supplied billing account +- `roles/resourcemanager.organizationAdmin` on GCP Organization +- `roles/resourcemanager.projectCreator` on GCP Organization or folder + +### APIs + +A project with the following APIs enabled must be used to host the +resources of this module: + +- Google Cloud Resource Manager API: `cloudresourcemanager.googleapis.com` +- Google Cloud Billing API: `cloudbilling.googleapis.com` +- Google Cloud IAM API: `iam.googleapis.com` +- Google Cloud Storage API `storage-api.googleapis.com` +- Google Cloud Service Usage API: `serviceusage.googleapis.com` +- Google Cloud Build API: `cloudbuild.googleapis.com` +- Google Cloud Source Repo API: `sourcerepo.googleapis.com` +- Google Cloud KMS API: `cloudkms.googleapis.com` + +This API can be enabled in the default project created during establishing an organization. + +## Contributing + +Refer to the [contribution guidelines](../../CONTRIBUTING.md) for +information on contributing to this module. \ No newline at end of file From c3e50352262f456fe69fe248e8416fb411339d9a Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Mon, 18 Nov 2019 15:46:16 +1100 Subject: [PATCH 17/27] Update cloudbuild submodule documentation --- .pre-commit-config.yaml | 16 +++++++++++++++- README.md | 10 +++++----- examples/cloudbuild_enabled/README.md | 2 +- .../cloudbuild-tf-apply.yaml | 2 +- .../cloudbuild_enabled/cloudbuild-tf-plan.yaml | 2 +- examples/cloudbuild_enabled/main.tf | 2 +- examples/cloudbuild_enabled/outputs.tf | 2 +- examples/cloudbuild_enabled/variables.tf | 16 ++++++++++++++++ examples/simple/README.md | 2 +- examples/simple/main.tf | 2 +- examples/simple/outputs.tf | 2 +- examples/simple/variables.tf | 18 +++++++++++++++++- modules/cloudbuild/README.md | 2 +- .../cloudbuild/cloudbuild_builder/Dockerfile | 14 ++++++++++++++ .../cloudbuild_builder/cloudbuild.yaml | 16 +++++++++++++++- modules/cloudbuild/main.tf | 2 +- modules/cloudbuild/outputs.tf | 2 +- modules/cloudbuild/variables.tf | 2 +- test/fixtures/cloudbuild_enabled/main.tf | 1 + test/fixtures/cloudbuild_enabled/outputs.tf | 2 +- test/fixtures/cloudbuild_enabled/variables.tf | 2 +- .../cloudbuild_enabled/controls/gcp.rb | 2 +- test/integration/simple/controls/gcp.rb | 2 +- test/integration/simple/inspec.yml | 2 +- test/setup/make_source.sh | 2 +- 25 files changed, 101 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3d025cc..6ae7f259 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,23 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + repos: - repo: git://github.com/antonbabenko/pre-commit-terraform rev: v1.17.0 hooks: - id: terraform_fmt - - id: terraform_docs + # - id: terraform_docs - repo: git://github.com/pre-commit/pre-commit-hooks rev: v2.2.3 hooks: diff --git a/README.md b/README.md index f9880a86..bdb7c650 100644 --- a/README.md +++ b/README.md @@ -46,18 +46,18 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| activate\_apis | List of APIs to enable in the seed project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | +| activate\_apis | List of APIs to enable in the seed project. | list(string) | `` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | | default\_region | Default region to create resources where applicable. | string | n/a | yes | | folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | -| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/resourcemanager.organizationAdmin" ]` | no | -| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `[]` | no | +| org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `` | no | +| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `` | no | | organization\_id | GCP Organization ID | string | n/a | yes | | project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | | sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | -| sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `[ "roles/billing.user", "roles/compute.networkAdmin", "roles/compute.xpnAdmin", "roles/iam.serviceAccountAdmin", "roles/logging.configWriter", "roles/orgpolicy.policyAdmin", "roles/resourcemanager.folderCreator", "roles/resourcemanager.folderViewer", "roles/resourcemanager.organizationViewer" ]` | no | +| sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `` | no | ## Outputs @@ -106,4 +106,4 @@ This API can be enabled in the default project created during establishing an or ## Contributing Refer to the [contribution guidelines](./CONTRIBUTING.md) for -information on contributing to this module. \ No newline at end of file +information on contributing to this module. diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index e2bda401..7aae8b00 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -30,4 +30,4 @@ This example combines the Organization bootstrap module with the Cloud Build sub | terraform\_sa\_email | | | terraform\_sa\_name | | - \ No newline at end of file + diff --git a/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml b/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml index 9aae09c9..40bc21ab 100644 --- a/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml +++ b/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml @@ -35,4 +35,4 @@ steps: artifacts: objects: location: 'gs://${_ARTIFACT_BUCKET_NAME}/terraform/cloudbuild/apply/$BUILD_ID' - paths: ['cloudbuild-tf-apply.yaml', 'tfplan'] \ No newline at end of file + paths: ['cloudbuild-tf-apply.yaml', 'tfplan'] diff --git a/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml b/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml index a73ad47a..d454810a 100644 --- a/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml +++ b/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml @@ -28,4 +28,4 @@ steps: artifacts: objects: location: 'gs://${_ARTIFACT_BUCKET_NAME}/terraform/cloudbuild/plan/$BUILD_ID' - paths: ['cloudbuild-tf-plan.yaml', 'tfplan'] \ No newline at end of file + paths: ['cloudbuild-tf-plan.yaml', 'tfplan'] diff --git a/examples/cloudbuild_enabled/main.tf b/examples/cloudbuild_enabled/main.tf index ea4e9323..d44d3ae4 100644 --- a/examples/cloudbuild_enabled/main.tf +++ b/examples/cloudbuild_enabled/main.tf @@ -40,4 +40,4 @@ module "cloudbuild_bootstrap" { terraform_sa_email = module.seed_bootstrap.terraform_sa_email terraform_sa_name = module.seed_bootstrap.terraform_sa_name terraform_state_bucket = module.seed_bootstrap.gcs_bucket_tfstate -} \ No newline at end of file +} diff --git a/examples/cloudbuild_enabled/outputs.tf b/examples/cloudbuild_enabled/outputs.tf index db06a69b..677c2994 100644 --- a/examples/cloudbuild_enabled/outputs.tf +++ b/examples/cloudbuild_enabled/outputs.tf @@ -56,4 +56,4 @@ output "kms_keyring" { output "kms_crypto_key" { value = module.cloudbuild_bootstrap.kms_crypto_key -} \ No newline at end of file +} diff --git a/examples/cloudbuild_enabled/variables.tf b/examples/cloudbuild_enabled/variables.tf index 280e8e41..8f3efcc2 100644 --- a/examples/cloudbuild_enabled/variables.tf +++ b/examples/cloudbuild_enabled/variables.tf @@ -1,3 +1,19 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + variable "organization_id" { description = "GCP Organization ID" } diff --git a/examples/simple/README.md b/examples/simple/README.md index 5bb41ff1..b75c37e4 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -25,4 +25,4 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | terraform\_sa\_email | | | terraform\_sa\_name | | - \ No newline at end of file + diff --git a/examples/simple/main.tf b/examples/simple/main.tf index b9a96e54..f6e76544 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -27,4 +27,4 @@ module "seed_bootstrap" { group_billing_admins = var.group_billing_admins default_region = var.default_region org_project_creators = var.org_project_creators -} \ No newline at end of file +} diff --git a/examples/simple/outputs.tf b/examples/simple/outputs.tf index 30312bda..d98988c0 100644 --- a/examples/simple/outputs.tf +++ b/examples/simple/outputs.tf @@ -36,4 +36,4 @@ output "terraform_sa_name" { output "gcs_bucket_tfstate" { value = module.seed_bootstrap.gcs_bucket_tfstate -} \ No newline at end of file +} diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf index 32700fd0..8f3efcc2 100644 --- a/examples/simple/variables.tf +++ b/examples/simple/variables.tf @@ -1,3 +1,19 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + variable "organization_id" { description = "GCP Organization ID" } @@ -22,4 +38,4 @@ variable "org_project_creators" { description = "Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required." type = list(string) default = [] -} \ No newline at end of file +} diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index 2c4843e7..f3c900f2 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -111,4 +111,4 @@ This API can be enabled in the default project created during establishing an or ## Contributing Refer to the [contribution guidelines](../../CONTRIBUTING.md) for -information on contributing to this module. \ No newline at end of file +information on contributing to this module. diff --git a/modules/cloudbuild/cloudbuild_builder/Dockerfile b/modules/cloudbuild/cloudbuild_builder/Dockerfile index a20a26f2..3c4bb77c 100644 --- a/modules/cloudbuild/cloudbuild_builder/Dockerfile +++ b/modules/cloudbuild/cloudbuild_builder/Dockerfile @@ -1,3 +1,17 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + FROM gcr.io/cloud-builders/gcloud-slim ENV TERRAFORM_VERSION=0.12.13 diff --git a/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml b/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml index 774bde7d..1c50e298 100644 --- a/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml +++ b/modules/cloudbuild/cloudbuild_builder/cloudbuild.yaml @@ -1,3 +1,17 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # In this directory, run the following command to build this builder. # $ gcloud builds submit . --config=cloudbuild.yaml steps: @@ -5,4 +19,4 @@ steps: args: ['build', '--tag=gcr.io/${PROJECT_ID}/terraform', '.'] - name: 'gcr.io/${PROJECT_ID}/terraform' args: ['version'] -images: ['gcr.io/${PROJECT_ID}/terraform'] \ No newline at end of file +images: ['gcr.io/${PROJECT_ID}/terraform'] diff --git a/modules/cloudbuild/main.tf b/modules/cloudbuild/main.tf index 236ff160..50492dc4 100644 --- a/modules/cloudbuild/main.tf +++ b/modules/cloudbuild/main.tf @@ -245,4 +245,4 @@ resource "google_storage_bucket_iam_member" "cloudbuild_state_iam" { bucket = var.terraform_state_bucket role = "roles/storage.objectAdmin" member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" -} \ No newline at end of file +} diff --git a/modules/cloudbuild/outputs.tf b/modules/cloudbuild/outputs.tf index 07f8a2d3..677cfa13 100644 --- a/modules/cloudbuild/outputs.tf +++ b/modules/cloudbuild/outputs.tf @@ -34,4 +34,4 @@ output "kms_keyring" { output "kms_crypto_key" { value = google_kms_crypto_key.tf_key -} \ No newline at end of file +} diff --git a/modules/cloudbuild/variables.tf b/modules/cloudbuild/variables.tf index 72aff8d9..e4be93f6 100644 --- a/modules/cloudbuild/variables.tf +++ b/modules/cloudbuild/variables.tf @@ -102,4 +102,4 @@ variable "folder_id" { description = "The ID of a folder to host this project" type = string default = "" -} \ No newline at end of file +} diff --git a/test/fixtures/cloudbuild_enabled/main.tf b/test/fixtures/cloudbuild_enabled/main.tf index 3ac24154..d3c69328 100644 --- a/test/fixtures/cloudbuild_enabled/main.tf +++ b/test/fixtures/cloudbuild_enabled/main.tf @@ -23,3 +23,4 @@ module "cloudbuild_enabled" { group_billing_admins = var.group_billing_admins default_region = var.default_region } + diff --git a/test/fixtures/cloudbuild_enabled/outputs.tf b/test/fixtures/cloudbuild_enabled/outputs.tf index 0f939008..6d6b91d7 100644 --- a/test/fixtures/cloudbuild_enabled/outputs.tf +++ b/test/fixtures/cloudbuild_enabled/outputs.tf @@ -52,4 +52,4 @@ output "kms_keyring" { output "kms_crypto_key" { value = module.cloudbuild_enabled.kms_crypto_key -} \ No newline at end of file +} diff --git a/test/fixtures/cloudbuild_enabled/variables.tf b/test/fixtures/cloudbuild_enabled/variables.tf index 11cf07cd..2425ce04 100644 --- a/test/fixtures/cloudbuild_enabled/variables.tf +++ b/test/fixtures/cloudbuild_enabled/variables.tf @@ -53,4 +53,4 @@ variable "org_project_creators" { description = "Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required." type = list(string) default = [] -} \ No newline at end of file +} diff --git a/test/integration/cloudbuild_enabled/controls/gcp.rb b/test/integration/cloudbuild_enabled/controls/gcp.rb index efb09e0f..a3036b1e 100644 --- a/test/integration/cloudbuild_enabled/controls/gcp.rb +++ b/test/integration/cloudbuild_enabled/controls/gcp.rb @@ -104,4 +104,4 @@ its('members') {should include 'group:' + attribute("group_org_admins")} end -end \ No newline at end of file +end diff --git a/test/integration/simple/controls/gcp.rb b/test/integration/simple/controls/gcp.rb index aeb50301..a476a09a 100644 --- a/test/integration/simple/controls/gcp.rb +++ b/test/integration/simple/controls/gcp.rb @@ -51,4 +51,4 @@ end end -end \ No newline at end of file +end diff --git a/test/integration/simple/inspec.yml b/test/integration/simple/inspec.yml index 0f8cc27b..2ad40dda 100644 --- a/test/integration/simple/inspec.yml +++ b/test/integration/simple/inspec.yml @@ -17,4 +17,4 @@ attributes: type: string - name: gcs_bucket_tfstate required: true - type: string \ No newline at end of file + type: string diff --git a/test/setup/make_source.sh b/test/setup/make_source.sh index be0e4d85..7aa1f9a8 100755 --- a/test/setup/make_source.sh +++ b/test/setup/make_source.sh @@ -26,4 +26,4 @@ done sa_json=$(terraform output sa_key) # shellcheck disable=SC2086 -echo "export SERVICE_ACCOUNT_JSON='$(echo $sa_json | base64 --decode)'" >> ../source.sh \ No newline at end of file +echo "export SERVICE_ACCOUNT_JSON='$(echo $sa_json | base64 --decode)'" >> ../source.sh From 419b823049c0d85bd732cf756581399d9326e460 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Tue, 19 Nov 2019 10:53:30 +1100 Subject: [PATCH 18/27] Retry converge and destroy to overcome transient test errors --- .pre-commit-config.yaml | 2 +- build/int.cloudbuild.yaml | 4 ++-- examples/cloudbuild_enabled/README.md | 2 +- examples/simple/README.md | 2 +- modules/cloudbuild/README.md | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ae7f259..c9c39f73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - repo: git://github.com/antonbabenko/pre-commit-terraform - rev: v1.17.0 + rev: v1.21.0 hooks: - id: terraform_fmt # - id: terraform_docs diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index d8301a79..e958f120 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -29,13 +29,13 @@ steps: args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create'] - id: converge name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge'] + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge || kitchen_do converge'] - id: verify name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do verify'] - id: destroy name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy'] + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy || kitchen_do destroy'] tags: - 'ci' - 'integration' diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index 7aae8b00..67969202 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -11,7 +11,7 @@ This example combines the Organization bootstrap module with the Cloud Build sub | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | -| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `[]` | no | +| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `` | no | | organization\_id | GCP Organization ID | string | n/a | yes | ## Outputs diff --git a/examples/simple/README.md b/examples/simple/README.md index b75c37e4..9da77c73 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -11,7 +11,7 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | -| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `[]` | no | +| org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `` | no | | organization\_id | GCP Organization ID | string | n/a | yes | ## Outputs diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index f3c900f2..1f3f3832 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -52,9 +52,9 @@ Functional examples and sample Cloud Build definitions are included in the [exam | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `[ "servicenetworking.googleapis.com", "compute.googleapis.com", "logging.googleapis.com", "bigquery-json.googleapis.com", "cloudresourcemanager.googleapis.com", "cloudbilling.googleapis.com", "iam.googleapis.com", "admin.googleapis.com", "appengine.googleapis.com" ]` | no | +| activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | -| cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `[ "gcp-org", "gcp-networks", "gcp-projects" ]` | no | +| cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `` | no | | default\_region | Default region to create resources where applicable. | string | n/a | yes | | folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | From 66142795664197c30e09bb3061592b2e6742243b Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Tue, 19 Nov 2019 17:24:34 +1100 Subject: [PATCH 19/27] Modify behaviour for enabling APIs for more reliable behaviour for apply/destroy --- main.tf | 17 ++++++------ modules/cloudbuild/main.tf | 54 ++++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/main.tf b/main.tf index 8a27c051..aa3652b5 100644 --- a/main.tf +++ b/main.tf @@ -35,14 +35,15 @@ data "google_organization" "org" { *******************************************/ module "seed_project" { - source = "terraform-google-modules/project-factory/google" - version = "~> 5.0" - name = local.seed_project_id - random_project_id = "true" - folder_id = var.folder_id - org_id = var.organization_id - billing_account = var.billing_account - activate_apis = local.activate_apis + source = "terraform-google-modules/project-factory/google" + version = "~> 5.0" + name = local.seed_project_id + random_project_id = "true" + disable_services_on_destroy = "false" + folder_id = var.folder_id + org_id = var.organization_id + billing_account = var.billing_account + activate_apis = local.activate_apis } /****************************************** diff --git a/modules/cloudbuild/main.tf b/modules/cloudbuild/main.tf index 50492dc4..a8962821 100644 --- a/modules/cloudbuild/main.tf +++ b/modules/cloudbuild/main.tf @@ -18,7 +18,7 @@ locals { cloudbuild_project_id = format("%s-%s", var.project_prefix, "cloudbuild") cloudbuild_apis = ["cloudbuild.googleapis.com", "sourcerepo.googleapis.com", "cloudkms.googleapis.com"] impersonation_enabled_count = var.sa_enable_impersonation == true ? 1 : 0 - activate_apis = distinct(concat(var.activate_apis, local.cloudbuild_apis)) + activate_apis = distinct(var.activate_apis) } resource "random_id" "suffix" { @@ -35,14 +35,22 @@ data "google_organization" "org" { *******************************************/ module "cloudbuild_project" { - source = "terraform-google-modules/project-factory/google" - version = "~> 5.0" - name = local.cloudbuild_project_id - random_project_id = "true" - folder_id = var.folder_id - org_id = var.organization_id - billing_account = var.billing_account - activate_apis = local.activate_apis + source = "terraform-google-modules/project-factory/google" + version = "~> 5.0" + name = local.cloudbuild_project_id + random_project_id = "true" + disable_services_on_destroy = "false" + folder_id = var.folder_id + org_id = var.organization_id + billing_account = var.billing_account + activate_apis = local.activate_apis +} + +resource "google_project_service" "cloudbuild_apis" { + for_each = toset(local.cloudbuild_apis) + project = module.cloudbuild_project.project_id + service = each.value + disable_on_destroy = false } /****************************************** @@ -80,6 +88,9 @@ resource "google_kms_key_ring" "tf_keyring" { project = module.cloudbuild_project.project_id name = "tf-keyring" location = var.default_region + depends_on = [ + google_project_service.cloudbuild_apis, + ] } /****************************************** @@ -103,6 +114,9 @@ resource "google_kms_crypto_key_iam_binding" "cloudbuild_crypto_key_decrypter" { "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com", "serviceAccount:${var.terraform_sa_email}" ] + depends_on = [ + google_project_service.cloudbuild_apis, + ] } /****************************************** @@ -126,6 +140,9 @@ resource "google_sourcerepo_repository" "gcp_repo" { for_each = toset(var.cloud_source_repos) project = module.cloudbuild_project.project_id name = each.value + depends_on = [ + google_project_service.cloudbuild_apis, + ] } /****************************************** @@ -164,7 +181,7 @@ resource "google_cloudbuild_trigger" "master_trigger" { filename = "cloudbuild-tf-apply.yaml" depends_on = [ - google_sourcerepo_repository.gcp_repo + google_sourcerepo_repository.gcp_repo, ] } @@ -194,7 +211,7 @@ resource "google_cloudbuild_trigger" "non_master_trigger" { filename = "cloudbuild-tf-plan.yaml" depends_on = [ - google_sourcerepo_repository.gcp_repo + google_sourcerepo_repository.gcp_repo, ] } @@ -210,6 +227,9 @@ resource "null_resource" "cloudbuild_terraform_builder" { provisioner "local-exec" { command = "gcloud builds submit ${path.module}/cloudbuild_builder/ --project ${module.cloudbuild_project.project_id} --config=${path.module}/cloudbuild_builder/cloudbuild.yaml" } + depends_on = [ + google_project_service.cloudbuild_apis, + ] } /*********************************************** @@ -220,6 +240,9 @@ resource "google_storage_bucket_iam_member" "cloudbuild_artifacts_iam" { bucket = google_storage_bucket.cloudbuild_artifacts.name role = "roles/storage.objectAdmin" member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" + depends_on = [ + google_project_service.cloudbuild_apis, + ] } resource "google_service_account_iam_member" "cloudbuild_terraform_sa_impersonate_permissions" { @@ -228,6 +251,9 @@ resource "google_service_account_iam_member" "cloudbuild_terraform_sa_impersonat service_account_id = var.terraform_sa_name role = "roles/iam.serviceAccountTokenCreator" member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" + depends_on = [ + google_project_service.cloudbuild_apis, + ] } resource "google_organization_iam_member" "cloudbuild_serviceusage_consumer" { @@ -236,6 +262,9 @@ resource "google_organization_iam_member" "cloudbuild_serviceusage_consumer" { org_id = var.organization_id role = "roles/serviceusage.serviceUsageConsumer" member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" + depends_on = [ + google_project_service.cloudbuild_apis, + ] } # Required to allow cloud build to access state with impersonation. @@ -245,4 +274,7 @@ resource "google_storage_bucket_iam_member" "cloudbuild_state_iam" { bucket = var.terraform_state_bucket role = "roles/storage.objectAdmin" member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" + depends_on = [ + google_project_service.cloudbuild_apis, + ] } From 2d70ef4f945bfb69669fe890e68831deb1a05a1f Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 20 Nov 2019 08:34:52 +1100 Subject: [PATCH 20/27] Remove workaround cloudbuild workaround --- build/int.cloudbuild.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index e958f120..d8301a79 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -29,13 +29,13 @@ steps: args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create'] - id: converge name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge || kitchen_do converge'] + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge'] - id: verify name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do verify'] - id: destroy name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy || kitchen_do destroy'] + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy'] tags: - 'ci' - 'integration' From f1289483965a6782d494e3f8c7fc7bd1eaf2c085 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 20 Nov 2019 11:03:57 +1100 Subject: [PATCH 21/27] Address PR feedback --- Makefile | 2 +- README.md | 2 +- build/int.cloudbuild.yaml | 7 +++---- .../cloudbuild_enabled/cloudbuild-tf-apply.yaml | 4 ++++ .../cloudbuild_enabled/cloudbuild-tf-plan.yaml | 4 ++++ examples/cloudbuild_enabled/main.tf | 4 ++-- examples/cloudbuild_enabled/variables.tf | 2 +- examples/simple/main.tf | 2 +- examples/simple/variables.tf | 2 +- main.tf | 16 ++++++++-------- modules/cloudbuild/README.md | 2 +- modules/cloudbuild/main.tf | 10 +++++----- modules/cloudbuild/variables.tf | 3 ++- test/fixtures/cloudbuild_enabled/main.tf | 2 +- test/fixtures/simple/main.tf | 2 +- variables.tf | 3 ++- 16 files changed, 38 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index c70ce448..8d6309b1 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ # Make will use bash instead of sh SHELL := /usr/bin/env bash -DOCKER_TAG_VERSION_DEVELOPER_TOOLS := 0 +DOCKER_TAG_VERSION_DEVELOPER_TOOLS := 0.6.0 DOCKER_IMAGE_DEVELOPER_TOOLS := cft/developer-tools REGISTRY_URL := gcr.io/cloud-foundation-cicd diff --git a/README.md b/README.md index bdb7c650..0e6d56a8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ module "bootstrap" { source = "terraform-google-modules/bootstrap/google" version = "~> 0.1" - organization_id = "" + org_id = "" billing_account = "" group_org_admins = "gcp-organization-admins@example.com" group_billing_admins = "gcp-billing-admins@example.com" diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index d8301a79..cb58363f 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -21,9 +21,8 @@ steps: - 'TF_VAR_org_id=$_ORG_ID' - 'TF_VAR_folder_id=$_FOLDER_ID' - 'TF_VAR_billing_account=$_BILLING_ACCOUNT' - - 'TF_VAR_group_org_admins=$_GROUP_ORG_ADMINS' - - 'TF_VAR_group_billing_admins=$_GROUP_BILLING_ADMINS' - - 'TF_VAR_default_region=$_DEFAULT_REGION' + - 'TF_VAR_group_org_admins=test-gcp-org-admins@test.infra.cft.tips' + - 'TF_VAR_group_billing_admins=test-gcp-billing-admins@test.infra.cft.tips' - id: create name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create'] @@ -41,4 +40,4 @@ tags: - 'integration' substitutions: _DOCKER_IMAGE_DEVELOPER_TOOLS: 'cft/developer-tools' - _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '0.1.0' + _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '0.6.0' diff --git a/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml b/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml index 40bc21ab..bb9daca3 100644 --- a/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml +++ b/examples/cloudbuild_enabled/cloudbuild-tf-apply.yaml @@ -19,6 +19,10 @@ steps: args: - init - -input=false +# terraform validate +- name: gcr.io/$PROJECT_ID/terraform + args: + - validate # terraform plan -input=false -out=tfplan - name: gcr.io/$PROJECT_ID/terraform args: diff --git a/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml b/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml index d454810a..0c7fe954 100644 --- a/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml +++ b/examples/cloudbuild_enabled/cloudbuild-tf-plan.yaml @@ -19,6 +19,10 @@ steps: args: - init - -input=false +# terraform validate +- name: gcr.io/$PROJECT_ID/terraform + args: + - validate # terraform plan -input=false -out=tfplan - name: gcr.io/$PROJECT_ID/terraform args: diff --git a/examples/cloudbuild_enabled/main.tf b/examples/cloudbuild_enabled/main.tf index d44d3ae4..f75519dd 100644 --- a/examples/cloudbuild_enabled/main.tf +++ b/examples/cloudbuild_enabled/main.tf @@ -21,7 +21,7 @@ provider "google" { module "seed_bootstrap" { source = "../.." - organization_id = var.organization_id + org_id = var.org_id billing_account = var.billing_account group_org_admins = var.group_org_admins group_billing_admins = var.group_billing_admins @@ -32,7 +32,7 @@ module "seed_bootstrap" { module "cloudbuild_bootstrap" { source = "../../modules/cloudbuild" - organization_id = var.organization_id + org_id = var.org_id billing_account = var.billing_account group_org_admins = var.group_org_admins default_region = var.default_region diff --git a/examples/cloudbuild_enabled/variables.tf b/examples/cloudbuild_enabled/variables.tf index 8f3efcc2..0f914599 100644 --- a/examples/cloudbuild_enabled/variables.tf +++ b/examples/cloudbuild_enabled/variables.tf @@ -14,7 +14,7 @@ * limitations under the License. */ -variable "organization_id" { +variable "org_id" { description = "GCP Organization ID" } diff --git a/examples/simple/main.tf b/examples/simple/main.tf index f6e76544..e6c602d6 100644 --- a/examples/simple/main.tf +++ b/examples/simple/main.tf @@ -21,7 +21,7 @@ provider "google" { module "seed_bootstrap" { source = "../.." - organization_id = var.organization_id + org_id = var.org_id billing_account = var.billing_account group_org_admins = var.group_org_admins group_billing_admins = var.group_billing_admins diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf index 8f3efcc2..0f914599 100644 --- a/examples/simple/variables.tf +++ b/examples/simple/variables.tf @@ -14,7 +14,7 @@ * limitations under the License. */ -variable "organization_id" { +variable "org_id" { description = "GCP Organization ID" } diff --git a/main.tf b/main.tf index aa3652b5..86e823c8 100644 --- a/main.tf +++ b/main.tf @@ -27,7 +27,7 @@ resource "random_id" "suffix" { } data "google_organization" "org" { - organization = var.organization_id + organization = var.org_id } /****************************************** @@ -41,7 +41,7 @@ module "seed_project" { random_project_id = "true" disable_services_on_destroy = "false" folder_id = var.folder_id - org_id = var.organization_id + org_id = var.org_id billing_account = var.billing_account activate_apis = local.activate_apis } @@ -74,7 +74,7 @@ resource "google_storage_bucket" "org_terraform_state" { ***********************************************/ resource "google_organization_iam_binding" "billing_creator" { - org_id = var.organization_id + org_id = var.org_id role = "roles/billing.creator" members = [ "group:${var.group_billing_admins}", @@ -82,7 +82,7 @@ resource "google_organization_iam_binding" "billing_creator" { } resource "google_organization_iam_binding" "project_creator" { - org_id = var.organization_id + org_id = var.org_id role = "roles/resourcemanager.projectCreator" members = local.org_project_creators } @@ -93,7 +93,7 @@ resource "google_organization_iam_binding" "project_creator" { resource "google_organization_iam_member" "org_admins_group" { for_each = toset(var.org_admins_org_iam_permissions) - org_id = var.organization_id + org_id = var.org_id role = each.value member = "group:${var.group_org_admins}" } @@ -103,7 +103,7 @@ resource "google_organization_iam_member" "org_admins_group" { ***********************************************/ resource "google_organization_iam_member" "org_billing_admin" { - org_id = var.organization_id + org_id = var.org_id role = "roles/billing.admin" member = "group:${var.group_billing_admins}" } @@ -115,7 +115,7 @@ resource "google_organization_iam_member" "org_billing_admin" { resource "google_organization_iam_member" "tf_sa_org_perms" { for_each = toset(var.sa_org_iam_permissions) - org_id = var.organization_id + org_id = var.org_id role = each.value member = "serviceAccount:${google_service_account.org_terraform.email}" } @@ -148,7 +148,7 @@ resource "google_service_account_iam_member" "org_admin_sa_impersonate_permissio resource "google_organization_iam_member" "org_admin_serviceusage_consumer" { count = local.impersonation_enabled_count - org_id = var.organization_id + org_id = var.org_id role = "roles/serviceusage.serviceUsageConsumer" member = "group:${var.group_org_admins}" } diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index 1f3f3832..e47d8854 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -10,7 +10,7 @@ module "bootstrap" { source = "terraform-google-modules/bootstrap/google//modules/cloudbuild" version = "~> 0.1" - organization_id = "" + org_id = "" billing_account = "" group_org_admins = "gcp-organization-admins@example.com" group_billing_admins = "gcp-billing-admins@example.com" diff --git a/modules/cloudbuild/main.tf b/modules/cloudbuild/main.tf index a8962821..eff2f998 100644 --- a/modules/cloudbuild/main.tf +++ b/modules/cloudbuild/main.tf @@ -26,7 +26,7 @@ resource "random_id" "suffix" { } data "google_organization" "org" { - organization = var.organization_id + organization = var.org_id } @@ -41,7 +41,7 @@ module "cloudbuild_project" { random_project_id = "true" disable_services_on_destroy = "false" folder_id = var.folder_id - org_id = var.organization_id + org_id = var.org_id billing_account = var.billing_account activate_apis = local.activate_apis } @@ -170,7 +170,7 @@ resource "google_cloudbuild_trigger" "master_trigger" { } substitutions = { - _ORG_ID = var.organization_id + _ORG_ID = var.org_id _BILLING_ID = var.billing_account _DEFAULT_REGION = var.default_region _TF_SA_EMAIL = var.terraform_sa_email @@ -200,7 +200,7 @@ resource "google_cloudbuild_trigger" "non_master_trigger" { } substitutions = { - _ORG_ID = var.organization_id + _ORG_ID = var.org_id _BILLING_ID = var.billing_account _DEFAULT_REGION = var.default_region _TF_SA_EMAIL = var.terraform_sa_email @@ -259,7 +259,7 @@ resource "google_service_account_iam_member" "cloudbuild_terraform_sa_impersonat resource "google_organization_iam_member" "cloudbuild_serviceusage_consumer" { count = local.impersonation_enabled_count - org_id = var.organization_id + org_id = var.org_id role = "roles/serviceusage.serviceUsageConsumer" member = "serviceAccount:${module.cloudbuild_project.project_number}@cloudbuild.gserviceaccount.com" depends_on = [ diff --git a/modules/cloudbuild/variables.tf b/modules/cloudbuild/variables.tf index e4be93f6..92507339 100644 --- a/modules/cloudbuild/variables.tf +++ b/modules/cloudbuild/variables.tf @@ -19,7 +19,7 @@ Required variables *******************************************/ -variable "organization_id" { +variable "org_id" { description = "GCP Organization ID" type = string } @@ -37,6 +37,7 @@ variable "group_org_admins" { variable "default_region" { description = "Default region to create resources where applicable." type = string + default = "us-central-1" } variable "terraform_sa_email" { diff --git a/test/fixtures/cloudbuild_enabled/main.tf b/test/fixtures/cloudbuild_enabled/main.tf index d3c69328..67e4ce9e 100644 --- a/test/fixtures/cloudbuild_enabled/main.tf +++ b/test/fixtures/cloudbuild_enabled/main.tf @@ -17,7 +17,7 @@ module "cloudbuild_enabled" { source = "../../../examples/cloudbuild_enabled" - organization_id = var.org_id + org_id = var.org_id billing_account = var.billing_account group_org_admins = var.group_org_admins group_billing_admins = var.group_billing_admins diff --git a/test/fixtures/simple/main.tf b/test/fixtures/simple/main.tf index 2d242d66..51dacc83 100644 --- a/test/fixtures/simple/main.tf +++ b/test/fixtures/simple/main.tf @@ -17,7 +17,7 @@ module "simple" { source = "../../../examples/simple" - organization_id = var.org_id + org_id = var.org_id billing_account = var.billing_account group_org_admins = var.group_org_admins group_billing_admins = var.group_billing_admins diff --git a/variables.tf b/variables.tf index d51ccb56..1a179363 100644 --- a/variables.tf +++ b/variables.tf @@ -18,7 +18,7 @@ Required variables *******************************************/ -variable "organization_id" { +variable "org_id" { description = "GCP Organization ID" type = string } @@ -41,6 +41,7 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." type = string + default = "us-central-1" } /****************************************** From 75844ef3d8b2691ef37517be7e22650b3d79883a Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 20 Nov 2019 11:20:57 +1100 Subject: [PATCH 22/27] Change type for boolean variables and retry converge --- README.md | 4 ++-- build/int.cloudbuild.yaml | 2 +- examples/cloudbuild_enabled/README.md | 2 +- examples/simple/README.md | 2 +- main.tf | 4 ++-- modules/cloudbuild/README.md | 4 ++-- modules/cloudbuild/main.tf | 4 ++-- test/setup/main.tf | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0e6d56a8..28757794 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,13 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). |------|-------------|:----:|:-----:|:-----:| | activate\_apis | List of APIs to enable in the seed project. | list(string) | `` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | -| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| default\_region | Default region to create resources where applicable. | string | `"us-central-1"` | no | | folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | org\_admins\_org\_iam\_permissions | List of permissions granted to the group supplied in group_org_admins variable across the GCP organization. | list(string) | `` | no | +| org\_id | GCP Organization ID | string | n/a | yes | | org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `` | no | -| organization\_id | GCP Organization ID | string | n/a | yes | | project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | | sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | | sa\_org\_iam\_permissions | List of permissions granted to Terraform service account across the GCP organization. | list(string) | `` | no | diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index cb58363f..a3a0f3b6 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -28,7 +28,7 @@ steps: args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create'] - id: converge name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge'] + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge || kitchen_do converge'] - id: verify name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do verify'] diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index 67969202..d0970f17 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -11,8 +11,8 @@ This example combines the Organization bootstrap module with the Cloud Build sub | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| org\_id | GCP Organization ID | string | n/a | yes | | org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `` | no | -| organization\_id | GCP Organization ID | string | n/a | yes | ## Outputs diff --git a/examples/simple/README.md b/examples/simple/README.md index 9da77c73..000cec54 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -11,8 +11,8 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | default\_region | Default region to create resources where applicable. | string | n/a | yes | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | +| org\_id | GCP Organization ID | string | n/a | yes | | org\_project\_creators | Additional list of members to have project creator role accross the organization. Prefix of group: user: or serviceAccount: is required. | list(string) | `` | no | -| organization\_id | GCP Organization ID | string | n/a | yes | ## Outputs diff --git a/main.tf b/main.tf index 86e823c8..e01bc665 100644 --- a/main.tf +++ b/main.tf @@ -38,8 +38,8 @@ module "seed_project" { source = "terraform-google-modules/project-factory/google" version = "~> 5.0" name = local.seed_project_id - random_project_id = "true" - disable_services_on_destroy = "false" + random_project_id = true + disable_services_on_destroy = false folder_id = var.folder_id org_id = var.org_id billing_account = var.billing_account diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index e47d8854..ff2b3afa 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -55,10 +55,10 @@ Functional examples and sample Cloud Build definitions are included in the [exam | activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | | cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `` | no | -| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| default\_region | Default region to create resources where applicable. | string | `"us-central-1"` | no | | folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | -| organization\_id | GCP Organization ID | string | n/a | yes | +| org\_id | GCP Organization ID | string | n/a | yes | | project\_prefix | Name prefix to use for projects created. | string | `"cft"` | no | | sa\_enable\_impersonation | Allow org_admins group to impersonate service account & enable APIs required. | bool | `"false"` | no | | terraform\_sa\_email | Email for terraform service account. | string | n/a | yes | diff --git a/modules/cloudbuild/main.tf b/modules/cloudbuild/main.tf index eff2f998..d627d98a 100644 --- a/modules/cloudbuild/main.tf +++ b/modules/cloudbuild/main.tf @@ -38,8 +38,8 @@ module "cloudbuild_project" { source = "terraform-google-modules/project-factory/google" version = "~> 5.0" name = local.cloudbuild_project_id - random_project_id = "true" - disable_services_on_destroy = "false" + random_project_id = true + disable_services_on_destroy = false folder_id = var.folder_id org_id = var.org_id billing_account = var.billing_account diff --git a/test/setup/main.tf b/test/setup/main.tf index c4664eb1..66a7a1f6 100644 --- a/test/setup/main.tf +++ b/test/setup/main.tf @@ -19,7 +19,7 @@ module "project" { version = "~> 5.0" name = "ci-bootstrap" - random_project_id = "true" + random_project_id = true org_id = var.org_id folder_id = var.folder_id billing_account = var.billing_account From eefdc1a989a321d36f09502f9ef478514baea2e7 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 20 Nov 2019 11:54:26 +1100 Subject: [PATCH 23/27] Fix issue where permissions are removed between test runs --- build/int.cloudbuild.yaml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index a3a0f3b6..8227ccb2 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -26,12 +26,28 @@ steps: - id: create name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create'] -- id: converge +- id: converge-simple name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge || kitchen_do converge'] -- id: verify + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge simple-default'] +- id: verify-simple name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do verify'] + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do verify simple-default'] + # Required to rerun to reinstate ci-integration account as project creator as not member of group_org_admins. +- id: prepare-rerun + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && prepare_environment'] + env: + - 'TF_VAR_org_id=$_ORG_ID' + - 'TF_VAR_folder_id=$_FOLDER_ID' + - 'TF_VAR_billing_account=$_BILLING_ACCOUNT' + - 'TF_VAR_group_org_admins=test-gcp-org-admins@test.infra.cft.tips' + - 'TF_VAR_group_billing_admins=test-gcp-billing-admins@test.infra.cft.tips' +- id: converge-cloudbuild-enabled + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge cloudbuild-enabled-default'] +- id: verify-cloudbuild-enabled + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do verify cloudbuild-enabled-default'] - id: destroy name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy'] From f6887cbbd7297135b75c5aa4e8b8c71fe37a56db Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 20 Nov 2019 14:21:22 +1100 Subject: [PATCH 24/27] Fix default region and remove outputs required for tests from examples --- CONTRIBUTING.md | 1 - README.md | 2 +- examples/cloudbuild_enabled/README.md | 4 +--- examples/cloudbuild_enabled/outputs.tf | 8 -------- examples/cloudbuild_enabled/variables.tf | 2 ++ examples/simple/README.md | 4 +--- examples/simple/outputs.tf | 8 -------- examples/simple/variables.tf | 2 ++ modules/cloudbuild/README.md | 2 +- modules/cloudbuild/variables.tf | 2 +- test/fixtures/cloudbuild_enabled/outputs.tf | 2 +- test/fixtures/cloudbuild_enabled/variables.tf | 1 + test/fixtures/simple/variables.tf | 1 + test/setup/variables.tf | 2 ++ variables.tf | 2 +- 15 files changed, 15 insertions(+), 28 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ffae759..6458f256 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,6 @@ export TF_VAR_folder_id="your_folder_id" export TF_VAR_billing_account="your_billing_account_id" export TF_VAR_group_org_admins="gcp-organization-admins@example.com" export TF_VAR_group_billing_admins="gcp-billing-admins@example.com" -export TF_VAR_default_region="your-default-region1" ``` With these settings in place, you can prepare a test project using Docker: diff --git a/README.md b/README.md index 28757794..db00bf5d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). |------|-------------|:----:|:-----:|:-----:| | activate\_apis | List of APIs to enable in the seed project. | list(string) | `` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | -| default\_region | Default region to create resources where applicable. | string | `"us-central-1"` | no | +| default\_region | Default region to create resources where applicable. | string | `"us-central1"` | no | | folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index d0970f17..1cd57dd7 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -8,7 +8,7 @@ This example combines the Organization bootstrap module with the Cloud Build sub | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | -| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| default\_region | Default region to create resources where applicable. | string | `"us-central1"` | no | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | org\_id | GCP Organization ID | string | n/a | yes | @@ -22,8 +22,6 @@ This example combines the Organization bootstrap module with the Cloud Build sub | csr\_repos | | | gcs\_bucket\_cloudbuild\_artifacts | | | gcs\_bucket\_tfstate | | -| group\_billing\_admins | | -| group\_org\_admins | | | kms\_crypto\_key | | | kms\_keyring | | | seed\_project\_id | | diff --git a/examples/cloudbuild_enabled/outputs.tf b/examples/cloudbuild_enabled/outputs.tf index 677c2994..cc9ad3ab 100644 --- a/examples/cloudbuild_enabled/outputs.tf +++ b/examples/cloudbuild_enabled/outputs.tf @@ -14,14 +14,6 @@ * limitations under the License. */ -output "group_org_admins" { - value = var.group_org_admins -} - -output "group_billing_admins" { - value = var.group_billing_admins -} - output "seed_project_id" { value = module.seed_bootstrap.seed_project_id } diff --git a/examples/cloudbuild_enabled/variables.tf b/examples/cloudbuild_enabled/variables.tf index 0f914599..95b86d4b 100644 --- a/examples/cloudbuild_enabled/variables.tf +++ b/examples/cloudbuild_enabled/variables.tf @@ -32,6 +32,8 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." + type = string + default = "us-central1" } variable "org_project_creators" { diff --git a/examples/simple/README.md b/examples/simple/README.md index 000cec54..3bd8150e 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -8,7 +8,7 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | -| default\_region | Default region to create resources where applicable. | string | n/a | yes | +| default\_region | Default region to create resources where applicable. | string | `"us-central1"` | no | | group\_billing\_admins | Google Group for GCP Billing Administrators | string | n/a | yes | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | org\_id | GCP Organization ID | string | n/a | yes | @@ -19,8 +19,6 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | Name | Description | |------|-------------| | gcs\_bucket\_tfstate | | -| group\_billing\_admins | | -| group\_org\_admins | | | seed\_project\_id | | | terraform\_sa\_email | | | terraform\_sa\_name | | diff --git a/examples/simple/outputs.tf b/examples/simple/outputs.tf index d98988c0..944608cd 100644 --- a/examples/simple/outputs.tf +++ b/examples/simple/outputs.tf @@ -14,14 +14,6 @@ * limitations under the License. */ -output "group_org_admins" { - value = var.group_org_admins -} - -output "group_billing_admins" { - value = var.group_billing_admins -} - output "seed_project_id" { value = module.seed_bootstrap.seed_project_id } diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf index 0f914599..95b86d4b 100644 --- a/examples/simple/variables.tf +++ b/examples/simple/variables.tf @@ -32,6 +32,8 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." + type = string + default = "us-central1" } variable "org_project_creators" { diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index ff2b3afa..3ca3eb04 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -55,7 +55,7 @@ Functional examples and sample Cloud Build definitions are included in the [exam | activate\_apis | List of APIs to enable in the Cloudbuild project. | list(string) | `` | no | | billing\_account | The ID of the billing account to associate projects with. | string | n/a | yes | | cloud\_source\_repos | List of Cloud Source Repo's to create with CloudBuild triggers. | list(string) | `` | no | -| default\_region | Default region to create resources where applicable. | string | `"us-central-1"` | no | +| default\_region | Default region to create resources where applicable. | string | `"us-central1"` | no | | folder\_id | The ID of a folder to host this project | string | `""` | no | | group\_org\_admins | Google Group for GCP Organization Administrators | string | n/a | yes | | org\_id | GCP Organization ID | string | n/a | yes | diff --git a/modules/cloudbuild/variables.tf b/modules/cloudbuild/variables.tf index 92507339..46c8c2a1 100644 --- a/modules/cloudbuild/variables.tf +++ b/modules/cloudbuild/variables.tf @@ -37,7 +37,7 @@ variable "group_org_admins" { variable "default_region" { description = "Default region to create resources where applicable." type = string - default = "us-central-1" + default = "us-central1" } variable "terraform_sa_email" { diff --git a/test/fixtures/cloudbuild_enabled/outputs.tf b/test/fixtures/cloudbuild_enabled/outputs.tf index 6d6b91d7..391bedf9 100644 --- a/test/fixtures/cloudbuild_enabled/outputs.tf +++ b/test/fixtures/cloudbuild_enabled/outputs.tf @@ -15,7 +15,7 @@ */ output "group_org_admins" { - value = module.cloudbuild_enabled.group_org_admins + value = var.group_org_admins } output "seed_project_id" { diff --git a/test/fixtures/cloudbuild_enabled/variables.tf b/test/fixtures/cloudbuild_enabled/variables.tf index 2425ce04..0285956b 100644 --- a/test/fixtures/cloudbuild_enabled/variables.tf +++ b/test/fixtures/cloudbuild_enabled/variables.tf @@ -47,6 +47,7 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." type = string + default = "us-central1" } variable "org_project_creators" { diff --git a/test/fixtures/simple/variables.tf b/test/fixtures/simple/variables.tf index 2425ce04..0285956b 100644 --- a/test/fixtures/simple/variables.tf +++ b/test/fixtures/simple/variables.tf @@ -47,6 +47,7 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." type = string + default = "us-central1" } variable "org_project_creators" { diff --git a/test/setup/variables.tf b/test/setup/variables.tf index 1e1576f1..30effff1 100644 --- a/test/setup/variables.tf +++ b/test/setup/variables.tf @@ -35,4 +35,6 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." + type = string + default = "us-central1" } diff --git a/variables.tf b/variables.tf index 1a179363..bea477a7 100644 --- a/variables.tf +++ b/variables.tf @@ -41,7 +41,7 @@ variable "group_billing_admins" { variable "default_region" { description = "Default region to create resources where applicable." type = string - default = "us-central-1" + default = "us-central1" } /****************************************** From 7719c16a267b87710dd81b4b7e8e4d2e7c570fb5 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 20 Nov 2019 15:48:36 +1100 Subject: [PATCH 25/27] Remove environment variable, no longer required --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 8d6309b1..1a23e1a1 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ docker_test_prepare: -e TF_VAR_billing_account \ -e TF_VAR_group_org_admins \ -e TF_VAR_group_billing_admins \ - -e TF_VAR_default_region \ -v $(CURDIR):/workspace \ $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ /usr/local/bin/execute_with_credentials.sh prepare_environment @@ -56,7 +55,6 @@ docker_test_cleanup: -e TF_VAR_billing_account \ -e TF_VAR_group_org_admins \ -e TF_VAR_group_billing_admins \ - -e TF_VAR_default_region \ -v $(CURDIR):/workspace \ $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ /usr/local/bin/execute_with_credentials.sh cleanup_environment From 428b22858d0dbfbdb5f01647cae444fc43da0bc7 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Wed, 20 Nov 2019 21:17:58 +1100 Subject: [PATCH 26/27] Address remaining pull request feedback --- README.md | 4 +-- examples/cloudbuild_enabled/README.md | 18 +++++------ examples/cloudbuild_enabled/outputs.tf | 27 +++++++++++------ examples/cloudbuild_enabled/variables.tf | 4 +++ examples/simple/README.md | 8 ++--- examples/simple/outputs.tf | 12 +++++--- examples/simple/variables.tf | 4 +++ main.tf | 7 ++--- modules/cloudbuild/README.md | 6 ++-- .../cloudbuild/cloudbuild_builder/README.md | 11 ++----- modules/cloudbuild/main.tf | 7 ++--- modules/cloudbuild/outputs.tf | 9 ++++-- outputs.tf | 4 +-- test/fixtures/cloudbuild_enabled/outputs.tf | 30 ++++++++++++------- test/fixtures/simple/outputs.tf | 12 +++++--- test/setup/variables.tf | 5 ++++ 16 files changed, 102 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index db00bf5d..81886edc 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ For the cloudbuild submodule, see the README [cloudbuild](./modules/cloudbuild). |------|-------------| | gcs\_bucket\_tfstate | Bucket used for storing terraform state for foundations pipelines in seed project. | | seed\_project\_id | Project where service accounts and core APIs will be enabled. | -| terraform\_sa\_email | Email for privileged service account. | -| terraform\_sa\_name | Fully qualified name for privileged service account. | +| terraform\_sa\_email | Email for privileged service account for Terraform. | +| terraform\_sa\_name | Fully qualified name for privileged service account for Terraform. | diff --git a/examples/cloudbuild_enabled/README.md b/examples/cloudbuild_enabled/README.md index 1cd57dd7..b3acd83d 100644 --- a/examples/cloudbuild_enabled/README.md +++ b/examples/cloudbuild_enabled/README.md @@ -18,14 +18,14 @@ This example combines the Organization bootstrap module with the Cloud Build sub | Name | Description | |------|-------------| -| cloudbuild\_project\_id | | -| csr\_repos | | -| gcs\_bucket\_cloudbuild\_artifacts | | -| gcs\_bucket\_tfstate | | -| kms\_crypto\_key | | -| kms\_keyring | | -| seed\_project\_id | | -| terraform\_sa\_email | | -| terraform\_sa\_name | | +| cloudbuild\_project\_id | Project where CloudBuild configuration and terraform container image will reside. | +| csr\_repos | List of Cloud Source Repos created by the module, linked to Cloud Build triggers. | +| gcs\_bucket\_cloudbuild\_artifacts | Bucket used to store Cloud/Build artefacts in CloudBuild project. | +| gcs\_bucket\_tfstate | Bucket used for storing terraform state for foundations pipelines in seed project. | +| kms\_crypto\_key | KMS key created by the module. | +| kms\_keyring | KMS Keyring created by the module. | +| seed\_project\_id | Project where service accounts and core APIs will be enabled. | +| terraform\_sa\_email | Email for privileged service account for Terraform. | +| terraform\_sa\_name | Fully qualified name for privileged service account for Terraform. | diff --git a/examples/cloudbuild_enabled/outputs.tf b/examples/cloudbuild_enabled/outputs.tf index cc9ad3ab..7aa983f1 100644 --- a/examples/cloudbuild_enabled/outputs.tf +++ b/examples/cloudbuild_enabled/outputs.tf @@ -15,37 +15,46 @@ */ output "seed_project_id" { - value = module.seed_bootstrap.seed_project_id + description = "Project where service accounts and core APIs will be enabled." + value = module.seed_bootstrap.seed_project_id } output "terraform_sa_email" { - value = module.seed_bootstrap.terraform_sa_email + description = "Email for privileged service account for Terraform." + value = module.seed_bootstrap.terraform_sa_email } output "terraform_sa_name" { - value = module.seed_bootstrap.terraform_sa_name + description = "Fully qualified name for privileged service account for Terraform." + value = module.seed_bootstrap.terraform_sa_name } output "gcs_bucket_tfstate" { - value = module.seed_bootstrap.gcs_bucket_tfstate + description = "Bucket used for storing terraform state for foundations pipelines in seed project." + value = module.seed_bootstrap.gcs_bucket_tfstate } output "cloudbuild_project_id" { - value = module.cloudbuild_bootstrap.cloudbuild_project_id + description = "Project where CloudBuild configuration and terraform container image will reside." + value = module.cloudbuild_bootstrap.cloudbuild_project_id } output "gcs_bucket_cloudbuild_artifacts" { - value = module.cloudbuild_bootstrap.gcs_bucket_cloudbuild_artifacts + description = "Bucket used to store Cloud/Build artefacts in CloudBuild project." + value = module.cloudbuild_bootstrap.gcs_bucket_cloudbuild_artifacts } output "csr_repos" { - value = module.cloudbuild_bootstrap.csr_repos + description = "List of Cloud Source Repos created by the module, linked to Cloud Build triggers." + value = module.cloudbuild_bootstrap.csr_repos } output "kms_keyring" { - value = module.cloudbuild_bootstrap.kms_keyring + description = "KMS Keyring created by the module." + value = module.cloudbuild_bootstrap.kms_keyring } output "kms_crypto_key" { - value = module.cloudbuild_bootstrap.kms_crypto_key + description = "KMS key created by the module." + value = module.cloudbuild_bootstrap.kms_crypto_key } diff --git a/examples/cloudbuild_enabled/variables.tf b/examples/cloudbuild_enabled/variables.tf index 95b86d4b..c955dd76 100644 --- a/examples/cloudbuild_enabled/variables.tf +++ b/examples/cloudbuild_enabled/variables.tf @@ -16,18 +16,22 @@ variable "org_id" { description = "GCP Organization ID" + type = string } variable "billing_account" { description = "The ID of the billing account to associate projects with." + type = string } variable "group_org_admins" { description = "Google Group for GCP Organization Administrators" + type = string } variable "group_billing_admins" { description = "Google Group for GCP Billing Administrators" + type = string } variable "default_region" { diff --git a/examples/simple/README.md b/examples/simple/README.md index 3bd8150e..4752a593 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -18,9 +18,9 @@ This example demonstrates the simplest usage of the GCP organization bootstrap m | Name | Description | |------|-------------| -| gcs\_bucket\_tfstate | | -| seed\_project\_id | | -| terraform\_sa\_email | | -| terraform\_sa\_name | | +| gcs\_bucket\_tfstate | Bucket used for storing terraform state for foundations pipelines in seed project. | +| seed\_project\_id | Project where service accounts and core APIs will be enabled. | +| terraform\_sa\_email | Email for privileged service account for Terraform. | +| terraform\_sa\_name | Fully qualified name for privileged service account for Terraform. | diff --git a/examples/simple/outputs.tf b/examples/simple/outputs.tf index 944608cd..504f3158 100644 --- a/examples/simple/outputs.tf +++ b/examples/simple/outputs.tf @@ -15,17 +15,21 @@ */ output "seed_project_id" { - value = module.seed_bootstrap.seed_project_id + description = "Project where service accounts and core APIs will be enabled." + value = module.seed_bootstrap.seed_project_id } output "terraform_sa_email" { - value = module.seed_bootstrap.terraform_sa_email + description = "Email for privileged service account for Terraform." + value = module.seed_bootstrap.terraform_sa_email } output "terraform_sa_name" { - value = module.seed_bootstrap.terraform_sa_name + description = "Fully qualified name for privileged service account for Terraform." + value = module.seed_bootstrap.terraform_sa_name } output "gcs_bucket_tfstate" { - value = module.seed_bootstrap.gcs_bucket_tfstate + description = "Bucket used for storing terraform state for foundations pipelines in seed project." + value = module.seed_bootstrap.gcs_bucket_tfstate } diff --git a/examples/simple/variables.tf b/examples/simple/variables.tf index 95b86d4b..c955dd76 100644 --- a/examples/simple/variables.tf +++ b/examples/simple/variables.tf @@ -16,18 +16,22 @@ variable "org_id" { description = "GCP Organization ID" + type = string } variable "billing_account" { description = "The ID of the billing account to associate projects with." + type = string } variable "group_org_admins" { description = "Google Group for GCP Organization Administrators" + type = string } variable "group_billing_admins" { description = "Google Group for GCP Billing Administrators" + type = string } variable "default_region" { diff --git a/main.tf b/main.tf index e01bc665..26361e61 100644 --- a/main.tf +++ b/main.tf @@ -61,10 +61,9 @@ resource "google_service_account" "org_terraform" { ***********************************************/ resource "google_storage_bucket" "org_terraform_state" { - project = module.seed_project.project_id - name = format("%s-%s-%s", var.project_prefix, "tfstate", random_id.suffix.hex) - location = var.default_region - force_destroy = true + project = module.seed_project.project_id + name = format("%s-%s-%s", var.project_prefix, "tfstate", random_id.suffix.hex) + location = var.default_region } /*********************************************** diff --git a/modules/cloudbuild/README.md b/modules/cloudbuild/README.md index 3ca3eb04..c7d68dfd 100644 --- a/modules/cloudbuild/README.md +++ b/modules/cloudbuild/README.md @@ -70,10 +70,10 @@ Functional examples and sample Cloud Build definitions are included in the [exam | Name | Description | |------|-------------| | cloudbuild\_project\_id | Project where CloudBuild configuration and terraform container image will reside. | -| csr\_repos | | +| csr\_repos | List of Cloud Source Repos created by the module, linked to Cloud Build triggers. | | gcs\_bucket\_cloudbuild\_artifacts | Bucket used to store Cloud/Build artefacts in CloudBuild project. | -| kms\_crypto\_key | | -| kms\_keyring | | +| kms\_crypto\_key | KMS key created by the module. | +| kms\_keyring | KMS Keyring created by the module. | diff --git a/modules/cloudbuild/cloudbuild_builder/README.md b/modules/cloudbuild/cloudbuild_builder/README.md index 656a43a7..8ffd95e9 100644 --- a/modules/cloudbuild/cloudbuild_builder/README.md +++ b/modules/cloudbuild/cloudbuild_builder/README.md @@ -1,14 +1,9 @@ -# [Terraform](https://www.terraform.io/docs) cloud builder +# Terraform cloud builder -## Terraform cloud builder -This builder can be used to run the terraform tool in the GCE. From the Hashicorp Terraform [product page](https://www.terraform.io/): - -> HashiCorp Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source -> tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, -> edited, reviewed, and versioned. +This builder creates a [Terraform](https://www.terraform.io/) image for use in cloud build to run the [Cloud Foundation Toolkit](https://cloud.google.com/foundation-toolkit/) modules. ### Building this builder -To build this builder, run the following command in this directory. +This builder is automatically created if you use the cloudbuild terraform submodule. If you would like to build manually, run the following command in this directory. ```sh $ gcloud builds submit --config=cloudbuild.yaml ``` diff --git a/modules/cloudbuild/main.tf b/modules/cloudbuild/main.tf index d627d98a..f509dbaf 100644 --- a/modules/cloudbuild/main.tf +++ b/modules/cloudbuild/main.tf @@ -74,10 +74,9 @@ resource "google_project_iam_member" "org_admins_cloudbuild_viewer" { *******************************************/ resource "google_storage_bucket" "cloudbuild_artifacts" { - project = module.cloudbuild_project.project_id - name = format("%s-%s-%s", var.project_prefix, "cloudbuild-artifacts", random_id.suffix.hex) - location = var.default_region - force_destroy = true + project = module.cloudbuild_project.project_id + name = format("%s-%s-%s", var.project_prefix, "cloudbuild-artifacts", random_id.suffix.hex) + location = var.default_region } /****************************************** diff --git a/modules/cloudbuild/outputs.tf b/modules/cloudbuild/outputs.tf index 677cfa13..a50fff0c 100644 --- a/modules/cloudbuild/outputs.tf +++ b/modules/cloudbuild/outputs.tf @@ -25,13 +25,16 @@ output "gcs_bucket_cloudbuild_artifacts" { } output "csr_repos" { - value = google_sourcerepo_repository.gcp_repo + description = "List of Cloud Source Repos created by the module, linked to Cloud Build triggers." + value = google_sourcerepo_repository.gcp_repo } output "kms_keyring" { - value = google_kms_key_ring.tf_keyring + description = "KMS Keyring created by the module." + value = google_kms_key_ring.tf_keyring } output "kms_crypto_key" { - value = google_kms_crypto_key.tf_key + description = "KMS key created by the module." + value = google_kms_crypto_key.tf_key } diff --git a/outputs.tf b/outputs.tf index 186cd041..f0d41197 100644 --- a/outputs.tf +++ b/outputs.tf @@ -28,12 +28,12 @@ output "seed_project_id" { *******************************************/ output "terraform_sa_email" { - description = "Email for privileged service account." + description = "Email for privileged service account for Terraform." value = google_service_account.org_terraform.email } output "terraform_sa_name" { - description = "Fully qualified name for privileged service account." + description = "Fully qualified name for privileged service account for Terraform." value = google_service_account.org_terraform.name } diff --git a/test/fixtures/cloudbuild_enabled/outputs.tf b/test/fixtures/cloudbuild_enabled/outputs.tf index 391bedf9..dfb88703 100644 --- a/test/fixtures/cloudbuild_enabled/outputs.tf +++ b/test/fixtures/cloudbuild_enabled/outputs.tf @@ -15,41 +15,51 @@ */ output "group_org_admins" { - value = var.group_org_admins + description = "Google Group for GCP Organization Administrators" + value = var.group_org_admins } output "seed_project_id" { - value = module.cloudbuild_enabled.seed_project_id + description = "Project where service accounts and core APIs will be enabled." + value = module.cloudbuild_enabled.seed_project_id } output "terraform_sa_email" { - value = module.cloudbuild_enabled.terraform_sa_email + description = "Email for privileged service account for Terraform." + value = module.cloudbuild_enabled.terraform_sa_email } output "terraform_sa_name" { - value = module.cloudbuild_enabled.terraform_sa_name + description = "Fully qualified name for privileged service account for Terraform." + value = module.cloudbuild_enabled.terraform_sa_name } output "gcs_bucket_tfstate" { - value = module.cloudbuild_enabled.gcs_bucket_tfstate + description = "Bucket used for storing terraform state for foundations pipelines in seed project." + value = module.cloudbuild_enabled.gcs_bucket_tfstate } output "cloudbuild_project_id" { - value = module.cloudbuild_enabled.cloudbuild_project_id + description = "Project where CloudBuild configuration and terraform container image will reside." + value = module.cloudbuild_enabled.cloudbuild_project_id } output "gcs_bucket_cloudbuild_artifacts" { - value = module.cloudbuild_enabled.gcs_bucket_cloudbuild_artifacts + description = "Bucket used to store Cloud/Build artefacts in CloudBuild project." + value = module.cloudbuild_enabled.gcs_bucket_cloudbuild_artifacts } output "csr_repos" { - value = module.cloudbuild_enabled.csr_repos + description = "List of Cloud Source Repos created by the module, linked to Cloud Build triggers." + value = module.cloudbuild_enabled.csr_repos } output "kms_keyring" { - value = module.cloudbuild_enabled.kms_keyring + description = "KMS Keyring created by the module." + value = module.cloudbuild_enabled.kms_keyring } output "kms_crypto_key" { - value = module.cloudbuild_enabled.kms_crypto_key + description = "KMS key created by the module." + value = module.cloudbuild_enabled.kms_crypto_key } diff --git a/test/fixtures/simple/outputs.tf b/test/fixtures/simple/outputs.tf index bee7de1a..a514416a 100644 --- a/test/fixtures/simple/outputs.tf +++ b/test/fixtures/simple/outputs.tf @@ -15,17 +15,21 @@ */ output "seed_project_id" { - value = module.simple.seed_project_id + description = "Project where service accounts and core APIs will be enabled." + value = module.simple.seed_project_id } output "terraform_sa_email" { - value = module.simple.terraform_sa_email + description = "Email for privileged service account for Terraform." + value = module.simple.terraform_sa_email } output "terraform_sa_name" { - value = module.simple.terraform_sa_name + description = "Fully qualified name for privileged service account for Terraform." + value = module.simple.terraform_sa_name } output "gcs_bucket_tfstate" { - value = module.simple.gcs_bucket_tfstate + description = "Bucket used for storing terraform state for foundations pipelines in seed project." + value = module.simple.gcs_bucket_tfstate } diff --git a/test/setup/variables.tf b/test/setup/variables.tf index 30effff1..f56d667f 100644 --- a/test/setup/variables.tf +++ b/test/setup/variables.tf @@ -15,22 +15,27 @@ */ variable "org_id" { description = "The numeric organization id" + type = string } variable "folder_id" { description = "The folder to deploy in" + type = string } variable "billing_account" { description = "The billing account id associated with the project, e.g. XXXXXX-YYYYYY-ZZZZZZ" + type = string } variable "group_org_admins" { description = "Google Group for GCP Organization Administrators" + type = string } variable "group_billing_admins" { description = "Google Group for GCP Billing Administrators" + type = string } variable "default_region" { From e3b70649392868f4b6d35d322718130b66eafdb1 Mon Sep 17 00:00:00 2001 From: Rohan Jerrems Date: Fri, 22 Nov 2019 08:28:24 +1100 Subject: [PATCH 27/27] Remove make_source.sh as no longer required --- test/setup/make_source.sh | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100755 test/setup/make_source.sh diff --git a/test/setup/make_source.sh b/test/setup/make_source.sh deleted file mode 100755 index 7aa1f9a8..00000000 --- a/test/setup/make_source.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -echo "#!/usr/bin/env bash" > ../source.sh - -TF_VARS="project_id org_id folder_id billing_account group_org_admins group_billing_admins default_region org_project_creators" - -for TF_VAR in $TF_VARS -do - TF_VAR_VAL=$(terraform output "${TF_VAR}") - echo "export TF_VAR_${TF_VAR}='$TF_VAR_VAL'" >> ../source.sh -done - -sa_json=$(terraform output sa_key) -# shellcheck disable=SC2086 -echo "export SERVICE_ACCOUNT_JSON='$(echo $sa_json | base64 --decode)'" >> ../source.sh