diff --git a/.ci/cloudbuild.test.yaml b/.ci/cloudbuild.test.yaml index 75b90b92..d1945de5 100644 --- a/.ci/cloudbuild.test.yaml +++ b/.ci/cloudbuild.test.yaml @@ -40,6 +40,8 @@ steps: entrypoint: pytest args: - -vv + - tests/cloud_operations + - tests/data_solutions - tests/foundations - tests/networking env: diff --git a/README.md b/README.md index 465f37d9..fa2d2864 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ The current list of modules supports most of the core foundational and networkin Currently available modules: -- **foundational** - [folders](./modules/folders), [log sinks](./modules/logging-sinks), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-accounts) +- **foundational** - [folder](./modules/folder), [log sinks](./modules/logging-sinks), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-account) - **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [GKE cluster](./modules/gke-cluster), [GKE nodepool](./modules/gke-nodepool), [COS container](./modules/cos-container) (coredns, mysql, onprem, squid) - **data** - [GCS](./modules/gcs), [BigQuery dataset](./modules/bigquery-dataset), [Pub/Sub](./modules/pubsub), [Datafusion](./modules/datafusion), [Bigtable instance](./modules/bigtable-instance) diff --git a/cloud-operations/asset-inventory-feed-remediation/main.tf b/cloud-operations/asset-inventory-feed-remediation/main.tf index 0d6b29ef..17f48697 100644 --- a/cloud-operations/asset-inventory-feed-remediation/main.tf +++ b/cloud-operations/asset-inventory-feed-remediation/main.tf @@ -41,8 +41,7 @@ module "project" { "compute.zoneOperations.list" ] } - iam_roles = [local.role_id] - iam_members = { + iam = { (local.role_id) = [module.service-account.iam_email] } } @@ -64,10 +63,7 @@ module "pubsub" { project_id = module.project.project_id name = var.name subscriptions = { "${var.name}-default" = null } - iam_roles = [ - "roles/pubsub.publisher" - ] - iam_members = { + iam = { "roles/pubsub.publisher" = [ "serviceAccount:${module.project.service_accounts.robots.cloudasset}" ] @@ -75,9 +71,9 @@ module "pubsub" { } module "service-account" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = module.project.project_id - names = ["${var.name}-cf"] + name = "${var.name}-cf" # iam_project_roles = { (module.project.project_id) = [local.role_id] } } diff --git a/cloud-operations/dns-fine-grained-iam/main.tf b/cloud-operations/dns-fine-grained-iam/main.tf index 35e59dc0..19675295 100644 --- a/cloud-operations/dns-fine-grained-iam/main.tf +++ b/cloud-operations/dns-fine-grained-iam/main.tf @@ -77,26 +77,22 @@ module "service-directory" { project_id = module.project.project_id location = var.region name = var.name - iam_members = { + iam = { "roles/servicedirectory.editor" = [ module.vm-ns-editor.service_account_iam_email ] } - iam_roles = ["roles/servicedirectory.editor"] services = { app1 = { endpoints = ["vm1", "vm2"], metadata = null } app2 = { endpoints = ["vm1", "vm2"], metadata = null } } - service_iam_members = { + service_iam = { app1 = { "roles/servicedirectory.editor" = [ module.vm-svc-editor.service_account_iam_email ] } } - service_iam_roles = { - app1 = ["roles/servicedirectory.editor"] - } endpoint_config = { "app1/vm1" = { address = "127.0.0.2", port = 80, metadata = {} } "app1/vm2" = { address = "127.0.0.3", port = 80, metadata = {} } diff --git a/cloud-operations/quota-monitoring/main.tf b/cloud-operations/quota-monitoring/main.tf index 0727ae4b..171f4805 100644 --- a/cloud-operations/quota-monitoring/main.tf +++ b/cloud-operations/quota-monitoring/main.tf @@ -34,10 +34,7 @@ module "project" { disable_on_destroy = false, disable_dependent_services = false } - iam_roles = [ - "roles/monitoring.metricWriter", - ] - iam_members = { + iam = { "roles/monitoring.metricWriter" = [module.cf.service_account_iam_email] } } diff --git a/cloud-operations/scheduled-asset-inventory-export-bq/backend.tf.sample b/cloud-operations/scheduled-asset-inventory-export-bq/backend.tf.sample index 61572d61..09644750 100644 --- a/cloud-operations/scheduled-asset-inventory-export-bq/backend.tf.sample +++ b/cloud-operations/scheduled-asset-inventory-export-bq/backend.tf.sample @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC +# Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/cloud-operations/scheduled-asset-inventory-export-bq/main.tf b/cloud-operations/scheduled-asset-inventory-export-bq/main.tf index f1f07aea..9af8951b 100644 --- a/cloud-operations/scheduled-asset-inventory-export-bq/main.tf +++ b/cloud-operations/scheduled-asset-inventory-export-bq/main.tf @@ -17,6 +17,7 @@ ############################################################################### # Projects # ############################################################################### + module "project" { source = "../../modules/project" name = var.project_id @@ -35,9 +36,9 @@ module "project" { } module "service-account" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = module.project.project_id - names = ["${var.name}-cf"] + name = "${var.name}-cf" iam_project_roles = { (var.project_id) = ["roles/cloudasset.viewer"] } @@ -46,6 +47,7 @@ module "service-account" { ############################################################################### # Pub/Sub # ############################################################################### + module "pubsub" { source = "../../modules/pubsub" project_id = module.project.project_id @@ -60,6 +62,7 @@ module "pubsub" { ############################################################################### # Cloud Function # ############################################################################### + module "cf" { source = "../../modules/cloud-function" project_id = module.project.project_id @@ -88,6 +91,7 @@ resource "random_pet" "random" { ############################################################################### # Cloud Scheduler # ############################################################################### + resource "google_app_engine_application" "app" { project = module.project.project_id location_id = var.location @@ -116,6 +120,7 @@ resource "google_cloud_scheduler_job" "job" { ############################################################################### # Bigquery # ############################################################################### + module "bq" { source = "../../modules/bigquery-dataset" project_id = module.project.project_id diff --git a/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf b/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf deleted file mode 100644 index 057095c0..00000000 --- a/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2020 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. - -terraform { - required_version = ">= 0.12.6" -} diff --git a/data-solutions/cmek-via-centralized-kms/main.tf b/data-solutions/cmek-via-centralized-kms/main.tf index 08b7b7de..6f7a388c 100644 --- a/data-solutions/cmek-via-centralized-kms/main.tf +++ b/data-solutions/cmek-via-centralized-kms/main.tf @@ -79,10 +79,7 @@ module "kms" { location = var.location } keys = { key-gce = null, key-gcs = null } - key_iam_roles = { - key-gce = ["roles/cloudkms.cryptoKeyEncrypterDecrypter"] - } - key_iam_members = { + key_iam = { key-gce = { "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ "serviceAccount:${module.project-service.service_accounts.robots.compute}", @@ -145,11 +142,9 @@ module "kms_vm_example" { ############################################################################### module "kms-gcs" { - source = "../../modules/gcs" - project_id = module.project-service.project_id - prefix = "my-bucket-001" - names = ["kms-gcs"] - encryption_keys = { - kms-gcs = module.kms.keys.key-gce.self_link, - } + source = "../../modules/gcs" + project_id = module.project-service.project_id + prefix = "my-bucket-001" + name = "kms-gcs" + encryption_key = module.kms.keys.key-gcs.self_link } diff --git a/data-solutions/cmek-via-centralized-kms/outputs.tf b/data-solutions/cmek-via-centralized-kms/outputs.tf index 99d26e15..efde89c2 100644 --- a/data-solutions/cmek-via-centralized-kms/outputs.tf +++ b/data-solutions/cmek-via-centralized-kms/outputs.tf @@ -13,19 +13,13 @@ # limitations under the License. output "bucket" { - description = "GCS Bucket Cloud KMS crypto keys." - value = { - for bucket in module.kms-gcs.buckets : - bucket.name => bucket.url - } + description = "GCS Bucket URL." + value = module.kms-gcs.url } output "bucket_keys" { description = "GCS Bucket Cloud KMS crypto keys." - value = { - for bucket in module.kms-gcs.buckets : - bucket.name => bucket.encryption - } + value = module.kms-gcs.bucket.encryption } output "projects" { diff --git a/data-solutions/gcs-to-bq-with-dataflow/main.tf b/data-solutions/gcs-to-bq-with-dataflow/main.tf index 39a9ffd3..f9fda8aa 100644 --- a/data-solutions/gcs-to-bq-with-dataflow/main.tf +++ b/data-solutions/gcs-to-bq-with-dataflow/main.tf @@ -57,9 +57,9 @@ module "project-kms" { ############################################################################### module "service-account-bq" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = module.project-service.project_id - names = ["bq-test"] + name = "bq-test" iam_project_roles = { (var.project_service_name) = [ "roles/logging.logWriter", @@ -70,9 +70,9 @@ module "service-account-bq" { } module "service-account-gce" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = module.project-service.project_id - names = ["gce-test"] + name = "gce-test" iam_project_roles = { (var.project_service_name) = [ "roles/logging.logWriter", @@ -86,9 +86,9 @@ module "service-account-gce" { } module "service-account-df" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = module.project-service.project_id - names = ["df-test"] + name = "df-test" iam_project_roles = { (var.project_service_name) = [ "roles/dataflow.worker", @@ -120,12 +120,7 @@ module "kms" { location = var.location } keys = { key-gce = null, key-gcs = null, key-bq = null } - key_iam_roles = { - key-gce = ["roles/cloudkms.cryptoKeyEncrypterDecrypter"] - key-gcs = ["roles/cloudkms.cryptoKeyEncrypterDecrypter"] - key-bq = ["roles/cloudkms.cryptoKeyEncrypterDecrypter"] - } - key_iam_members = { + key_iam = { key-gce = { "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ "serviceAccount:${module.project-service.service_accounts.robots.compute}", @@ -155,10 +150,7 @@ module "kms-regional" { location = var.region } keys = { key-df = null } - key_iam_roles = { - key-df = ["roles/cloudkms.cryptoKeyEncrypterDecrypter"] - } - key_iam_members = { + key_iam = { key-df = { "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [ "serviceAccount:${module.project-service.service_accounts.robots.dataflow}", @@ -254,38 +246,33 @@ module "vm_example" { ############################################################################### module "kms-gcs" { - source = "../../modules/gcs" - project_id = module.project-service.project_id - prefix = module.project-service.project_id - names = ["data", "df-tmplocation"] - iam_roles = { - data = ["roles/storage.admin", "roles/storage.objectViewer"], - df-tmplocation = ["roles/storage.admin"] - } - iam_members = { + source = "../../modules/gcs" + for_each = { data = { - "roles/storage.admin" = [ - "serviceAccount:${module.service-account-gce.email}", - ], - "roles/storage.viewer" = [ - "serviceAccount:${module.service-account-df.email}", - ], - }, + members = { + "roles/storage.admin" = [ + "serviceAccount:${module.service-account-gce.email}", + ], + "roles/storage.objectViewer" = [ + "serviceAccount:${module.service-account-df.email}", + ] + } + } df-tmplocation = { - "roles/storage.admin" = [ - "serviceAccount:${module.service-account-gce.email}", - "serviceAccount:${module.service-account-df.email}", - ] + members = { + "roles/storage.admin" = [ + "serviceAccount:${module.service-account-gce.email}", + "serviceAccount:${module.service-account-df.email}", + ] + } } } - encryption_keys = { - data = module.kms.keys.key-gcs.self_link, - df-tmplocation = module.kms.keys.key-gcs.self_link, - } - force_destroy = { - data = true, - df-tmplocation = true, - } + project_id = module.project-service.project_id + prefix = module.project-service.project_id + name = each.key + iam = each.value.members + encryption_key = module.kms.keys.key-gcs.self_link + force_destroy = true } ############################################################################### @@ -297,10 +284,11 @@ module "bigquery-dataset" { project_id = module.project-service.project_id id = "bq_dataset" access_roles = { - reader-group = { role = "READER", type = "domain" } + reader-group = { role = "READER", type = "service_account" } owner = { role = "OWNER", type = "user_by_email" } } access_identities = { + reader-group = module.service-account-bq.email owner = module.service-account-bq.email } encryption_key = module.kms.keys.key-bq.self_link diff --git a/data-solutions/gcs-to-bq-with-dataflow/outputs.tf b/data-solutions/gcs-to-bq-with-dataflow/outputs.tf index 9dc4de31..af683896 100644 --- a/data-solutions/gcs-to-bq-with-dataflow/outputs.tf +++ b/data-solutions/gcs-to-bq-with-dataflow/outputs.tf @@ -14,13 +14,13 @@ output "bq_tables" { description = "Bigquery Tables." - value = module.bigquery-dataset.table_ids + value = module.bigquery-dataset.table_ids } output "buckets" { description = "GCS Bucket Cloud KMS crypto keys." value = { - for bucket in module.kms-gcs.buckets : + for name, bucket in module.kms-gcs : bucket.name => bucket.url } } diff --git a/foundations/business-units/main.tf b/foundations/business-units/main.tf index b30b5c46..39fecf6f 100644 --- a/foundations/business-units/main.tf +++ b/foundations/business-units/main.tf @@ -21,9 +21,9 @@ # Shared folder module "shared-folder" { - source = "../../modules/folders" + source = "../../modules/folder" parent = var.root_node - names = ["shared"] + name = "shared" } # Terraform project @@ -34,7 +34,7 @@ module "tf-project" { parent = module.shared-folder.id prefix = var.prefix billing_account = var.billing_account_id - iam_additive_bindings = { + iam_additive = { for name in var.iam_terraform_owners : (name) => ["roles/owner"] } services = var.project_services @@ -45,7 +45,7 @@ module "tf-project" { module "tf-gcs-bootstrap" { source = "../../modules/gcs" project_id = module.tf-project.project_id - names = ["tf-bootstrap"] + name = "tf-bootstrap" prefix = "${var.prefix}-tf" location = var.gcs_defaults.location } @@ -96,14 +96,10 @@ module "audit-project" { parent = var.root_node prefix = var.prefix billing_account = var.billing_account_id - iam_members = { + iam = { "roles/bigquery.dataEditor" = [module.audit-log-sinks.writer_identities[0]] "roles/viewer" = var.iam_audit_viewers } - iam_roles = [ - "roles/bigquery.dataEditor", - "roles/viewer" - ] services = concat(var.project_services, [ "bigquery.googleapis.com", ]) @@ -147,7 +143,7 @@ module "shared-project" { parent = module.shared-folder.id prefix = var.prefix billing_account = var.billing_account_id - iam_additive_bindings = { + iam_additive = { for name in var.iam_shared_owners : (name) => ["roles/owner"] } services = var.project_services diff --git a/foundations/environments/README.md b/foundations/environments/README.md index 338c7286..8993e599 100644 --- a/foundations/environments/README.md +++ b/foundations/environments/README.md @@ -33,7 +33,7 @@ If no shared services are needed, the shared service project module can of cours | name | description | type | required | default | |---|---|:---: |:---:|:---:| | billing_account_id | Billing account id used as to create projects. | string | ✓ | | -| environments | Environment short names. | list(string) | ✓ | | +| environments | Environment short names. | set(string) | ✓ | | | organization_id | Organization id in organizations/nnnnnnnn format. | string | ✓ | | | prefix | Prefix used for resources that need unique names. | string | ✓ | | | root_node | Root node for the new hierarchy, either 'organizations/org_id' or 'folders/folder_id'. | string | ✓ | | diff --git a/foundations/environments/main.tf b/foundations/environments/main.tf index 701bca93..48ac7568 100644 --- a/foundations/environments/main.tf +++ b/foundations/environments/main.tf @@ -24,7 +24,7 @@ module "tf-project" { parent = var.root_node prefix = var.prefix billing_account = var.billing_account_id - iam_additive_bindings = { + iam_additive = { for name in var.iam_terraform_owners : (name) => ["roles/owner"] } services = var.project_services @@ -33,9 +33,10 @@ module "tf-project" { # per-environment service accounts module "tf-service-accounts" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" + for_each = var.environments project_id = module.tf-project.project_id - names = var.environments + name = each.value prefix = var.prefix iam_billing_roles = { (var.billing_account_id) = ( @@ -49,7 +50,7 @@ module "tf-service-accounts" { var.iam_xpn_config.grant ? local.sa_xpn_org_roles : [] ) } - generate_keys = var.service_account_keys + generate_key = var.service_account_keys } # bootstrap Terraform state GCS bucket @@ -57,7 +58,7 @@ module "tf-service-accounts" { module "tf-gcs-bootstrap" { source = "../../modules/gcs" project_id = module.tf-project.project_id - names = ["tf-bootstrap"] + name = "tf-bootstrap" prefix = "${var.prefix}-tf" location = var.gcs_location } @@ -66,17 +67,13 @@ module "tf-gcs-bootstrap" { module "tf-gcs-environments" { source = "../../modules/gcs" + for_each = var.environments project_id = module.tf-project.project_id - names = var.environments + name = each.value prefix = "${var.prefix}-tf" location = var.gcs_location - iam_roles = { - for name in var.environments : (name) => ["roles/storage.objectAdmin"] - } - iam_members = { - for name in var.environments : (name) => { - "roles/storage.objectAdmin" = [module.tf-service-accounts.iam_emails[name]] - } + iam = { + "roles/storage.objectAdmin" = [module.tf-service-accounts[each.value].iam_email] } } @@ -85,17 +82,13 @@ module "tf-gcs-environments" { ############################################################################### module "environment-folders" { - source = "../../modules/folders" - parent = var.root_node - names = var.environments - iam_roles = { - for name in var.environments : (name) => local.folder_roles - } - iam_members = { - for name in var.environments : (name) => { - for role in local.folder_roles : - (role) => [module.tf-service-accounts.iam_emails[name]] - } + source = "../../modules/folder" + for_each = var.environments + parent = var.root_node + name = each.value + iam = { + for role in local.folder_roles : + (role) => [module.tf-service-accounts[each.value].iam_email] } } @@ -111,14 +104,10 @@ module "audit-project" { parent = var.root_node prefix = var.prefix billing_account = var.billing_account_id - iam_members = { + iam = { "roles/bigquery.dataEditor" = [module.audit-log-sinks.writer_identities[0]] "roles/viewer" = var.iam_audit_viewers } - iam_roles = [ - "roles/bigquery.dataEditor", - "roles/viewer" - ] services = concat(var.project_services, [ "bigquery.googleapis.com", ]) @@ -163,7 +152,7 @@ module "sharedsvc-project" { parent = var.root_node prefix = var.prefix billing_account = var.billing_account_id - iam_additive_bindings = { + iam_additive = { for name in var.iam_shared_owners : (name) => ["roles/owner"] } services = var.project_services diff --git a/foundations/environments/outputs.tf b/foundations/environments/outputs.tf index 495de1f7..a550b6a6 100644 --- a/foundations/environments/outputs.tf +++ b/foundations/environments/outputs.tf @@ -24,23 +24,23 @@ output "bootstrap_tf_gcs_bucket" { output "environment_folders" { description = "Top-level environment folders." - value = module.environment-folders.ids + value = { for folder in module.environment-folders : folder.name => folder.id } } output "environment_tf_gcs_buckets" { description = "GCS buckets used for each environment Terraform state." - value = module.tf-gcs-environments.names + value = { for env, bucket in module.tf-gcs-environments : env => bucket.name } } output "environment_service_account_keys" { description = "Service account keys used to run each environment Terraform modules." sensitive = true - value = module.tf-service-accounts.keys + value = { for env, sa in module.tf-service-accounts : env => sa.key } } output "environment_service_accounts" { description = "Service accounts used to run each environment Terraform modules." - value = module.tf-service-accounts.emails + value = { for env, sa in module.tf-service-accounts : env => sa.email } } output "audit_logs_bq_dataset" { diff --git a/foundations/environments/variables.tf b/foundations/environments/variables.tf index 436d8557..7fa2e500 100644 --- a/foundations/environments/variables.tf +++ b/foundations/environments/variables.tf @@ -29,7 +29,7 @@ variable "billing_account_id" { variable "environments" { description = "Environment short names." - type = list(string) + type = set(string) } variable "gcs_location" { diff --git a/modules/README.md b/modules/README.md index 722b5ee4..c9ab6f2d 100644 --- a/modules/README.md +++ b/modules/README.md @@ -10,11 +10,11 @@ Specific modules also offer support for non-authoritative bindings (e.g. `google ## Foundational modules -- [folders](./folders) +- [folder](./folder) - [log sinks](./logging-sinks) - [organization](./organization) - [project](./project) -- [service accounts](./iam-service-accounts) +- [service account](./iam-service-account) ## Networking modules diff --git a/modules/artifact-registry/README.md b/modules/artifact-registry/README.md index 480ea23a..49fd84c2 100644 --- a/modules/artifact-registry/README.md +++ b/modules/artifact-registry/README.md @@ -13,8 +13,7 @@ module "docker_artifact_registry" { location = "europe-west1" format = "DOCKER" id = "myregistry" - iam_roles = ["roles/artifactregistry.admin"] - iam_members = { + iam = { "roles/artifactregistry.admin" = ["group:cicd@example.com"] } } @@ -29,8 +28,7 @@ module "docker_artifact_registry" { | project_id | Registry project id. | string | ✓ | | | *description* | An optional description for the repository | string | | Terraform-managed registry | | *format* | Repository format. One of DOCKER or UNSPECIFIED | string | | DOCKER | -| *iam_members* | Map of member lists used to set authoritative bindings, keyed by role. | map(list(string)) | | {} | -| *iam_roles* | List of roles used to set authoritative bindings. | list(string) | | [] | +| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *labels* | Labels to be attached to the registry. | map(string) | | {} | | *location* | Registry location. Use `gcloud beta artifacts locations list' to get valid values | string | | | diff --git a/modules/artifact-registry/main.tf b/modules/artifact-registry/main.tf index 81c366f4..9b687873 100644 --- a/modules/artifact-registry/main.tf +++ b/modules/artifact-registry/main.tf @@ -26,10 +26,10 @@ resource "google_artifact_registry_repository" "registry" { resource "google_artifact_registry_repository_iam_binding" "bindings" { provider = google-beta - for_each = toset(var.iam_roles) + for_each = var.iam project = var.project_id location = google_artifact_registry_repository.registry.location repository = google_artifact_registry_repository.registry.name - role = each.value - members = lookup(var.iam_members, each.value, []) + role = each.key + members = each.value } diff --git a/modules/artifact-registry/variables.tf b/modules/artifact-registry/variables.tf index 7aa8fdca..b977f6c7 100644 --- a/modules/artifact-registry/variables.tf +++ b/modules/artifact-registry/variables.tf @@ -14,18 +14,12 @@ * limitations under the License. */ -variable "iam_members" { - description = "Map of member lists used to set authoritative bindings, keyed by role." +variable "iam" { + description = "IAM bindings in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "iam_roles" { - description = "List of roles used to set authoritative bindings." - type = list(string) - default = [] -} - variable "location" { description = "Registry location. Use `gcloud beta artifacts locations list' to get valid values" type = string diff --git a/modules/bigtable-instance/README.md b/modules/bigtable-instance/README.md index f63e1cf0..23aac694 100644 --- a/modules/bigtable-instance/README.md +++ b/modules/bigtable-instance/README.md @@ -13,23 +13,21 @@ This module allows managing a single BigTable instance, including access configu ```hcl -module "big-table-instance" { +module "bigtable-instance" { source = "./modules/bigtable-instance" project_id = "my-project" name = "instance" cluster_id = "instance" - instance_type = "PRODUCTION" + zone = "europe-west1-b" tables = { - test1 = { table_options = null }, - test2 = { table_options = { + test1 = null, + test2 = { split_keys = ["a", "b", "c"] column_family = null - } } } - iam_roles = ["viewer"] - iam_members = { - viewer = ["user:viewer@testdomain.com"] + iam = { + "roles/bigtable.user" = ["user:viewer@testdomain.com"] } } ``` @@ -45,13 +43,12 @@ module "big-table-instance" { | *cluster_id* | The ID of the Cloud Bigtable cluster. | string | | europe-west1 | | *deletion_protection* | Whether or not to allow Terraform to destroy the instance. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the instance will fail. | | | true | | *display_name* | The human-readable display name of the Bigtable instance. | | | null | -| *iam_members* | Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the instance are preserved. | map(list(string)) | | {} | -| *iam_roles* | Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. | list(string) | | [] | -| *instance_type* | None | string | | DEVELOPMENT | +| *iam* | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| *instance_type* | (deprecated) The instance type to create. One of 'DEVELOPMENT' or 'PRODUCTION'. | string | | null | | *num_nodes* | The number of nodes in your Cloud Bigtable cluster. | number | | 1 | | *storage_type* | The storage type to use. | string | | SSD | | *table_options_defaults* | Default option of tables created in the BigTable instance. | object({...}) | | ... | -| *tables* | Tables to be created in the BigTable instance. | map(object({...})) | | {} | +| *tables* | Tables to be created in the BigTable instance, options can be null. | map(object({...})) | | {} | ## Outputs diff --git a/modules/bigtable-instance/main.tf b/modules/bigtable-instance/main.tf index 0e7129ff..f8660606 100644 --- a/modules/bigtable-instance/main.tf +++ b/modules/bigtable-instance/main.tf @@ -16,11 +16,7 @@ locals { tables = { - for k, v in var.tables : k => v.table_options != null ? v.table_options : var.table_options_defaults - } - - iam_roles_bindings = { - for k in var.iam_roles : k => lookup(var.iam_members, k, []) + for k, v in var.tables : k => v != null ? v : var.table_options_defaults } } @@ -39,11 +35,10 @@ resource "google_bigtable_instance" "default" { } resource "google_bigtable_instance_iam_binding" "default" { - for_each = local.iam_roles_bindings - + for_each = var.iam project = var.project_id instance = google_bigtable_instance.default.name - role = "roles/bigtable.${each.key}" + role = each.key members = each.value } diff --git a/modules/bigtable-instance/outputs.tf b/modules/bigtable-instance/outputs.tf index 2012b5c6..4d7a5217 100644 --- a/modules/bigtable-instance/outputs.tf +++ b/modules/bigtable-instance/outputs.tf @@ -18,8 +18,8 @@ output "id" { description = "An identifier for the resource with format projects/{{project}}/instances/{{name}}." value = google_bigtable_instance.default.id depends_on = [ - google_bigtable_instance_iam_binding, - google_bigtable_table + google_bigtable_instance_iam_binding.default, + google_bigtable_table.default ] } @@ -27,8 +27,8 @@ output "instance" { description = "BigTable intance." value = google_bigtable_instance.default depends_on = [ - google_bigtable_instance_iam_binding, - google_bigtable_table + google_bigtable_instance_iam_binding.default, + google_bigtable_table.default ] } diff --git a/modules/bigtable-instance/variables.tf b/modules/bigtable-instance/variables.tf index 7dbbaa25..662ac5b3 100644 --- a/modules/bigtable-instance/variables.tf +++ b/modules/bigtable-instance/variables.tf @@ -14,18 +14,6 @@ * limitations under the License. */ -variable "iam_roles" { - description = "Authoritative for a given role. Updates the IAM policy to grant a role to a list of members." - type = list(string) - default = [] -} - -variable "iam_members" { - description = "Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the instance are preserved." - type = map(list(string)) - default = {} -} - variable "cluster_id" { description = "The ID of the Cloud Bigtable cluster." type = string @@ -42,10 +30,16 @@ variable "display_name" { default = null } +variable "iam" { + description = "IAM bindings for topic in {ROLE => [MEMBERS]} format." + type = map(list(string)) + default = {} +} + variable "instance_type" { - description = "The instance type to create. One of \"DEVELOPMENT\" or \"PRODUCTION\". Defaults to \"DEVELOPMENT\"" + description = "(deprecated) The instance type to create. One of 'DEVELOPMENT' or 'PRODUCTION'." type = string - default = "DEVELOPMENT" + default = null } variable "name" { @@ -71,12 +65,10 @@ variable "storage_type" { } variable "tables" { - description = "Tables to be created in the BigTable instance." + description = "Tables to be created in the BigTable instance, options can be null." type = map(object({ - table_options = object({ - split_keys = list(string) - column_family = string - }) + split_keys = list(string) + column_family = string })) default = {} } diff --git a/modules/cloud-function/README.md b/modules/cloud-function/README.md index ca9848f4..696154a1 100644 --- a/modules/cloud-function/README.md +++ b/modules/cloud-function/README.md @@ -63,8 +63,7 @@ module "cf-http" { source_dir = "my-cf-source-folder" output_path = "bundle.zip" } - iam_roles = ["roles/cloudfunctions.invoker"] - iam_members = { + iam = { "roles/cloudfunctions.invoker" = ["allUsers"] } } @@ -137,8 +136,7 @@ module "cf-http" { | *bucket_config* | Enable and configure auto-created bucket. Set fields to null to use defaults. | object({...}) | | null | | *environment_variables* | Cloud function environment variables. | map(string) | | {} | | *function_config* | Cloud function configuration. | object({...}) | | ... | -| *iam_members* | Map of member lists used to set authoritative bindings, keyed by role. Ignored for template use. | map(list(string)) | | {} | -| *iam_roles* | List of roles used to set authoritative bindings. Ignored for template use. | list(string) | | [] | +| *iam* | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *ingress_settings* | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL and ALLOW_INTERNAL_ONLY. | string | | null | | *labels* | Resource labels | map(string) | | {} | | *prefix* | Optional prefix used for resource names. | string | | null | diff --git a/modules/cloud-function/main.tf b/modules/cloud-function/main.tf index 22424765..a55fe3f5 100644 --- a/modules/cloud-function/main.tf +++ b/modules/cloud-function/main.tf @@ -95,12 +95,12 @@ resource "google_cloudfunctions_function" "function" { } resource "google_cloudfunctions_function_iam_binding" "default" { - for_each = toset(var.iam_roles) + for_each = var.iam project = var.project_id region = var.region cloud_function = google_cloudfunctions_function.function.name - role = each.value - members = try(var.iam_members[each.value], {}) + role = each.key + members = each.value } resource "google_storage_bucket" "bucket" { diff --git a/modules/cloud-function/variables.tf b/modules/cloud-function/variables.tf index eea9131f..40b67c5b 100644 --- a/modules/cloud-function/variables.tf +++ b/modules/cloud-function/variables.tf @@ -42,18 +42,6 @@ variable "environment_variables" { default = {} } -variable "iam_members" { - description = "Map of member lists used to set authoritative bindings, keyed by role. Ignored for template use." - type = map(list(string)) - default = {} -} - -variable "iam_roles" { - description = "List of roles used to set authoritative bindings. Ignored for template use." - type = list(string) - default = [] -} - variable "function_config" { description = "Cloud function configuration." type = object({ @@ -74,6 +62,12 @@ variable "function_config" { } } +variable "iam" { + description = "IAM bindings for topic in {ROLE => [MEMBERS]} format." + type = map(list(string)) + default = {} +} + variable "ingress_settings" { description = "Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL and ALLOW_INTERNAL_ONLY." type = string diff --git a/modules/compute-vm/README.md b/modules/compute-vm/README.md index 45895f28..3771452c 100644 --- a/modules/compute-vm/README.md +++ b/modules/compute-vm/README.md @@ -184,8 +184,7 @@ module "instance-group" { | *encryption* | Encryption options. Only one of kms_key_self_link and disk_encryption_key_raw may be set. If needed, you can specify to encrypt or not the boot disk. | object({...}) | | null | | *group* | Define this variable to create an instance group for instances. Disabled for template use. | object({...}) | | null | | *hostname* | Instance FQDN name. | string | | null | -| *iam_members* | Map of member lists used to set authoritative bindings, keyed by role. Ignored for template use. | map(list(string)) | | {} | -| *iam_roles* | List of roles used to set authoritative bindings. Ignored for template use. | list(string) | | [] | +| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *instance_count* | Number of instances to create (only for non-template usage). | number | | 1 | | *instance_type* | Instance type. | string | | f1-micro | | *labels* | Instance labels. | map(string) | | {} | diff --git a/modules/compute-vm/main.tf b/modules/compute-vm/main.tf index 1a911022..782c4425 100644 --- a/modules/compute-vm/main.tf +++ b/modules/compute-vm/main.tf @@ -25,9 +25,9 @@ locals { for pair in setproduct(keys(local.names), keys(local.attached_disks)) : "${pair[0]}-${pair[1]}" => { disk_name = pair[1], name = pair[0] } } - iam_roles = var.use_instance_template ? {} : { - for pair in setproduct(var.iam_roles, keys(local.names)) : - "${pair.0}/${pair.1}" => { role = pair.0, name = pair.1 } + iam_members = var.use_instance_template ? {} : { + for pair in setproduct(keys(var.iam), keys(local.names)) : + "${pair.0}/${pair.1}" => { role = pair.0, name = pair.1, members = var.iam[pair.0] } } names = ( var.use_instance_template ? { (var.name) = 0 } : { @@ -196,12 +196,12 @@ resource "google_compute_instance" "default" { } resource "google_compute_instance_iam_binding" "default" { - for_each = local.iam_roles + for_each = local.iam_members project = var.project_id zone = local.zones[each.value.name] instance_name = each.value.name role = each.value.role - members = lookup(var.iam_members, each.value.role, []) + members = each.value.members depends_on = [google_compute_instance.default] } diff --git a/modules/compute-vm/variables.tf b/modules/compute-vm/variables.tf index 02c13480..a3aa80a2 100644 --- a/modules/compute-vm/variables.tf +++ b/modules/compute-vm/variables.tf @@ -90,18 +90,12 @@ variable "hostname" { default = null } -variable "iam_members" { - description = "Map of member lists used to set authoritative bindings, keyed by role. Ignored for template use." +variable "iam" { + description = "IAM bindings in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "iam_roles" { - description = "List of roles used to set authoritative bindings. Ignored for template use." - type = list(string) - default = [] -} - variable "instance_count" { description = "Number of instances to create (only for non-template usage)." type = number diff --git a/modules/container-registry/README.md b/modules/container-registry/README.md index 167f79b3..ac382ccc 100644 --- a/modules/container-registry/README.md +++ b/modules/container-registry/README.md @@ -9,8 +9,7 @@ module "container_registry" { source = "../../modules/container-registry" project_id = "myproject" location = "EU" - iam_roles = ["roles/storage.admin"] - iam_members = { + iam = { "roles/storage.admin" = ["group:cicd@example.com"] } } @@ -22,8 +21,7 @@ module "container_registry" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| | project_id | Registry project id. | string | ✓ | | -| *iam_members* | Map of member lists used to set authoritative bindings, keyed by role. | map(list(string)) | | null | -| *iam_roles* | List of roles used to set authoritative bindings. | list(string) | | null | +| *iam* | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *location* | Registry location. Can be US, EU, ASIA or empty | string | | | ## Outputs diff --git a/modules/container-registry/main.tf b/modules/container-registry/main.tf index 073e2995..7c750e96 100644 --- a/modules/container-registry/main.tf +++ b/modules/container-registry/main.tf @@ -20,8 +20,8 @@ resource "google_container_registry" "registry" { } resource "google_storage_bucket_iam_binding" "bindings" { - for_each = toset(var.iam_roles) + for_each = var.iam bucket = google_container_registry.registry.id - role = each.value - members = lookup(var.iam_members, each.value, []) + role = each.key + members = each.value } diff --git a/modules/container-registry/variables.tf b/modules/container-registry/variables.tf index 15074aca..72f3a87e 100644 --- a/modules/container-registry/variables.tf +++ b/modules/container-registry/variables.tf @@ -14,16 +14,10 @@ * limitations under the License. */ -variable "iam_members" { - description = "Map of member lists used to set authoritative bindings, keyed by role." +variable "iam" { + description = "IAM bindings for topic in {ROLE => [MEMBERS]} format." type = map(list(string)) - default = null -} - -variable "iam_roles" { - description = "List of roles used to set authoritative bindings." - type = list(string) - default = null + default = {} } variable "location" { diff --git a/modules/dns/README.md b/modules/dns/README.md index 2c00ebb1..780e9f0b 100644 --- a/modules/dns/README.md +++ b/modules/dns/README.md @@ -37,7 +37,7 @@ module "private-dns" { | *peer_network* | Peering network self link, only valid for 'peering' zone types. | string | | null | | *recordsets* | List of DNS record objects to manage. | list(object({...})) | | [] | | *service_directory_namespace* | Service directory namespace id (URL), only valid for 'service-directory' zone types. | string | | null | -| *type* | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'. | string | | private | +| *type* | Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'. | string | | ... | | *zone_create* | Create zone. When set to false, uses a data source to reference existing zone. | bool | | true | ## Outputs diff --git a/modules/dns/main.tf b/modules/dns/main.tf index d7e98b52..40a511cc 100644 --- a/modules/dns/main.tf +++ b/modules/dns/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,10 @@ locals { zone = ( var.zone_create ? try( - google_dns_managed_zone.non-public.0, try( - google_dns_managed_zone.public.0, null - ) + google_dns_managed_zone.non-public.0, try( + google_dns_managed_zone.public.0, null ) + ) : try(data.google_dns_managed_zone.public.0, null) ) dns_keys = try( @@ -34,12 +34,12 @@ locals { } resource "google_dns_managed_zone" "non-public" { - count = (var.zone_create && var.type != "public" ) ? 1 : 0 + count = (var.zone_create && var.type != "public") ? 1 : 0 provider = google-beta project = var.project_id name = var.name dns_name = var.domain - description = "Terraform-managed zone." + description = var.description visibility = "private" dynamic forwarding_config { @@ -94,12 +94,12 @@ resource "google_dns_managed_zone" "non-public" { } data "google_dns_managed_zone" "public" { - count = var.zone_create ? 0 : 1 - name = var.name + count = var.zone_create ? 0 : 1 + name = var.name } resource "google_dns_managed_zone" "public" { - count = (var.zone_create && var.type == "public" ) ? 1 : 0 + count = (var.zone_create && var.type == "public") ? 1 : 0 project = var.project_id name = var.name dns_name = var.domain @@ -132,7 +132,7 @@ resource "google_dns_managed_zone" "public" { } data "google_dns_keys" "dns_keys" { - count = var.zone_create && ( var.dnssec_config == {} || var.type != "public" ) ? 0 : 1 + count = var.zone_create && (var.dnssec_config == {} || var.type != "public") ? 0 : 1 managed_zone = local.zone.id } diff --git a/modules/dns/variables.tf b/modules/dns/variables.tf index d6e267d9..be32349b 100644 --- a/modules/dns/variables.tf +++ b/modules/dns/variables.tf @@ -97,6 +97,10 @@ variable "type" { description = "Type of zone to create, valid values are 'public', 'private', 'forwarding', 'peering', 'service-directory'." type = string default = "private" + validation { + condition = contains(["public", "private", "forwarding", "peering", "service-directory"], var.type) + error_message = "Zone must be one of 'public', 'private', 'forwarding', 'peering', 'service-directory'." + } } variable "zone_create" { @@ -106,3 +110,4 @@ variable "zone_create" { } + diff --git a/modules/dns/versions.tf b/modules/dns/versions.tf index 9ecd9e3b..635b41dd 100644 --- a/modules/dns/versions.tf +++ b/modules/dns/versions.tf @@ -15,7 +15,7 @@ */ terraform { - required_version = ">= 0.12.20" + required_version = ">= 0.13.0" required_providers { google = "~> 3.10" google-beta = "~> 3.20" diff --git a/modules/endpoints/README.md b/modules/endpoints/README.md index f57952e4..6706c944 100644 --- a/modules/endpoints/README.md +++ b/modules/endpoints/README.md @@ -1,6 +1,6 @@ # Google Cloud Endpoints -This module allows simple management of ['Google Cloud Endpoints'](https://cloud.google.com/endpoints/) services. It supports creating ['OpenAPI'](https://cloud.google.com/endpoints/docs/openapi) or ['gRPC'](https://cloud.google.com/endpoints/docs/grpc/about-grpc) endpoints. +This module allows simple management of ['Google Cloud Endpoints'](https://cloud.google.com/endpoints/) services. It supports creating ['OpenAPI'](https://cloud.google.com/endpoints/docs/openapi) or ['gRPC'](https://cloud.google.com/endpoints/docs/grpc/about-grpc) endpoints. ## Examples @@ -12,26 +12,23 @@ module "endpoint" { project_id = "my-project" service_name = "YOUR-API.endpoints.YOUR-PROJECT-ID.cloud.goog" openapi_config = { "yaml_path" = "openapi.yaml" } - grpc_config = null - iam_roles = ["servicemanagement.serviceController"] - iam_members = { - "servicemanagement.serviceController" = ["serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com"] + iam = { + "servicemanagement.serviceController" = ["serviceAccount:123456890-compute@developer.gserviceaccount.com"] } } ``` -[Here](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/endpoints/getting-started/openapi.yaml) you can find an example of an openapi.yaml file. Once created the endpoint, remember to activate the service at project level. +[Here](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/endpoints/getting-started/openapi.yaml) you can find an example of an openapi.yaml file. Once created the endpoint, remember to activate the service at project level. ## Variables | name | description | type | required | default | |---|---|:---: |:---:|:---:| -| grpc_config | The configuration for a gRPC enpoint. Either this or openapi_config must be specified. | object({...}) | ✓ | | | openapi_config | The configuration for an OpenAPI endopoint. Either this or grpc_config must be specified. | object({...}) | ✓ | | | service_name | The name of the service. Usually of the form '$apiname.endpoints.$projectid.cloud.goog'. | string | ✓ | | -| *iam_members* | Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the instance are preserved. | map(list(string)) | | {} | -| *iam_roles* | Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. | list(string) | | [] | +| *grpc_config* | The configuration for a gRPC enpoint. Either this or openapi_config must be specified. | object({...}) | | null | +| *iam* | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *project_id* | The project ID that the service belongs to. | string | | null | ## Outputs diff --git a/modules/endpoints/main.tf b/modules/endpoints/main.tf index 1b9cedbe..782e61ff 100644 --- a/modules/endpoints/main.tf +++ b/modules/endpoints/main.tf @@ -14,12 +14,6 @@ * limitations under the License. */ -locals { - iam_roles_bindings = { - for k in var.iam_roles : k => lookup(var.iam_members, k, []) - } -} - resource "google_endpoints_service" "default" { project = var.project_id service_name = var.service_name @@ -29,8 +23,8 @@ resource "google_endpoints_service" "default" { } resource "google_endpoints_service_iam_binding" "default" { - for_each = local.iam_roles_bindings + for_each = var.iam service_name = google_endpoints_service.default.service_name - role = "roles/${each.key}" + role = each.key members = each.value } diff --git a/modules/endpoints/variables.tf b/modules/endpoints/variables.tf index 76fb8b8b..74695d8d 100644 --- a/modules/endpoints/variables.tf +++ b/modules/endpoints/variables.tf @@ -16,27 +16,22 @@ variable "grpc_config" { description = "The configuration for a gRPC enpoint. Either this or openapi_config must be specified." - type = object({ + type = object({ yaml_path = string protoc_output_path = string }) + default = null } -variable "iam_roles" { - description = "Authoritative for a given role. Updates the IAM policy to grant a role to a list of members." - type = list(string) - default = [] -} - -variable "iam_members" { - description = "Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the instance are preserved." +variable "iam" { + description = "IAM bindings for topic in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } variable "openapi_config" { description = "The configuration for an OpenAPI endopoint. Either this or grpc_config must be specified." - type = object({ + type = object({ yaml_path = string }) } diff --git a/modules/folders/README.md b/modules/folder/README.md similarity index 52% rename from modules/folders/README.md rename to modules/folder/README.md index 7efb4b68..5e85b49a 100644 --- a/modules/folders/README.md +++ b/modules/folder/README.md @@ -1,6 +1,6 @@ # Google Cloud Folder Module -This module allow creation and management of sets of folders sharing a common parent, and their individual IAM bindings. It also allows setting a common set of organization policies on all folders. +This module allows the creation and management of folders together with their individual IAM bindings and organization policies. ## Examples @@ -8,16 +8,11 @@ This module allow creation and management of sets of folders sharing a common pa ```hcl module "folder" { - source = "./modules/folders" + source = "./modules/folder" parent = "organizations/1234567890" - names = ["Folder one", "Folder two"] - iam_members = { - "Folder one" = { - "roles/owner" = ["group:users@example.com"] - } - } - iam_roles = { - "Folder one" = ["roles/owner"] + name = "Folder name" + iam = { + "roles/owner" = ["group:users@example.com"] } } ``` @@ -26,9 +21,9 @@ module "folder" { ```hcl module "folder" { - source = "./modules/folders" + source = "./modules/folder" parent = "organizations/1234567890" - names = ["Folder one", "Folder two"] + name = "Folder name" policy_boolean = { "constraints/compute.disableGuestAttributesAccess" = true "constraints/compute.skipDefaultNetworkCreation" = true @@ -49,10 +44,9 @@ module "folder" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| -| parent | Parent in folders/folder_id or organizations/org_id format. | string | ✓ | | -| *iam_members* | List of IAM members keyed by folder name and role. | map(map(list(string))) | | null | -| *iam_roles* | List of IAM roles keyed by folder name. | map(list(string)) | | null | -| *names* | Folder names. | list(string) | | [] | +| name | Folder name. | string | ✓ | | +| parent | Parent in folders/folder_id or organizations/org_id format. | string | ✓ | | +| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | map(set(string)) | | {} | | *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | @@ -60,12 +54,7 @@ module "folder" { | name | description | sensitive | |---|---|:---:| -| folder | Folder resource (for single use). | | -| folders | Folder resources. | | -| id | Folder id (for single use). | | -| ids | Folder ids. | | -| ids_list | List of folder ids. | | -| name | Folder name (for single use). | | -| names | Folder names. | | -| names_list | List of folder names. | | +| folder | Folder resource. | | +| id | Folder id. | | +| name | Folder name. | | diff --git a/modules/folders/main.tf b/modules/folder/main.tf similarity index 51% rename from modules/folders/main.tf rename to modules/folder/main.tf index e144726b..fb018b7a 100644 --- a/modules/folders/main.tf +++ b/modules/folder/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 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,63 +14,26 @@ * limitations under the License. */ -locals { - folders = ( - local.has_folders - ? [for name in var.names : google_folder.folders[name]] - : [] - ) - # needed when destroying - has_folders = length(google_folder.folders) > 0 - iam_pairs = var.iam_roles == null ? [] : flatten([ - for name, roles in var.iam_roles : - [for role in roles : { name = name, role = role }] - ]) - iam_keypairs = { - for pair in local.iam_pairs : - "${pair.name}-${pair.role}" => pair - } - iam_members = var.iam_members == null ? {} : var.iam_members - policy_boolean_pairs = { - for pair in setproduct(var.names, keys(var.policy_boolean)) : - "${pair.0}-${pair.1}" => { - folder = pair.0, - policy = pair.1, - policy_data = var.policy_boolean[pair.1] - } - } - policy_list_pairs = { - for pair in setproduct(var.names, keys(var.policy_list)) : - "${pair.0}-${pair.1}" => { - folder = pair.0, - policy = pair.1, - policy_data = var.policy_list[pair.1] - } - } -} -resource "google_folder" "folders" { - for_each = toset(var.names) - display_name = each.value +resource "google_folder" "folder" { + display_name = var.name parent = var.parent } resource "google_folder_iam_binding" "authoritative" { - for_each = local.iam_keypairs - folder = google_folder.folders[each.value.name].name - role = each.value.role - members = lookup( - lookup(local.iam_members, each.value.name, {}), each.value.role, [] - ) + for_each = var.iam + folder = google_folder.folder.name + role = each.key + members = each.value } resource "google_folder_organization_policy" "boolean" { - for_each = local.policy_boolean_pairs - folder = google_folder.folders[each.value.folder].id - constraint = each.value.policy + for_each = var.policy_boolean + folder = google_folder.folder.name + constraint = each.key dynamic boolean_policy { - for_each = each.value.policy_data == null ? [] : [each.value.policy_data] + for_each = each.value == null ? [] : [each.value] iterator = policy content { enforced = policy.value @@ -78,7 +41,7 @@ resource "google_folder_organization_policy" "boolean" { } dynamic restore_policy { - for_each = each.value.policy_data == null ? [""] : [] + for_each = each.value == null ? [""] : [] content { default = true } @@ -86,12 +49,12 @@ resource "google_folder_organization_policy" "boolean" { } resource "google_folder_organization_policy" "list" { - for_each = local.policy_list_pairs - folder = google_folder.folders[each.value.folder].id - constraint = each.value.policy + for_each = var.policy_list + folder = google_folder.folder.name + constraint = each.key dynamic list_policy { - for_each = each.value.policy_data.status == null ? [] : [each.value.policy_data] + for_each = each.value.status == null ? [] : [each.value] iterator = policy content { inherit_from_parent = policy.value.inherit_from_parent @@ -130,7 +93,7 @@ resource "google_folder_organization_policy" "list" { } dynamic restore_policy { - for_each = each.value.policy_data.status == null ? [true] : [] + for_each = each.value.status == null ? [true] : [] content { default = true } diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf new file mode 100644 index 00000000..4be12eb1 --- /dev/null +++ b/modules/folder/outputs.tf @@ -0,0 +1,35 @@ +/** + * Copyright 2020 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 "folder" { + description = "Folder resource." + value = google_folder.folder +} + +output "id" { + description = "Folder id." + value = google_folder.folder.name + depends_on = [ + google_folder_iam_binding.authoritative, + google_folder_organization_policy.boolean, + google_folder_organization_policy.list + ] +} + +output "name" { + description = "Folder name." + value = google_folder.folder.display_name +} diff --git a/modules/folders/variables.tf b/modules/folder/variables.tf similarity index 74% rename from modules/folders/variables.tf rename to modules/folder/variables.tf index 3dbf502b..1231be0d 100644 --- a/modules/folders/variables.tf +++ b/modules/folder/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 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,27 +14,24 @@ * limitations under the License. */ -variable "iam_members" { - description = "List of IAM members keyed by folder name and role." - type = map(map(list(string))) - default = null +variable "iam" { + description = "IAM bindings in {ROLE => [MEMBERS]} format." + type = map(set(string)) + default = {} } -variable "iam_roles" { - description = "List of IAM roles keyed by folder name." - type = map(list(string)) - default = null -} - -variable "names" { - description = "Folder names." - type = list(string) - default = [] +variable "name" { + description = "Folder name." + type = string } variable "parent" { description = "Parent in folders/folder_id or organizations/org_id format." type = string + validation { + condition = can(regex("(organizations|folders)/[0-9]+", var.parent)) + error_message = "Parent must be of the form folders/folder_id or organizations/organization_id." + } } variable "policy_boolean" { diff --git a/modules/folders/versions.tf b/modules/folder/versions.tf similarity index 94% rename from modules/folders/versions.tf rename to modules/folder/versions.tf index bc4c2a9d..2a088552 100644 --- a/modules/folders/versions.tf +++ b/modules/folder/versions.tf @@ -15,5 +15,5 @@ */ terraform { - required_version = ">= 0.12.6" + required_version = ">= 0.13.0" } diff --git a/modules/folders-unit/README.md b/modules/folders-unit/README.md index 43c84ec0..9ebad644 100644 --- a/modules/folders-unit/README.md +++ b/modules/folders-unit/README.md @@ -36,10 +36,9 @@ module "folders-unit" { | short_name | Short name used as GCS bucket and service account prefixes, do not use capital letters or spaces. | string | ✓ | | | *environments* | Unit environments short names. | map(string) | | ... | | *gcs_defaults* | Defaults use for the state GCS buckets. | map(string) | | ... | +| *iam* | IAM bindings for the top-level folder in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *iam_billing_config* | Grant billing user role to service accounts, defaults to granting on the billing account. | object({...}) | | ... | | *iam_enviroment_roles* | IAM roles granted to the environment service account on the environment sub-folder. | list(string) | | ... | -| *iam_members* | IAM members for roles applied on the unit folder. | map(list(string)) | | null | -| *iam_roles* | IAM roles applied on the unit folder. | list(string) | | null | | *iam_xpn_config* | Grant Shared VPC creation roles to service accounts, defaults to granting at folder level. | object({...}) | | ... | | *prefix* | Optional prefix used for GCS bucket names to ensure uniqueness. | string | | null | | *service_account_keys* | Generate and store service account keys in the state file. | bool | | false | diff --git a/modules/folders-unit/locals.tf b/modules/folders-unit/locals.tf index 1ad80700..96edad4a 100644 --- a/modules/folders-unit/locals.tf +++ b/modules/folders-unit/locals.tf @@ -16,12 +16,7 @@ locals { folder_roles = concat(var.iam_enviroment_roles, local.sa_xpn_folder_roles) - iam_members = var.iam_members == null ? {} : var.iam_members - iam_roles = var.iam_roles == null ? [] : var.iam_roles - unit_iam_bindings = { - for role in local.iam_roles : - role => lookup(local.iam_members, role, []) - } + iam = var.iam == null ? {} : var.iam folder_iam_service_account_bindings = { for pair in setproduct(keys(var.environments), local.folder_roles) : "${pair.0}-${pair.1}" => { environment = pair.0, role = pair.1 } diff --git a/modules/folders-unit/main.tf b/modules/folders-unit/main.tf index 3034d20f..25e03071 100644 --- a/modules/folders-unit/main.tf +++ b/modules/folders-unit/main.tf @@ -34,7 +34,7 @@ resource "google_folder" "environment" { } resource "google_folder_iam_binding" "unit" { - for_each = local.unit_iam_bindings + for_each = var.iam folder = google_folder.unit.name role = each.key members = each.value @@ -92,9 +92,9 @@ resource "google_storage_bucket" "tfstate" { var.prefix == null ? "" : "${var.prefix}-", "${var.short_name}-${each.key}-tf" ]) - location = var.gcs_defaults.location - storage_class = var.gcs_defaults.storage_class - force_destroy = false + location = var.gcs_defaults.location + storage_class = var.gcs_defaults.storage_class + force_destroy = false uniform_bucket_level_access = true versioning { enabled = true diff --git a/modules/folders-unit/variables.tf b/modules/folders-unit/variables.tf index f7a8df3f..d8782edc 100644 --- a/modules/folders-unit/variables.tf +++ b/modules/folders-unit/variables.tf @@ -42,6 +42,12 @@ variable "gcs_defaults" { } } +variable "iam" { + description = "IAM bindings for the top-level folder in {ROLE => [MEMBERS]} format." + type = map(list(string)) + default = {} +} + variable "iam_billing_config" { description = "Grant billing user role to service accounts, defaults to granting on the billing account." type = object({ @@ -65,18 +71,6 @@ variable "iam_enviroment_roles" { ] } -variable "iam_members" { - description = "IAM members for roles applied on the unit folder." - type = map(list(string)) - default = null -} - -variable "iam_roles" { - description = "IAM roles applied on the unit folder." - type = list(string) - default = null -} - variable "iam_xpn_config" { description = "Grant Shared VPC creation roles to service accounts, defaults to granting at folder level." type = object({ diff --git a/modules/folders/outputs.tf b/modules/folders/outputs.tf deleted file mode 100644 index cf7b54ca..00000000 --- a/modules/folders/outputs.tf +++ /dev/null @@ -1,78 +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 "folder" { - description = "Folder resource (for single use)." - value = local.has_folders ? local.folders[0] : null -} - -output "id" { - description = "Folder id (for single use)." - value = local.has_folders ? local.folders[0].name : null - depends_on = [ - google_folder_iam_binding.authoritative, - google_folder_organization_policy.boolean, - google_folder_organization_policy.list - ] -} - -output "name" { - description = "Folder name (for single use)." - value = local.has_folders ? local.folders[0].display_name : null -} - -output "folders" { - description = "Folder resources." - value = local.folders -} - -output "ids" { - description = "Folder ids." - value = ( - local.has_folders - ? zipmap(var.names, [for f in local.folders : f.name]) - : {} - ) - depends_on = [ - google_folder_iam_binding.authoritative, - google_folder_organization_policy.boolean, - google_folder_organization_policy.list - ] -} - -output "names" { - description = "Folder names." - value = ( - local.has_folders - ? zipmap(var.names, [for f in local.folders : f.display_name]) - : {} - ) -} - -output "ids_list" { - description = "List of folder ids." - value = [for f in local.folders : f.name] - depends_on = [ - google_folder_iam_binding.authoritative, - google_folder_organization_policy.boolean, - google_folder_organization_policy.list - ] -} - -output "names_list" { - description = "List of folder names." - value = [for f in local.folders : f.display_name] -} diff --git a/modules/gcs/README.md b/modules/gcs/README.md index 672ec12e..33afea1f 100644 --- a/modules/gcs/README.md +++ b/modules/gcs/README.md @@ -7,21 +7,13 @@ ## Example ```hcl -module "buckets" { +module "bucket" { source = "./modules/gcs" project_id = "myproject" prefix = "test" - names = ["bucket-one", "bucket-two"] - bucket_policy_only = { - bucket-one = false - } - iam_members = { - bucket-two = { - "roles/storage.admin" = ["group:storage@example.com"] - } - } - iam_roles = { - bucket-two = ["roles/storage.admin"] + name = "my-bucket" + iam = { + "roles/storage.admin" = ["group:storage@example.com"] } } ``` @@ -29,56 +21,38 @@ module "buckets" { ### Example with Cloud KMS ```hcl -module "buckets" { +module "bucket" { source = "./modules/gcs" project_id = "myproject" prefix = "test" - names = ["bucket-one", "bucket-two"] - bucket_policy_only = { - bucket-one = false - } - iam_members = { - bucket-two = { - "roles/storage.admin" = ["group:storage@example.com"] - } - } - iam_roles = { - bucket-two = ["roles/storage.admin"] - } - encryption_keys = { - bucket-two = local.kms_key.self_link, + name = "my-bucket" + iam = { + "roles/storage.admin" = ["group:storage@example.com"] } + encryption_keys = local.kms_key.self_link } ``` ### Example with retention policy ```hcl -module "buckets" { +module "bucket" { source = "./modules/gcs" project_id = "myproject" prefix = "test" - names = ["bucket-one", "bucket-two"] - bucket_policy_only = { - bucket-one = false - } - iam_members = { - bucket-two = { - "roles/storage.admin" = ["group:storage@example.com"] - } - } - iam_roles = { - bucket-two = ["roles/storage.admin"] + name = "my-bucket" + iam = { + "roles/storage.admin" = ["group:storage@example.com"] } retention_policies = { - bucket-one = { retention_period = 100 , is_locked = true} - bucket-two = { retention_period = 900 , is_locked = false} + retention_period = 100 + is_locked = true } logging_config = { - bucket-one = { log_bucket = bucket_name_for_logging , log_object_prefix = null} - bucket-two = { log_bucket = bucket_name_for_logging , log_object_prefix = "logs_for_bucket_two"} + log_bucket = bucket_name_for_logging + log_object_prefix = null } } ``` @@ -88,31 +62,25 @@ module "buckets" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| -| names | Bucket name suffixes. | list(string) | ✓ | | +| name | Bucket name suffix. | string | ✓ | | | project_id | Bucket project id. | string | ✓ | | -| *uniform_bucket_level_access* | Optional map to enable object ACLs keyed by name, defaults to true. | map(bool) | | {} | -| *encryption_keys* | Per-bucket KMS keys that will be used for encryption. | map(string) | | {} | -| *force_destroy* | Optional map to set force destroy keyed by name, defaults to false. | map(bool) | | {} | -| *iam_members* | IAM members keyed by bucket name and role. | map(map(list(string))) | | {} | -| *iam_roles* | IAM roles keyed by bucket name. | map(list(string)) | | {} | +| *encryption_key* | KMS key that will be used for encryption. | string | | null | +| *force_destroy* | Optional map to set force destroy keyed by name, defaults to false. | bool | | false | +| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *labels* | Labels to be attached to all buckets. | map(string) | | {} | | *location* | Bucket location. | string | | EU | -| *logging_config* | Per-bucket logging. | map(object({...})) | | {} | +| *logging_config* | Bucket logging configuration. | object({...}) | | null | | *prefix* | Prefix used to generate the bucket name. | string | | null | -| *retention_policies* | Per-bucket retention policy. | map(object({...})) | | {} | -| *storage_class* | Bucket storage class. | string | | MULTI_REGIONAL | -| *versioning* | Optional map to set versioning keyed by name, defaults to false. | map(bool) | | {} | +| *retention_policy* | Bucket retention policy. | object({...}) | | null | +| *storage_class* | Bucket storage class. | string | | ... | +| *uniform_bucket_level_access* | Allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API). | bool | | true | +| *versioning* | Enable versioning, defaults to false. | bool | | false | ## Outputs | name | description | sensitive | |---|---|:---:| -| bucket | Bucket resource (for single use). | | -| buckets | Bucket resources. | | -| name | Bucket name (for single use). | | -| names | Bucket names. | | -| names_list | List of bucket names. | | -| url | Bucket URL (for single use). | | -| urls | Bucket URLs. | | -| urls_list | List of bucket URLs. | | +| bucket | Bucket resource. | | +| name | Bucket name. | | +| url | Bucket URL. | | diff --git a/modules/gcs/main.tf b/modules/gcs/main.tf index cd2ca1f2..b983b0a6 100644 --- a/modules/gcs/main.tf +++ b/modules/gcs/main.tf @@ -15,85 +15,57 @@ */ locals { - buckets = ( - local.has_buckets - ? [for name in var.names : google_storage_bucket.buckets[name]] - : [] - ) - # needed when destroying - has_buckets = length(google_storage_bucket.buckets) > 0 - iam_pairs = var.iam_roles == null ? [] : flatten([ - for name, roles in var.iam_roles : - [for role in roles : { name = name, role = role }] - ]) - iam_keypairs = { - for pair in local.iam_pairs : - "${pair.name}-${pair.role}" => pair - } - iam_members = var.iam_members == null ? {} : var.iam_members prefix = ( var.prefix == null || var.prefix == "" # keep "" for backward compatibility ? "" : join("-", [var.prefix, lower(var.location), ""]) ) - kms_keys = { - for name in var.names : name => lookup(var.encryption_keys, name, null) - } - retention_policy = { - for name in var.names : name => lookup(var.retention_policies, name, null) - } - logging_config = { - for name in var.names : name => lookup(var.logging_config, name, null) - } } -resource "google_storage_bucket" "buckets" { - for_each = toset(var.names) - name = "${local.prefix}${lower(each.key)}" - project = var.project_id - location = var.location - storage_class = var.storage_class - force_destroy = lookup(var.force_destroy, each.key, false) - uniform_bucket_level_access = lookup(var.uniform_bucket_level_access, each.key, true) +resource "google_storage_bucket" "bucket" { + name = "${local.prefix}${lower(var.name)}" + project = var.project_id + location = var.location + storage_class = var.storage_class + force_destroy = var.force_destroy + uniform_bucket_level_access = var.uniform_bucket_level_access versioning { - enabled = lookup(var.versioning, each.key, false) + enabled = var.versioning } labels = merge(var.labels, { location = lower(var.location) - name = lower(each.key) + name = lower(var.name) storage_class = lower(var.storage_class) }) dynamic encryption { - for_each = local.kms_keys[each.key] == null ? [] : [""] + for_each = var.encryption_key == null ? [] : [""] content { - default_kms_key_name = local.kms_keys[each.key] + default_kms_key_name = var.encryption_key } } dynamic retention_policy { - for_each = local.retention_policy[each.key] == null ? [] : [""] + for_each = var.retention_policy == null ? [] : [""] content { - retention_period = local.retention_policy[each.key]["retention_period"] - is_locked = local.retention_policy[each.key]["is_locked"] + retention_period = var.retention_policy.retention_period + is_locked = var.retention_policy.is_locked } } dynamic logging { - for_each = local.logging_config[each.key] == null ? [] : [""] + for_each = var.logging_config == null ? [] : [""] content { - log_bucket = local.logging_config[each.key]["log_bucket"] - log_object_prefix = local.logging_config[each.key]["log_object_prefix"] + log_bucket = var.logging_config.log_bucket + log_object_prefix = var.logging_config.log_object_prefix } } } resource "google_storage_bucket_iam_binding" "bindings" { - for_each = local.iam_keypairs - bucket = google_storage_bucket.buckets[each.value.name].name - role = each.value.role - members = lookup( - lookup(local.iam_members, each.value.name, {}), each.value.role, [] - ) + for_each = var.iam + bucket = google_storage_bucket.bucket.name + role = each.key + members = each.value } diff --git a/modules/gcs/outputs.tf b/modules/gcs/outputs.tf index 9a8f8dfa..8d434874 100644 --- a/modules/gcs/outputs.tf +++ b/modules/gcs/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,49 +15,16 @@ */ output "bucket" { - description = "Bucket resource (for single use)." - value = local.has_buckets ? local.buckets[0] : null + description = "Bucket resource." + value = google_storage_bucket.bucket } output "name" { - description = "Bucket name (for single use)." - value = local.has_buckets ? local.buckets[0].name : null + description = "Bucket name." + value = google_storage_bucket.bucket.name } output "url" { - description = "Bucket URL (for single use)." - value = local.has_buckets ? local.buckets[0].url : null -} - -output "buckets" { - description = "Bucket resources." - value = local.buckets -} - -output "names" { - description = "Bucket names." - value = ( - local.has_buckets - ? zipmap(var.names, [for b in local.buckets : lookup(b, "name", null)]) - : {} - ) -} - -output "urls" { - description = "Bucket URLs." - value = ( - local.has_buckets - ? zipmap(var.names, [for b in local.buckets : b.url]) - : {} - ) -} - -output "names_list" { - description = "List of bucket names." - value = [for b in local.buckets : b.name] -} - -output "urls_list" { - description = "List of bucket URLs." - value = [for b in local.buckets : b.name] + description = "Bucket URL." + value = google_storage_bucket.bucket.url } diff --git a/modules/gcs/variables.tf b/modules/gcs/variables.tf index 19ada263..dac13b04 100644 --- a/modules/gcs/variables.tf +++ b/modules/gcs/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,33 +15,27 @@ */ variable "uniform_bucket_level_access" { - description = "Optional map to allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API)." - type = map(bool) - default = {} + description = "Allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API)." + type = bool + default = true } variable "force_destroy" { description = "Optional map to set force destroy keyed by name, defaults to false." - type = map(bool) - default = {} + type = bool + default = false } -variable "iam_members" { - description = "IAM members keyed by bucket name and role." - type = map(map(list(string))) - default = {} -} - -variable "iam_roles" { - description = "IAM roles keyed by bucket name." +variable "iam" { + description = "IAM bindings in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "encryption_keys" { - description = "Per-bucket KMS keys that will be used for encryption." - type = map(string) - default = {} +variable "encryption_key" { + description = "KMS key that will be used for encryption." + type = string + default = null } variable "labels" { @@ -57,17 +51,17 @@ variable "location" { } variable "logging_config" { - description = "Per-bucket logging." - type = map(object({ + description = "Bucket logging configuration." + type = object({ log_bucket = string log_object_prefix = string - })) - default = {} + }) + default = null } -variable "names" { - description = "Bucket name suffixes." - type = list(string) +variable "name" { + description = "Bucket name suffix." + type = string } variable "prefix" { @@ -81,23 +75,27 @@ variable "project_id" { type = string } -variable "retention_policies" { - description = "Per-bucket retention policy." - type = map(object({ +variable "retention_policy" { + description = "Bucket retention policy." + type = object({ retention_period = number is_locked = bool - })) - default = {} + }) + default = null } variable "storage_class" { description = "Bucket storage class." type = string default = "MULTI_REGIONAL" + validation { + condition = contains(["STANDARD", "MULTI_REGIONAL", "REGIONAL", "NEARLINE", "COLDLINE", "ARCHIVE"], var.storage_class) + error_message = "Storage class must be one of STANDARD, MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, ARCHIVE." + } } variable "versioning" { - description = "Optional map to set versioning keyed by name, defaults to false." - type = map(bool) - default = {} + description = "Enable versioning, defaults to false." + type = bool + default = false } diff --git a/modules/gcs/versions.tf b/modules/gcs/versions.tf index bc4c2a9d..2a088552 100644 --- a/modules/gcs/versions.tf +++ b/modules/gcs/versions.tf @@ -15,5 +15,5 @@ */ terraform { - required_version = ">= 0.12.6" + required_version = ">= 0.13.0" } diff --git a/modules/iam-service-account/README.md b/modules/iam-service-account/README.md new file mode 100644 index 00000000..6389a078 --- /dev/null +++ b/modules/iam-service-account/README.md @@ -0,0 +1,52 @@ +# Google Service Account Module + +This module allows simplified creation and management of one a service account and its IAM bindings. A key can optionally be generated and will be stored in Terraform state. To use it create a sensitive output in your root modules referencing the `key` output, then extract the private key from the JSON formatted outputs. + +## Example + +```hcl +module "myproject-default-service-accounts" { + source = "./modules/iam-service-account" + project_id = "myproject" + name = "vm-default" + generate_key = true + # authoritative roles granted *on* the service accounts to other identities + iam = { + "roles/iam.serviceAccountUser" = ["user:foo@example.com"] + } + # non-authoritative roles granted *to* the service accounts on other resources + iam_project_roles = { + "myproject" = [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter", + ] + } +} +``` + + +## Variables + +| name | description | type | required | default | +|---|---|:---: |:---:|:---:| +| name | Name of the service account to create. | string | ✓ | | +| project_id | Project id where service account will be created. | string | ✓ | | +| *display_name* | Display name of the service account to create. | string | | Terraform-managed. | +| *generate_key* | Generate a key for service account. | bool | | false | +| *iam* | IAM bindings on the service account in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| *iam_billing_roles* | Project roles granted to the service account, by billing account id. | map(list(string)) | | {} | +| *iam_folder_roles* | Project roles granted to the service account, by folder id. | map(list(string)) | | {} | +| *iam_organization_roles* | Project roles granted to the service account, by organization id. | map(list(string)) | | {} | +| *iam_project_roles* | Project roles granted to the service account, by project id. | map(list(string)) | | {} | +| *iam_storage_roles* | Storage roles granted to the service account, by bucket name. | map(list(string)) | | {} | +| *prefix* | Prefix applied to service account names. | string | | null | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| email | Service account email. | | +| iam_email | IAM-format service account email. | | +| key | Service account key. | ✓ | +| service_account | Service account resource. | | + diff --git a/modules/iam-service-account/main.tf b/modules/iam-service-account/main.tf new file mode 100644 index 00000000..953fef57 --- /dev/null +++ b/modules/iam-service-account/main.tf @@ -0,0 +1,124 @@ +/** + * Copyright 2020 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. + */ + +locals { + iam_billing_pairs = flatten([ + for entity, roles in var.iam_billing_roles : [ + for role in roles : [ + { entity = entity, role = role } + ] + ] + ]) + iam_folder_pairs = flatten([ + for entity, roles in var.iam_folder_roles : [ + for role in roles : [ + { entity = entity, role = role } + ] + ] + ]) + iam_organization_pairs = flatten([ + for entity, roles in var.iam_organization_roles : [ + for role in roles : [ + { entity = entity, role = role } + ] + ] + ]) + iam_project_pairs = flatten([ + for entity, roles in var.iam_project_roles : [ + for role in roles : [ + { entity = entity, role = role } + ] + ] + ]) + iam_storage_pairs = flatten([ + for entity, roles in var.iam_storage_roles : [ + for role in roles : [ + { entity = entity, role = role } + ] + ] + ]) + key = var.generate_key ? google_service_account_key.key["1"] : {} + prefix = var.prefix != null ? "${var.prefix}-" : "" + resource_iam_email = "serviceAccount:${google_service_account.service_account.email}" +} + +resource "google_service_account" "service_account" { + project = var.project_id + account_id = "${local.prefix}${var.name}" + display_name = var.display_name +} + +resource "google_service_account_key" "key" { + for_each = var.generate_key ? { 1 = 1 } : {} + service_account_id = google_service_account.service_account.email +} + +resource "google_service_account_iam_binding" "roles" { + for_each = var.iam + service_account_id = google_service_account.service_account.name + role = each.key + members = each.value +} + +resource "google_billing_account_iam_member" "billing-roles" { + for_each = { + for pair in local.iam_billing_pairs : + "${pair.entity}-${pair.role}" => pair + } + billing_account_id = each.value.entity + role = each.value.role + member = local.resource_iam_email +} + +resource "google_folder_iam_member" "folder-roles" { + for_each = { + for pair in local.iam_folder_pairs : + "${pair.entity}-${pair.role}" => pair + } + folder = each.value.entity + role = each.value.role + member = local.resource_iam_email +} + +resource "google_organization_iam_member" "organization-roles" { + for_each = { + for pair in local.iam_organization_pairs : + "${pair.entity}-${pair.role}" => pair + } + org_id = each.value.entity + role = each.value.role + member = local.resource_iam_email +} + +resource "google_project_iam_member" "project-roles" { + for_each = { + for pair in local.iam_project_pairs : + "${pair.entity}-${pair.role}" => pair + } + project = each.value.entity + role = each.value.role + member = local.resource_iam_email +} + +resource "google_storage_bucket_iam_member" "bucket-roles" { + for_each = { + for pair in local.iam_storage_pairs : + "${pair.entity}-${pair.role}" => pair + } + bucket = each.value.entity + role = each.value.role + member = local.resource_iam_email +} diff --git a/modules/iam-service-account/outputs.tf b/modules/iam-service-account/outputs.tf new file mode 100644 index 00000000..86de600b --- /dev/null +++ b/modules/iam-service-account/outputs.tf @@ -0,0 +1,36 @@ +/** + * Copyright 2020 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 "service_account" { + description = "Service account resource." + value = google_service_account.service_account +} + +output "email" { + description = "Service account email." + value = google_service_account.service_account.email +} + +output "iam_email" { + description = "IAM-format service account email." + value = local.resource_iam_email +} + +output "key" { + description = "Service account key." + sensitive = true + value = local.key +} diff --git a/modules/iam-service-accounts/variables.tf b/modules/iam-service-account/variables.tf similarity index 60% rename from modules/iam-service-accounts/variables.tf rename to modules/iam-service-account/variables.tf index 1ffedff8..11463be6 100644 --- a/modules/iam-service-accounts/variables.tf +++ b/modules/iam-service-account/variables.tf @@ -14,58 +14,57 @@ * limitations under the License. */ -variable "generate_keys" { - description = "Generate keys for service accounts." +variable "generate_key" { + description = "Generate a key for service account." type = bool default = false } -variable "iam_members" { - description = "Map of member lists which are granted authoritative roles on the service accounts, keyed by role." +variable "iam" { + description = "IAM bindings on the service account in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "iam_roles" { - description = "List of authoritative roles granted on the service accounts." - type = list(string) - default = [] -} - variable "iam_billing_roles" { - description = "Project roles granted to all service accounts, by billing account id." + description = "Project roles granted to the service account, by billing account id." type = map(list(string)) default = {} } variable "iam_folder_roles" { - description = "Project roles granted to all service accounts, by folder id." + description = "Project roles granted to the service account, by folder id." type = map(list(string)) default = {} } variable "iam_organization_roles" { - description = "Project roles granted to all service accounts, by organization id." + description = "Project roles granted to the service account, by organization id." type = map(list(string)) default = {} } variable "iam_project_roles" { - description = "Project roles granted to all service accounts, by project id." + description = "Project roles granted to the service account, by project id." type = map(list(string)) default = {} } variable "iam_storage_roles" { - description = "Storage roles granted to all service accounts, by bucket name." + description = "Storage roles granted to the service account, by bucket name." type = map(list(string)) default = {} } -variable "names" { - description = "Names of the service accounts to create." - type = list(string) - default = [] +variable "name" { + description = "Name of the service account to create." + type = string +} + +variable "display_name" { + description = "Display name of the service account to create." + type = string + default = "Terraform-managed." } variable "prefix" { diff --git a/foundations/business-units/versions.tf b/modules/iam-service-account/versions.tf similarity index 100% rename from foundations/business-units/versions.tf rename to modules/iam-service-account/versions.tf diff --git a/modules/iam-service-accounts/README.md b/modules/iam-service-accounts/README.md deleted file mode 100644 index d348db72..00000000 --- a/modules/iam-service-accounts/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Google Service Accounts Module - -This module allows simplified creation and management of one or more service accounts and their IAM bindings. Keys can optionally be generated and will be stored in Terraform state. To use them create a sensitive output in your root modules referencing the `keys` or `key` outputs, then extract the private key from the JSON formatted outputs. - -## Example - -```hcl -module "myproject-default-service-accounts" { - source = "./modules/iam-service-accounts" - project_id = "myproject" - names = ["vm-default", "gke-node-default"] - generate_keys = true - # authoritative roles granted *on* the service accounts to other identities - iam_roles = ["roles/iam.serviceAccountUser"] - iam_members = { - "roles/iam.serviceAccountUser" = ["user:foo@example.com"] - } - # non-authoritative roles granted *to* the service accounts on other resources - iam_project_roles = { - "myproject" = [ - "roles/logging.logWriter", - "roles/monitoring.metricWriter", - ] - } -} -``` - - -## Variables - -| name | description | type | required | default | -|---|---|:---: |:---:|:---:| -| project_id | Project id where service account will be created. | string | ✓ | | -| *generate_keys* | Generate keys for service accounts. | bool | | false | -| *iam_billing_roles* | Project roles granted to all service accounts, by billing account id. | map(list(string)) | | {} | -| *iam_folder_roles* | Project roles granted to all service accounts, by folder id. | map(list(string)) | | {} | -| *iam_members* | Map of member lists which are granted authoritative roles on the service accounts, keyed by role. | map(list(string)) | | {} | -| *iam_organization_roles* | Project roles granted to all service accounts, by organization id. | map(list(string)) | | {} | -| *iam_project_roles* | Project roles granted to all service accounts, by project id. | map(list(string)) | | {} | -| *iam_roles* | List of authoritative roles granted on the service accounts. | list(string) | | [] | -| *iam_storage_roles* | Storage roles granted to all service accounts, by bucket name. | map(list(string)) | | {} | -| *names* | Names of the service accounts to create. | list(string) | | [] | -| *prefix* | Prefix applied to service account names. | string | | null | - -## Outputs - -| name | description | sensitive | -|---|---|:---:| -| email | Service account email (for single use). | | -| emails | Service account emails. | | -| emails_list | Service account emails. | | -| iam_email | IAM-format service account email (for single use). | | -| iam_emails | IAM-format service account emails. | | -| iam_emails_list | IAM-format service account emails. | | -| key | Service account key (for single use). | | -| keys | Map of service account keys. | ✓ | -| service_account | Service account resource (for single use). | | -| service_accounts | Service account resources. | | - diff --git a/modules/iam-service-accounts/main.tf b/modules/iam-service-accounts/main.tf deleted file mode 100644 index 67ff8095..00000000 --- a/modules/iam-service-accounts/main.tf +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright 2020 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. - */ - -locals { - iam_pairs = { - for pair in setproduct(var.names, var.iam_roles) : - "${pair.0}-${pair.1}" => { name = pair.0, role = pair.1 } - } - iam_billing_pairs = flatten([ - for entity, roles in var.iam_billing_roles : [ - for role in roles : [ - for name in var.names : { entity = entity, role = role, name = name } - ] - ] - ]) - iam_folder_pairs = flatten([ - for entity, roles in var.iam_folder_roles : [ - for role in roles : [ - for name in var.names : { entity = entity, role = role, name = name } - ] - ] - ]) - iam_organization_pairs = flatten([ - for entity, roles in var.iam_organization_roles : [ - for role in roles : [ - for name in var.names : { entity = entity, role = role, name = name } - ] - ] - ]) - iam_project_pairs = flatten([ - for entity, roles in var.iam_project_roles : [ - for role in roles : [ - for name in var.names : { entity = entity, role = role, name = name } - ] - ] - ]) - iam_storage_pairs = flatten([ - for entity, roles in var.iam_storage_roles : [ - for role in roles : [ - for name in var.names : { entity = entity, role = role, name = name } - ] - ] - ]) - keys = var.generate_keys ? google_service_account_key.keys : {} - prefix = var.prefix != null ? "${var.prefix}-" : "" - resource = try(google_service_account.service_accounts[var.names[0]], null) - resource_iam_emails = { - for name, resource in google_service_account.service_accounts : - name => "serviceAccount:${resource.email}" - } -} - -resource "google_service_account" "service_accounts" { - for_each = toset(var.names) - project = var.project_id - account_id = "${local.prefix}${lower(each.value)}" - display_name = "Terraform-managed." -} - -resource "google_service_account_key" "keys" { - for_each = var.generate_keys ? toset(var.names) : toset([]) - service_account_id = google_service_account.service_accounts[each.value].email -} - -resource "google_service_account_iam_binding" "sa-roles" { - for_each = local.iam_pairs - service_account_id = google_service_account.service_accounts[each.value.name].name - role = each.value.role - members = lookup(var.iam_members, each.value.role, []) -} - -resource "google_billing_account_iam_member" "roles" { - for_each = { - for pair in local.iam_billing_pairs : - "${pair.name}-${pair.entity}-${pair.role}" => pair - } - billing_account_id = each.value.entity - role = each.value.role - member = local.resource_iam_emails[each.value.name] -} - -resource "google_folder_iam_member" "roles" { - for_each = { - for pair in local.iam_folder_pairs : - "${pair.name}-${pair.entity}-${pair.role}" => pair - } - folder = each.value.entity - role = each.value.role - member = local.resource_iam_emails[each.value.name] -} - -resource "google_organization_iam_member" "roles" { - for_each = { - for pair in local.iam_organization_pairs : - "${pair.name}-${pair.entity}-${pair.role}" => pair - } - org_id = each.value.entity - role = each.value.role - member = local.resource_iam_emails[each.value.name] -} - -resource "google_project_iam_member" "project-roles" { - for_each = { - for pair in local.iam_project_pairs : - "${pair.name}-${pair.entity}-${pair.role}" => pair - } - project = each.value.entity - role = each.value.role - member = local.resource_iam_emails[each.value.name] -} - -resource "google_storage_bucket_iam_member" "bucket-roles" { - for_each = { - for pair in local.iam_storage_pairs : - "${pair.name}-${pair.entity}-${pair.role}" => pair - } - bucket = each.value.entity - role = each.value.role - member = local.resource_iam_emails[each.value.name] -} - -# TODO(ludoo): link from README -# ref: https://cloud.google.com/vpc/docs/shared-vpc diff --git a/modules/iam-service-accounts/outputs.tf b/modules/iam-service-accounts/outputs.tf deleted file mode 100644 index 3673a71f..00000000 --- a/modules/iam-service-accounts/outputs.tf +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright 2020 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 "service_account" { - description = "Service account resource (for single use)." - value = local.resource -} - -output "service_accounts" { - description = "Service account resources." - value = google_service_account.service_accounts -} - -output "email" { - description = "Service account email (for single use)." - value = try(local.resource.email, null) -} - -output "iam_email" { - description = "IAM-format service account email (for single use)." - value = try("serviceAccount:${local.resource.email}", null) -} - -output "key" { - description = "Service account key (for single use)." - value = try(local.keys[var.names[0]], null) -} - -output "emails" { - description = "Service account emails." - value = { - for name, resource in google_service_account.service_accounts : - name => resource.email - } -} - -output "iam_emails" { - description = "IAM-format service account emails." - value = local.resource_iam_emails -} - -output "emails_list" { - description = "Service account emails." - value = [ - for name, resource in google_service_account.service_accounts : - resource.email - ] -} - -output "iam_emails_list" { - description = "IAM-format service account emails." - value = [ - for name, resource in google_service_account.service_accounts : - "serviceAccount:${resource.email}" - ] -} - -output "keys" { - description = "Map of service account keys." - sensitive = true - value = local.keys -} diff --git a/modules/kms/README.md b/modules/kms/README.md index 69ceb882..67b98c18 100644 --- a/modules/kms/README.md +++ b/modules/kms/README.md @@ -16,8 +16,7 @@ In this module **no lifecycle blocks are set on resources to prevent destroy**, module "kms" { source = "../modules/kms" project_id = "my-project" - iam_roles = ["roles/owner"] - iam_members = { + iam = { "roles/owner" = ["user:user1@example.com"] } keyring = { location = "europe-west1", name = "test" } @@ -32,10 +31,7 @@ module "kms" { module "kms" { source = "../modules/kms" project_id = "my-project" - key_iam_roles = { - key-a = ["roles/owner"] - } - key_iam_members = { + key_iam = { key-a = { "roles/owner" = ["user:user1@example.com"] } @@ -76,10 +72,8 @@ module "kms" { |---|---|:---: |:---:|:---:| | keyring | Keyring attributes. | object({...}) | ✓ | | | project_id | Project id where the keyring will be created. | string | ✓ | | -| *iam_members* | Keyring IAM members. | map(list(string)) | | {} | -| *iam_roles* | Keyring IAM roles. | list(string) | | [] | -| *key_iam_members* | IAM members keyed by key name and role. | map(map(list(string))) | | {} | -| *key_iam_roles* | IAM roles keyed by key name. | map(list(string)) | | {} | +| *iam* | Keyring IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| *key_iam* | Key IAM bindings for topic in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | | *key_purpose* | Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | map(object({...})) | | {} | | *key_purpose_defaults* | Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | object({...}) | | ... | | *keyring_create* | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | diff --git a/modules/kms/main.tf b/modules/kms/main.tf index e69f3381..ab3a61aa 100644 --- a/modules/kms/main.tf +++ b/modules/kms/main.tf @@ -15,14 +15,15 @@ */ locals { - key_iam_pairs = flatten([ - for name, roles in var.key_iam_roles : - [for role in roles : { name = name, role = role }] + key_iam_members = flatten([ + for key, roles in var.key_iam : [ + for role, members in roles : { + key = key + role = role + members = members + } + ] ]) - key_iam_keypairs = { - for pair in local.key_iam_pairs : - "${pair.name}-${pair.role}" => pair - } key_purpose = { for key, attrs in var.keys : key => try( var.key_purpose[key], var.key_purpose_defaults @@ -47,16 +48,13 @@ resource "google_kms_key_ring" "default" { project = var.project_id name = var.keyring.name location = var.keyring.location - # lifecycle { - # prevent_destroy = true - # } } resource "google_kms_key_ring_iam_binding" "default" { - for_each = toset(var.iam_roles) + for_each = var.iam key_ring_id = local.keyring.self_link - role = each.value - members = lookup(var.iam_members, each.value, []) + role = each.key + members = each.value } resource "google_kms_crypto_key" "default" { @@ -73,16 +71,14 @@ resource "google_kms_crypto_key" "default" { protection_level = local.key_purpose[each.key].version_template.protection_level } } - # lifecycle { - # prevent_destroy = true - # } } resource "google_kms_crypto_key_iam_binding" "default" { - for_each = local.key_iam_keypairs + for_each = { + for binding in local.key_iam_members : + "${binding.key}.${binding.role}" => binding + } role = each.value.role - crypto_key_id = google_kms_crypto_key.default[each.value.name].self_link - members = lookup( - lookup(var.key_iam_members, each.value.name, {}), each.value.role, [] - ) + crypto_key_id = google_kms_crypto_key.default[each.value.key].self_link + members = each.value.members } diff --git a/modules/kms/outputs.tf b/modules/kms/outputs.tf index d92190a5..a1ea1e26 100644 --- a/modules/kms/outputs.tf +++ b/modules/kms/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/kms/variables.tf b/modules/kms/variables.tf index 42ec689b..c13bea28 100644 --- a/modules/kms/variables.tf +++ b/modules/kms/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 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,30 +14,18 @@ * limitations under the License. */ -variable "iam_members" { - description = "Keyring IAM members." +variable "iam" { + description = "Keyring IAM bindings for topic in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "iam_roles" { - description = "Keyring IAM roles." - type = list(string) - default = [] -} - -variable "key_iam_members" { - description = "IAM members keyed by key name and role." +variable "key_iam" { + description = "Key IAM bindings for topic in {KEY => {ROLE => [MEMBERS]}} format." type = map(map(list(string))) default = {} } -variable "key_iam_roles" { - description = "IAM roles keyed by key name." - type = map(list(string)) - default = {} -} - variable "key_purpose" { description = "Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required." type = map(object({ diff --git a/modules/net-cloudnat/outputs.tf b/modules/net-cloudnat/outputs.tf index 1cf94a55..15d0a649 100644 --- a/modules/net-cloudnat/outputs.tf +++ b/modules/net-cloudnat/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/net-vpc-peering/README.md b/modules/net-vpc-peering/README.md index b83964f7..df9170a8 100644 --- a/modules/net-vpc-peering/README.md +++ b/modules/net-vpc-peering/README.md @@ -21,7 +21,7 @@ module "peering" { } ``` -If you need to create more than one peering for the same VPC Network `(A -> B, A -> C)` you have to use output from the first module as a dependency for the second one to keep order of peering creation (It is not currently possible to create more than one peering connection for a VPC Network at the same time). +If you need to create more than one peering for the same VPC Network `(A -> B, A -> C)` you use a `depends_on` for second one to keep order of peering creation (It is not currently possible to create more than one peering connection for a VPC Network at the same time). ```hcl module "peering-a-b" { @@ -39,7 +39,7 @@ module "peering-a-c" { local_network = "" peer_network = "" - module_depends_on = [module.peering-a-b.complete] + depends_on = [ module.peering-a-b ] } ``` @@ -52,7 +52,6 @@ module "peering-a-c" { | peer_network | Resource link of the peer network. | string | ✓ | | | *export_local_custom_routes* | Export custom routes to peer network from local network. | bool | | false | | *export_peer_custom_routes* | Export custom routes to local network from peer network. | bool | | false | -| *module_depends_on* | List of modules or resources this module depends on. | list | | [] | | *peer_create_peering* | Create the peering on the remote side. If false, only the peering from this network to the remote network is created. | bool | | true | | *prefix* | Name prefix for the network peerings. | string | | network-peering | @@ -60,7 +59,6 @@ module "peering-a-c" { | name | description | sensitive | |---|---|:---:| -| complete | Output to be used as a module dependency. | | | local_network_peering | Network peering resource. | | | peer_network_peering | Peer network peering resource. | | - \ No newline at end of file + diff --git a/modules/net-vpc-peering/main.tf b/modules/net-vpc-peering/main.tf index cd004422..0d00a9ef 100644 --- a/modules/net-vpc-peering/main.tf +++ b/modules/net-vpc-peering/main.tf @@ -25,8 +25,6 @@ resource "google_compute_network_peering" "local_network_peering" { peer_network = var.peer_network export_custom_routes = var.export_local_custom_routes import_custom_routes = var.export_peer_custom_routes - - depends_on = [null_resource.module_depends_on] } resource "google_compute_network_peering" "peer_network_peering" { @@ -37,15 +35,5 @@ resource "google_compute_network_peering" "peer_network_peering" { export_custom_routes = var.export_peer_custom_routes import_custom_routes = var.export_local_custom_routes - depends_on = [null_resource.module_depends_on, google_compute_network_peering.local_network_peering] -} - -resource "null_resource" "module_depends_on" { - triggers = { - value = length(var.module_depends_on) - } -} - -resource "null_resource" "complete" { - depends_on = [google_compute_network_peering.local_network_peering, google_compute_network_peering.peer_network_peering] + depends_on = [google_compute_network_peering.local_network_peering] } diff --git a/modules/net-vpc-peering/outputs.tf b/modules/net-vpc-peering/outputs.tf index 92bdc536..b2781115 100644 --- a/modules/net-vpc-peering/outputs.tf +++ b/modules/net-vpc-peering/outputs.tf @@ -23,8 +23,3 @@ output "peer_network_peering" { description = "Peer network peering resource." value = google_compute_network_peering.peer_network_peering } - -output "complete" { - description = "Output to be used as a module dependency." - value = null_resource.complete.id -} diff --git a/modules/net-vpc-peering/variables.tf b/modules/net-vpc-peering/variables.tf index edea0188..ca42c79c 100644 --- a/modules/net-vpc-peering/variables.tf +++ b/modules/net-vpc-peering/variables.tf @@ -42,12 +42,6 @@ variable "export_local_custom_routes" { default = false } -variable "module_depends_on" { - description = "List of modules or resources this module depends on." - type = list - default = [] -} - variable "peer_create_peering" { description = "Create the peering on the remote side. If false, only the peering from this network to the remote network is created." type = bool diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 39406973..a000a474 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -86,13 +86,7 @@ module "vpc-host" { local.service_project_1.project_id, local.service_project_2.project_id ] - iam_roles = { - "europe-west1/subnet-1" = [ - "roles/compute.networkUser", - "roles/compute.securityAdmin" - ] - } - iam_members = { + iam = { "europe-west1/subnet-1" = { "roles/compute.networkUser" = [ local.service_project_1.cloudsvc_sa, @@ -116,14 +110,13 @@ module "vpc-host" { | *auto_create_subnetworks* | Set to true to create an auto mode subnet, defaults to custom mode. | bool | | false | | *delete_default_routes_on_create* | Set to true to delete the default routes at creation time. | bool | | false | | *description* | An optional description of this resource (triggers recreation on change). | string | | Terraform-managed. | -| *iam_members* | List of IAM members keyed by subnet 'region/name' and role. | map(map(list(string))) | | {} | -| *iam_roles* | List of IAM roles keyed by subnet 'region/name'. | map(list(string)) | | {} | +| *iam* | Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format. | map(map(list(string))) | | {} | | *log_config_defaults* | Default configuration for flow logs when enabled. | object({...}) | | ... | | *log_configs* | Map keyed by subnet 'region/name' of optional configurations for flow logs when enabled. | map(map(string)) | | {} | | *peering_config* | VPC peering configuration. | object({...}) | | null | | *peering_create_remote_end* | Skip creation of peering on the remote end when using peering_config | bool | | true | | *routes* | Network routes, keyed by name. | map(object({...})) | | {} | -| *routing_mode* | The network routing mode (default 'GLOBAL') | string | | GLOBAL | +| *routing_mode* | The network routing mode (default 'GLOBAL') | string | | ... | | *shared_vpc_host* | Enable shared VPC for this project. | bool | | false | | *shared_vpc_service_projects* | Shared VPC service projects to register with this host | list(string) | | [] | | *subnet_descriptions* | Optional map of subnet descriptions, keyed by subnet 'region/name'. | map(string) | | {} | diff --git a/modules/net-vpc/main.tf b/modules/net-vpc/main.tf index 9f22d375..14800ef5 100644 --- a/modules/net-vpc/main.tf +++ b/modules/net-vpc/main.tf @@ -15,15 +15,17 @@ */ locals { - iam_members = var.iam_members == null ? {} : var.iam_members - iam_pairs = var.iam_roles == null ? [] : flatten([ - for subnet, roles in var.iam_roles : - [for role in roles : { subnet = subnet, role = role }] + iam_members = var.iam == null ? {} : var.iam + subnet_iam_members = flatten([ + for subnet, roles in local.iam_members : [ + for role, members in roles : { + subnet = subnet + role = role + members = members + } + ] ]) - iam_keypairs = { - for pair in local.iam_pairs : - "${pair.subnet}-${pair.role}" => pair - } + log_configs = var.log_configs == null ? {} : var.log_configs peer_network = ( var.peering_config == null @@ -152,14 +154,15 @@ resource "google_compute_subnetwork" "subnetwork" { } resource "google_compute_subnetwork_iam_binding" "binding" { - for_each = local.iam_keypairs + for_each = { + for binding in local.subnet_iam_members : + "${binding.subnet}.${binding.role}" => binding + } project = var.project_id subnetwork = google_compute_subnetwork.subnetwork[each.value.subnet].name region = google_compute_subnetwork.subnetwork[each.value.subnet].region role = each.value.role - members = lookup( - lookup(local.iam_members, each.value.subnet, {}), each.value.role, [] - ) + members = each.value.members } resource "google_compute_route" "gateway" { diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index 3a4d0c03..485da879 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.tf @@ -32,14 +32,8 @@ variable "description" { default = "Terraform-managed." } -variable "iam_roles" { - description = "List of IAM roles keyed by subnet 'region/name'." - type = map(list(string)) - default = {} -} - -variable "iam_members" { - description = "List of IAM members keyed by subnet 'region/name' and role." +variable "iam" { + description = "Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format." type = map(map(list(string))) default = {} } @@ -106,6 +100,11 @@ variable "routing_mode" { description = "The network routing mode (default 'GLOBAL')" type = string default = "GLOBAL" + validation { + condition = var.routing_mode == "GLOBAL" || var.routing_mode == "REGIONAL" + error_message = "Routing type must be GLOBAL or REGIONAL." + } + } variable "shared_vpc_host" { diff --git a/modules/net-vpc/versions.tf b/modules/net-vpc/versions.tf index bc4c2a9d..2a088552 100644 --- a/modules/net-vpc/versions.tf +++ b/modules/net-vpc/versions.tf @@ -15,5 +15,5 @@ */ terraform { - required_version = ">= 0.12.6" + required_version = ">= 0.13.0" } diff --git a/modules/organization/README.md b/modules/organization/README.md index c95bba8f..0a090b12 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -13,8 +13,7 @@ This module allows managing several organization properties: module "org" { source = "./modules/organization" org_id = 1234567890 - iam_roles = ["roles/projectCreator"] - iam_members = { "roles/projectCreator" = ["group:cloud-admins@example.org"] } + iam = { "roles/projectCreator" = ["group:cloud-admins@example.org"] } policy_boolean = { "constraints/compute.disableGuestAttributesAccess" = true "constraints/compute.skipDefaultNetworkCreation" = true @@ -37,10 +36,9 @@ module "org" { |---|---|:---: |:---:|:---:| | org_id | Organization id in nnnnnn format. | number | ✓ | | | *custom_roles* | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | -| *iam_additive_bindings* | Map of roles lists used to set non authoritative bindings, keyed by members. | map(list(string)) | | {} | +| *iam* | IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| *iam_additive* | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *iam_audit_config* | Service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. | map(map(list(string))) | | {} | -| *iam_members* | Map of member lists used to set authoritative bindings, keyed by role. | map(list(string)) | | {} | -| *iam_roles* | List of roles used to set authoritative bindings. | list(string) | | [] | | *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | diff --git a/modules/organization/main.tf b/modules/organization/main.tf index a96ff141..6cf41017 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -16,7 +16,7 @@ locals { iam_additive_pairs = flatten([ - for member, roles in var.iam_additive_bindings : [ + for member, roles in var.iam_additive : [ for role in roles : { role = role, member = member } ] @@ -37,14 +37,14 @@ resource "google_organization_iam_custom_role" "roles" { } resource "google_organization_iam_binding" "authoritative" { - for_each = toset(var.iam_roles) + for_each = var.iam org_id = var.org_id - role = each.value - members = lookup(var.iam_members, each.value, []) + role = each.key + members = each.value } resource "google_organization_iam_member" "additive" { - for_each = length(var.iam_additive_bindings) > 0 ? local.iam_additive : {} + for_each = length(var.iam_additive) > 0 ? local.iam_additive : {} org_id = var.org_id role = each.value.role member = each.value.member diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 240e920f..293f0176 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -20,20 +20,14 @@ variable "custom_roles" { default = {} } -variable "iam_members" { - description = "Map of member lists used to set authoritative bindings, keyed by role." +variable "iam" { + description = "IAM bindings, in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "iam_roles" { - description = "List of roles used to set authoritative bindings." - type = list(string) - default = [] -} - -variable "iam_additive_bindings" { - description = "Map of roles lists used to set non authoritative bindings, keyed by members." +variable "iam_additive" { + description = "Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } diff --git a/modules/project/README.md b/modules/project/README.md index e4611cec..fcd8ca05 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -15,8 +15,7 @@ module "project" { "container.googleapis.com", "stackdriver.googleapis.com" ] - iam_roles = ["roles/container.hostServiceAgentUser"] - iam_members = { + iam = { "roles/container.hostServiceAgentUser" = [ "serviceAccount:${var.gke_service_account}" ] @@ -32,7 +31,7 @@ module "project" { name = "project-example" project_create = false - iam_additive_bindings = { + iam_additive = { "group:usergroup_watermlon_experimentation@lemonadeinc.io" = [ "roles/viewer", "roles/storage.objectAdmin" @@ -88,15 +87,14 @@ module "project" { | *auto_create_network* | Whether to create the default network for the project | bool | | false | | *billing_account* | Billing account id. | string | | null | | *custom_roles* | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | -| *iam_additive_bindings* | Map of roles lists used to set non authoritative bindings, keyed by members | map(list(string)) | | {} | -| *iam_members* | Map of member lists used to set authoritative bindings, keyed by role. | map(list(string)) | | {} | -| *iam_roles* | List of roles used to set authoritative bindings. | list(string) | | [] | +| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | map(set(string)) | | {} | +| *iam_additive* | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *labels* | Resource labels. | map(string) | | {} | | *lien_reason* | If non-empty, creates a project lien with this description. | string | | | | *oslogin* | Enable OS Login. | bool | | false | | *oslogin_admins* | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] | | *oslogin_users* | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] | -| *parent* | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | +| *parent* | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | ... | | *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | | *prefix* | Prefix used to generate project id and name. | string | | null | diff --git a/modules/project/main.tf b/modules/project/main.tf index e8acb861..f7cf5ddc 100644 --- a/modules/project/main.tf +++ b/modules/project/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ locals { iam_additive_pairs = flatten([ - for member, roles in var.iam_additive_bindings : [ + for member, roles in var.iam_additive : [ for role in roles : { role = role, member = member } ] @@ -91,10 +91,10 @@ resource "google_project_service" "project_services" { # - additive (non-authoritative) roles might fail due to dynamic values resource "google_project_iam_binding" "authoritative" { - for_each = toset(var.iam_roles) + for_each = var.iam project = local.project.project_id - role = each.value - members = lookup(var.iam_members, each.value, []) + role = each.key + members = each.value depends_on = [ google_project_service.project_services, google_project_iam_custom_role.roles @@ -102,7 +102,7 @@ resource "google_project_iam_binding" "authoritative" { } resource "google_project_iam_member" "additive" { - for_each = length(var.iam_additive_bindings) > 0 ? local.iam_additive : {} + for_each = length(var.iam_additive) > 0 ? local.iam_additive : {} project = local.project.project_id role = each.value.role member = each.value.member diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf index a64b7c56..f20c78b0 100644 --- a/modules/project/outputs.tf +++ b/modules/project/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/project/variables.tf b/modules/project/variables.tf index c47ba07a..760a9183 100644 --- a/modules/project/variables.tf +++ b/modules/project/variables.tf @@ -32,21 +32,14 @@ variable "custom_roles" { default = {} } -variable "iam_members" { - description = "Map of member lists used to set authoritative bindings, keyed by role." - type = map(list(string)) +variable "iam" { + description = "IAM bindings in {ROLE => [MEMBERS]} format." + type = map(set(string)) default = {} } -variable "iam_roles" { - description = "List of roles used to set authoritative bindings." - type = list(string) - default = [] -} - - -variable "iam_additive_bindings" { - description = "Map of roles lists used to set non authoritative bindings, keyed by members" +variable "iam_additive" { + description = "IAM additive bindings in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } @@ -90,6 +83,10 @@ variable "parent" { description = "Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format." type = string default = null + validation { + condition = var.parent == null || can(regex("(organizations|folders)/[0-9]+", var.parent)) + error_message = "Parent must be of the form folders/folder_id or organizations/organization_id." + } } variable "policy_boolean" { diff --git a/modules/project/versions.tf b/modules/project/versions.tf index bc4c2a9d..2a088552 100644 --- a/modules/project/versions.tf +++ b/modules/project/versions.tf @@ -15,5 +15,5 @@ */ terraform { - required_version = ">= 0.12.6" + required_version = ">= 0.13.0" } diff --git a/modules/pubsub/README.md b/modules/pubsub/README.md index 4b672760..e708078e 100644 --- a/modules/pubsub/README.md +++ b/modules/pubsub/README.md @@ -12,11 +12,7 @@ module "pubsub" { source = "./modules/pubsub" project_id = "my-project" name = "my-topic" - iam_roles = [ - "roles/pubsub.viewer", - "roles/pubsub.subscriber" - ] - iam_members = { + iam = { "roles/pubsub.viewer" = ["group:foo@example.com"] "roles/pubsub.subscriber" = ["user:user1@example.com"] } @@ -30,7 +26,7 @@ Subscriptions are defined with the `subscriptions` variable, allowing optional c ```hcl module "pubsub" { source = "./modules/pubsub" - project_id = "my-project + project_id = "my-project" name = "my-topic" subscriptions = { test-pull = null @@ -54,7 +50,7 @@ Push subscriptions need extra configuration in the `push_configs` variable. ```hcl module "pubsub" { source = "./modules/pubsub" - project_id = "my-project + project_id = "my-project" name = "my-topic" subscriptions = { test-push = null @@ -74,16 +70,13 @@ module "pubsub" { ```hcl module "pubsub" { source = "./modules/pubsub" - project_id = "my-project + project_id = "my-project" name = "my-topic" subscriptions = { test-1 = null test-1 = null } - subscription_iam_roles = { - test-1 = ["roles/pubsub.subscriber"] - } - subscription_iam_members = { + subscription_iam = { test-1 = { "roles/pubsub.subscriber" = ["user:user1@ludomagno.net"] } @@ -100,14 +93,12 @@ module "pubsub" { | project_id | Project used for resources. | string | ✓ | | | *dead_letter_configs* | Per-subscription dead letter policy configuration. | map(object({...})) | | {} | | *defaults* | Subscription defaults for options. | object({...}) | | ... | -| *iam_members* | IAM members for each topic role. | map(list(string)) | | {} | -| *iam_roles* | IAM roles for topic. | list(string) | | [] | +| *iam* | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *kms_key* | KMS customer managed encryption key. | string | | null | | *labels* | Labels. | map(string) | | {} | | *push_configs* | Push subscription configurations. | map(object({...})) | | {} | | *regions* | List of regions used to set persistence policy. | list(string) | | [] | -| *subscription_iam_members* | IAM members for each subscription and role. | map(map(list(string))) | | {} | -| *subscription_iam_roles* | IAM roles for each subscription. | map(list(string)) | | {} | +| *subscription_iam* | IAM bindings for subscriptions in {SUBSCRIPTION => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | | *subscriptions* | Topic subscriptions. Also define push configs for push subscriptions. If options is set to null subscription defaults will be used. Labels default to topic labels if set to null. | map(object({...})) | | {} | ## Outputs diff --git a/modules/pubsub/main.tf b/modules/pubsub/main.tf index 50876070..dd652e35 100644 --- a/modules/pubsub/main.tf +++ b/modules/pubsub/main.tf @@ -15,17 +15,15 @@ */ locals { - iam_pairs = var.subscription_iam_roles == null ? [] : flatten([ - for name, roles in var.subscription_iam_roles : - [for role in roles : { name = name, role = role }] + sub_iam_members = flatten([ + for sub, roles in var.subscription_iam : [ + for role, members in roles : { + sub = sub + role = role + members = members + } + ] ]) - iam_keypairs = { - for pair in local.iam_pairs : - "${pair.name}-${pair.role}" => pair - } - iam_members = ( - var.subscription_iam_members == null ? {} : var.subscription_iam_members - ) oidc_config = { for k, v in var.push_configs : k => v.oidc_token } @@ -52,11 +50,11 @@ resource "google_pubsub_topic" "default" { } resource "google_pubsub_topic_iam_binding" "default" { - for_each = toset(var.iam_roles) + for_each = var.iam project = var.project_id topic = google_pubsub_topic.default.name - role = each.value - members = lookup(var.iam_members, each.value, []) + role = each.key + members = each.value } resource "google_pubsub_subscription" "default" { @@ -103,11 +101,12 @@ resource "google_pubsub_subscription" "default" { } resource "google_pubsub_subscription_iam_binding" "default" { - for_each = local.iam_keypairs + for_each = { + for binding in local.sub_iam_members : + "${binding.sub}.${binding.role}" => binding + } project = var.project_id - subscription = google_pubsub_subscription.default[each.value.name].name + subscription = google_pubsub_subscription.default[each.value.sub].name role = each.value.role - members = lookup( - lookup(local.iam_members, each.value.name, {}), each.value.role, [] - ) + members = each.value.members } diff --git a/modules/pubsub/variables.tf b/modules/pubsub/variables.tf index d642c7a6..dd68354b 100644 --- a/modules/pubsub/variables.tf +++ b/modules/pubsub/variables.tf @@ -39,18 +39,12 @@ variable "defaults" { } } -variable "iam_members" { - description = "IAM members for each topic role." +variable "iam" { + description = "IAM bindings for topic in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "iam_roles" { - description = "IAM roles for topic." - type = list(string) - default = [] -} - variable "kms_key" { description = "KMS customer managed encryption key." type = string @@ -107,14 +101,8 @@ variable "subscriptions" { default = {} } -variable "subscription_iam_members" { - description = "IAM members for each subscription and role." +variable "subscription_iam" { + description = "IAM bindings for subscriptions in {SUBSCRIPTION => {ROLE => [MEMBERS]}} format." type = map(map(list(string))) default = {} } - -variable "subscription_iam_roles" { - description = "IAM roles for each subscription." - type = map(list(string)) - default = {} -} diff --git a/modules/secret-manager/README.md b/modules/secret-manager/README.md index b8224c53..7f237482 100644 --- a/modules/secret-manager/README.md +++ b/modules/secret-manager/README.md @@ -25,7 +25,7 @@ module "secret-manager" { ### Secret IAM bindings -IAM bindings can be set per secret in the same way as for most other modules supporting IAM, via `iam_roles` and `iam_members` variables. +IAM bindings can be set per secret in the same way as for most other modules supporting IAM, using the `iam` variable. ```hcl module "secret-manager" { @@ -35,11 +35,7 @@ module "secret-manager" { test-auto = null test-manual = ["europe-west1", "europe-west4"] } - iam_roles = { - test-auto = ["roles/secretmanager.secretAccessor"] - test-manual = ["roles/secretmanager.secretAccessor"] - } - iam_members = { + iam = { test-auto = { "roles/secretmanager.secretAccessor" = ["group:auto-readers@example.com"] } @@ -80,8 +76,7 @@ module "secret-manager" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| | project_id | Project id where the keyring will be created. | string | ✓ | | -| *iam_members* | IAM members keyed by secret name and role. | map(map(list(string))) | | {} | -| *iam_roles* | IAM roles keyed by secret name. | map(list(string)) | | {} | +| *iam* | IAM bindings in {SECRET => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | | *labels* | Optional labels for each secret. | map(map(string)) | | {} | | *secrets* | Map of secrets to manage and their locations. If locations is null, automatic management will be set. | map(list(string)) | | {} | | *versions* | Optional versions to manage for each secret. Version names are only used internally to track individual versions. | map(map(object({...}))) | | {} | diff --git a/modules/secret-manager/main.tf b/modules/secret-manager/main.tf index 57aaaf43..74179780 100644 --- a/modules/secret-manager/main.tf +++ b/modules/secret-manager/main.tf @@ -16,13 +16,15 @@ locals { # distinct is needed to make the expanding function argument work - iam_pairs = flatten([ - for name, roles in var.iam_roles : - [for role in roles : { name = name, role = role }] + iam = flatten([ + for secret, roles in var.iam : [ + for role, members in roles : { + secret = secret + role = role + members = members + } + ] ]) - iam_keypairs = { - for pair in local.iam_pairs : "${pair.name}-${pair.role}" => pair - } version_pairs = flatten([ for secret, versions in var.versions : [ for name, attrs in versions : merge(attrs, { name = name, secret = secret }) @@ -73,11 +75,11 @@ resource "google_secret_manager_secret_version" "default" { } resource "google_secret_manager_secret_iam_binding" "default" { - provider = google-beta - for_each = local.iam_keypairs + provider = google-beta + for_each = { + for binding in local.iam : "${binding.secret}.${binding.role}" => binding + } role = each.value.role - secret_id = google_secret_manager_secret.default[each.value.name].id - members = lookup( - lookup(var.iam_members, each.value.name, {}), each.value.role, [] - ) + secret_id = google_secret_manager_secret.default[each.value.secret].id + members = each.value.members } diff --git a/modules/secret-manager/outputs.tf b/modules/secret-manager/outputs.tf index 78256e5c..781de2b2 100644 --- a/modules/secret-manager/outputs.tf +++ b/modules/secret-manager/outputs.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/modules/secret-manager/variables.tf b/modules/secret-manager/variables.tf index b097018a..4f02c0b2 100644 --- a/modules/secret-manager/variables.tf +++ b/modules/secret-manager/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * Copyright 2020 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,18 +14,12 @@ * limitations under the License. */ -variable "iam_members" { - description = "IAM members keyed by secret name and role." +variable "iam" { + description = "IAM bindings in {SECRET => {ROLE => [MEMBERS]}} format." type = map(map(list(string))) default = {} } -variable "iam_roles" { - description = "IAM roles keyed by secret name." - type = map(list(string)) - default = {} -} - variable "labels" { description = "Optional labels for each secret." type = map(map(string)) diff --git a/modules/service-directory/README.md b/modules/service-directory/README.md index c57bd9f8..5bd32fd3 100644 --- a/modules/service-directory/README.md +++ b/modules/service-directory/README.md @@ -15,14 +15,11 @@ module "service-directory" { project_id = "my-project" location = "europe-west1" name = "sd-1" - iam_members = { + iam = { "roles/servicedirectory.editor" = [ "serviceAccount:namespace-editor@example.com" ] } - iam_roles = [ - "roles/servicedirectory.editor" - ] } ``` @@ -40,16 +37,13 @@ module "service-directory" { metadata = null } } - service_iam_members = { + service_iam = { one = { "roles/servicedirectory.editor" = [ "serviceAccount:service-editor.example.com" ] } } - service_iam_roles = { - one = ["roles/servicedirectory.editor"] - } endpoint_config = { "one/first" = { address = "127.0.0.1", port = 80, metadata = {} } "one/second" = { address = "127.0.0.2", port = 80, metadata = {} } @@ -67,14 +61,11 @@ module "service-directory" { project_id = "my-project" location = "europe-west1" name = "apps" - iam_members = { + iam = { "roles/servicedirectory.editor" = [ "serviceAccount:namespace-editor@example.com" ] } - iam_roles = [ - "roles/servicedirectory.editor" - ] services = { app1 = { endpoints = ["one"], metadata = null } } @@ -104,11 +95,9 @@ module "dns-sd" { | name | Namespace name. | string | ✓ | | | project_id | Project used for resources. | string | ✓ | | | *endpoint_config* | Map of endpoint attributes, keys are in service/endpoint format. | map(object({...})) | | {} | -| *iam_members* | IAM members for each namespace role. | map(list(string)) | | {} | -| *iam_roles* | IAM roles for the namespace. | list(string) | | [] | +| *iam* | IAM bindings for namespace, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | *labels* | Labels. | map(string) | | {} | -| *service_iam_members* | IAM members for each service and role. | map(map(list(string))) | | {} | -| *service_iam_roles* | IAM roles for each service. | map(list(string)) | | {} | +| *service_iam* | IAM bindings for services, in {SERVICE => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | | *services* | Service configuration, using service names as keys. | map(object({...})) | | {} | ## Outputs diff --git a/modules/service-directory/main.tf b/modules/service-directory/main.tf index 8348de44..bc27f1d5 100644 --- a/modules/service-directory/main.tf +++ b/modules/service-directory/main.tf @@ -23,17 +23,14 @@ locals { endpoints = { for ep in local.endpoint_list : "${ep.service}/${ep.endpoint}" => ep } - iam_pairs = var.service_iam_roles == null ? [] : flatten([ - for name, roles in var.service_iam_roles : - [for role in roles : { name = name, role = role }] + iam_pairs = var.service_iam == null ? [] : flatten([ + for name, bindings in var.service_iam : + [for role in keys(bindings) : { name = name, role = role }] ]) iam_keypairs = { for pair in local.iam_pairs : "${pair.name}-${pair.role}" => pair } - iam_members = ( - var.service_iam_members == null ? {} : var.service_iam_members - ) } resource "google_service_directory_namespace" "default" { @@ -46,10 +43,10 @@ resource "google_service_directory_namespace" "default" { resource "google_service_directory_namespace_iam_binding" "default" { provider = google-beta - for_each = toset(var.iam_roles) + for_each = var.iam name = google_service_directory_namespace.default.name - role = each.value - members = lookup(var.iam_members, each.value, []) + role = each.key + members = each.value } resource "google_service_directory_service" "default" { @@ -66,7 +63,7 @@ resource "google_service_directory_service_iam_binding" "default" { name = google_service_directory_service.default[each.value.name].name role = each.value.role members = lookup( - lookup(local.iam_members, each.value.name, {}), each.value.role, [] + lookup(var.service_iam, each.value.name, {}), each.value.role, [] ) } diff --git a/modules/service-directory/variables.tf b/modules/service-directory/variables.tf index 8b921d56..15563256 100644 --- a/modules/service-directory/variables.tf +++ b/modules/service-directory/variables.tf @@ -25,18 +25,12 @@ variable "endpoint_config" { default = {} } -variable "iam_members" { - description = "IAM members for each namespace role." +variable "iam" { + description = "IAM bindings for namespace, in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "iam_roles" { - description = "IAM roles for the namespace." - type = list(string) - default = [] -} - variable "labels" { description = "Labels." type = map(string) @@ -58,18 +52,12 @@ variable "project_id" { type = string } -variable "service_iam_members" { - description = "IAM members for each service and role." +variable "service_iam" { + description = "IAM bindings for services, in {SERVICE => {ROLE => [MEMBERS]}} format." type = map(map(list(string))) default = {} } -variable "service_iam_roles" { - description = "IAM roles for each service." - type = map(list(string)) - default = {} -} - variable "services" { description = "Service configuration, using service names as keys." type = map(object({ diff --git a/modules/source-repository/README.md b/modules/source-repository/README.md index c5a2eb85..78a7e7d7 100644 --- a/modules/source-repository/README.md +++ b/modules/source-repository/README.md @@ -12,8 +12,7 @@ module "repo" { source e = "./modules/source-repository" project_id = "my-project" name = "my-repo" - iam_roles = ["roles/source.reader"] - iam_members = { + iam = { "roles/source.reader" = ["user:foo@example.com"] } } @@ -24,10 +23,9 @@ module "repo" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| -| name | Repository topic name. | string | ✓ | | +| name | Repository name. | string | ✓ | | | project_id | Project used for resources. | string | ✓ | | -| *iam_members* | IAM members for each topic role. | map(list(string)) | | {} | -| *iam_roles* | IAM roles for topic. | list(string) | | [] | +| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | ## Outputs diff --git a/modules/source-repository/main.tf b/modules/source-repository/main.tf index 810b4482..f7bea1b1 100644 --- a/modules/source-repository/main.tf +++ b/modules/source-repository/main.tf @@ -20,11 +20,11 @@ resource "google_sourcerepo_repository" "default" { } resource "google_sourcerepo_repository_iam_binding" "default" { - for_each = toset(var.iam_roles) + for_each = var.iam project = var.project_id repository = google_sourcerepo_repository.default.name - role = each.value - members = lookup(var.iam_members, each.value, []) + role = each.key + members = each.value depends_on = [ google_sourcerepo_repository.default diff --git a/modules/source-repository/variables.tf b/modules/source-repository/variables.tf index 1725d4b4..9d5d0832 100644 --- a/modules/source-repository/variables.tf +++ b/modules/source-repository/variables.tf @@ -19,19 +19,13 @@ variable "project_id" { type = string } -variable "iam_members" { - description = "IAM members for each topic role." +variable "iam" { + description = "IAM bindings in {ROLE => [MEMBERS]} format." type = map(list(string)) default = {} } -variable "iam_roles" { - description = "IAM roles for topic." - type = list(string) - default = [] -} - variable "name" { - description = "Repository topic name." + description = "Repository name." type = string } diff --git a/networking/hub-and-spoke-peering/main.tf b/networking/hub-and-spoke-peering/main.tf index f77d49f6..d5c09cc6 100644 --- a/networking/hub-and-spoke-peering/main.tf +++ b/networking/hub-and-spoke-peering/main.tf @@ -136,7 +136,7 @@ module "hub-to-spoke-2-peering" { peer_network = module.vpc-spoke-2.self_link export_local_custom_routes = true export_peer_custom_routes = false - module_depends_on = [module.hub-to-spoke-1-peering.complete] + depends_on = [module.hub-to-spoke-1-peering] } ################################################################################ @@ -180,9 +180,9 @@ module "vm-spoke-2" { } module "service-account-gce" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = var.project_id - names = ["gce-test"] + name = "gce-test" iam_project_roles = { (var.project_id) = [ "roles/container.developer", @@ -232,9 +232,9 @@ module "cluster-1-nodepool-1" { # project level, with no risk of conflicts with pre-existing roles module "service-account-gke-node" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = var.project_id - names = ["gke-node"] + name = "gke-node" iam_project_roles = { (var.project_id) = [ "roles/logging.logWriter", "roles/monitoring.metricWriter", diff --git a/networking/ilb-next-hop/main.tf b/networking/ilb-next-hop/main.tf index b5ca75d2..a630ec97 100644 --- a/networking/ilb-next-hop/main.tf +++ b/networking/ilb-next-hop/main.tf @@ -37,9 +37,9 @@ module "project" { } module "service-accounts" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = module.project.project_id - names = ["${local.prefix}gce-vm"] + name = "${local.prefix}gce-vm" iam_project_roles = { (var.project_id) = [ "roles/logging.logWriter", diff --git a/networking/ilb-next-hop/versions.tf b/networking/ilb-next-hop/versions.tf deleted file mode 100644 index 057095c0..00000000 --- a/networking/ilb-next-hop/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2020 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. - -terraform { - required_version = ">= 0.12.6" -} diff --git a/networking/ilb-next-hop/vms.tf b/networking/ilb-next-hop/vms.tf index d3fbc0f8..c80226aa 100644 --- a/networking/ilb-next-hop/vms.tf +++ b/networking/ilb-next-hop/vms.tf @@ -42,7 +42,7 @@ module "vm-left" { startup-script = local.vm_startup_script } service_account = try( - module.service-accounts.emails["${local.prefix}gce-vm"], null + module.service-accounts.email, null ) service_account_scopes = ["https://www.googleapis.com/auth/cloud-platform"] instance_count = 2 @@ -68,7 +68,7 @@ module "vm-right" { startup-script = local.vm_startup_script } service_account = try( - module.service-accounts.emails["${local.prefix}gce-vm"], null + module.service-accounts.email, null ) service_account_scopes = ["https://www.googleapis.com/auth/cloud-platform"] instance_count = 2 diff --git a/networking/onprem-google-access-dns/main.tf b/networking/onprem-google-access-dns/main.tf index 2d803673..2c24d72e 100644 --- a/networking/onprem-google-access-dns/main.tf +++ b/networking/onprem-google-access-dns/main.tf @@ -170,9 +170,9 @@ resource "google_dns_policy" "inbound" { ################################################################################ module "service-account-gce" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = var.project_id - names = ["gce-test"] + name = "gce-test" iam_project_roles = { (var.project_id) = [ "roles/logging.logWriter", @@ -222,9 +222,9 @@ module "config-onprem" { } module "service-account-onprem" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = var.project_id - names = ["gce-onprem"] + name = "gce-onprem" iam_project_roles = { (var.project_id) = [ "roles/compute.viewer", diff --git a/networking/onprem-google-access-dns/versions.tf b/networking/onprem-google-access-dns/versions.tf deleted file mode 100644 index 057095c0..00000000 --- a/networking/onprem-google-access-dns/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2020 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. - -terraform { - required_version = ">= 0.12.6" -} diff --git a/networking/shared-vpc-gke/main.tf b/networking/shared-vpc-gke/main.tf index 8b7d0534..bee0d814 100644 --- a/networking/shared-vpc-gke/main.tf +++ b/networking/shared-vpc-gke/main.tf @@ -30,10 +30,7 @@ module "project-host" { enabled = true service_projects = [] # defined later } - iam_roles = [ - "roles/container.hostServiceAgentUser", "roles/owner" - ] - iam_members = { + iam = { "roles/container.hostServiceAgentUser" = [ "serviceAccount:${module.project-svc-gke.service_accounts.robots.container-engine}" ] @@ -54,12 +51,7 @@ module "project-svc-gce" { attach = true host_project = module.project-host.project_id } - iam_roles = [ - "roles/logging.logWriter", - "roles/monitoring.metricWriter", - "roles/owner" - ] - iam_members = { + iam = { "roles/logging.logWriter" = [module.vm-bastion.service_account_iam_email], "roles/monitoring.metricWriter" = [module.vm-bastion.service_account_iam_email], "roles/owner" = var.owners_gce, @@ -80,13 +72,7 @@ module "project-svc-gke" { attach = true host_project = module.project-host.project_id } - iam_roles = [ - "roles/container.developer", - "roles/logging.logWriter", - "roles/monitoring.metricWriter", - "roles/owner", - ] - iam_members = { + iam = { "roles/container.developer" = [module.vm-bastion.service_account_iam_email], "roles/logging.logWriter" = [module.service-account-gke-node.iam_email], "roles/monitoring.metricWriter" = [module.service-account-gke-node.iam_email], @@ -121,11 +107,7 @@ module "vpc-shared" { } } ] - iam_roles = { - "${var.region}/gke" = ["roles/compute.networkUser", "roles/compute.securityAdmin"] - "${var.region}/gce" = ["roles/compute.networkUser"] - } - iam_members = { + iam = { "${var.region}/gce" = { "roles/compute.networkUser" = concat(var.owners_gce, [ "serviceAccount:${module.project-svc-gce.service_accounts.cloud_services}", @@ -245,7 +227,7 @@ module "cluster-1-nodepool-1" { # project level, with no risk of conflicts with pre-existing roles module "service-account-gke-node" { - source = "../../modules/iam-service-accounts" + source = "../../modules/iam-service-account" project_id = module.project-svc-gke.project_id - names = ["gke-node"] + name = "gke-node" } diff --git a/tests/data_solutions/cmek_via_centralized_kms/test_plan.py b/tests/data_solutions/cmek_via_centralized_kms/test_plan.py index 21514522..66b971ec 100644 --- a/tests/data_solutions/cmek_via_centralized_kms/test_plan.py +++ b/tests/data_solutions/cmek_via_centralized_kms/test_plan.py @@ -24,4 +24,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner(FIXTURES_DIR) assert len(modules) == 7 - assert len(resources) == 22 + assert len(resources) == 23 diff --git a/tests/data_solutions/gc_to_bq_with_dataflow/__init__.py b/tests/data_solutions/gcs_to_bq_with_dataflow/__init__.py similarity index 100% rename from tests/data_solutions/gc_to_bq_with_dataflow/__init__.py rename to tests/data_solutions/gcs_to_bq_with_dataflow/__init__.py diff --git a/tests/data_solutions/gc_to_bq_with_dataflow/fixture/main.tf b/tests/data_solutions/gcs_to_bq_with_dataflow/fixture/main.tf similarity index 100% rename from tests/data_solutions/gc_to_bq_with_dataflow/fixture/main.tf rename to tests/data_solutions/gcs_to_bq_with_dataflow/fixture/main.tf diff --git a/tests/data_solutions/gc_to_bq_with_dataflow/fixture/variables.tf b/tests/data_solutions/gcs_to_bq_with_dataflow/fixture/variables.tf similarity index 100% rename from tests/data_solutions/gc_to_bq_with_dataflow/fixture/variables.tf rename to tests/data_solutions/gcs_to_bq_with_dataflow/fixture/variables.tf diff --git a/tests/data_solutions/gc_to_bq_with_dataflow/test_plan.py b/tests/data_solutions/gcs_to_bq_with_dataflow/test_plan.py similarity index 93% rename from tests/data_solutions/gc_to_bq_with_dataflow/test_plan.py rename to tests/data_solutions/gcs_to_bq_with_dataflow/test_plan.py index 1828f7f4..03fc27da 100644 --- a/tests/data_solutions/gc_to_bq_with_dataflow/test_plan.py +++ b/tests/data_solutions/gcs_to_bq_with_dataflow/test_plan.py @@ -23,5 +23,5 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner(FIXTURES_DIR) - assert len(modules) == 13 - assert len(resources) == 61 + assert len(modules) == 14 + assert len(resources) == 60 diff --git a/tests/modules/folders/__init__.py b/tests/foundations/business_units/__init__.py similarity index 100% rename from tests/modules/folders/__init__.py rename to tests/foundations/business_units/__init__.py diff --git a/tests/foundations/business_units/fixture/main.tf b/tests/foundations/business_units/fixture/main.tf new file mode 100644 index 00000000..7dfe870a --- /dev/null +++ b/tests/foundations/business_units/fixture/main.tf @@ -0,0 +1,23 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../foundations/business-units" + billing_account_id = var.billing_account_id + organization_id = var.organization_id + prefix = var.prefix + root_node = var.root_node +} diff --git a/tests/foundations/business_units/fixture/variables.tf b/tests/foundations/business_units/fixture/variables.tf new file mode 100644 index 00000000..eeeb616d --- /dev/null +++ b/tests/foundations/business_units/fixture/variables.tf @@ -0,0 +1,35 @@ +# Copyright 2020 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. + +variable "billing_account_id" { + type = string + default = "1234-5678-9012" +} + +variable "organization_id" { + type = string + default = "organizations/1234567890" +} + +variable "prefix" { + description = "Prefix used for resources that need unique names." + type = string + default = "test" +} + +variable "root_node" { + description = "Root node for the new hierarchy, either 'organizations/org_id' or 'folders/folder_id'." + type = string + default = "folders/1234567890" +} diff --git a/networking/hub-and-spoke-peering/versions.tf b/tests/foundations/business_units/test_plan.py similarity index 59% rename from networking/hub-and-spoke-peering/versions.tf rename to tests/foundations/business_units/test_plan.py index c36e893b..0e400b36 100644 --- a/networking/hub-and-spoke-peering/versions.tf +++ b/tests/foundations/business_units/test_plan.py @@ -4,7 +4,7 @@ # 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 +# 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, @@ -12,14 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -terraform { - required_version = ">= 0.12.16" -} -provider "google" { - version = "~> 3.3" -} +import os +import pytest -provider "google-beta" { - version = "~> 3.3" -} + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +def test_resources(e2e_plan_runner): + "Test that plan works and the numbers of resources is as expected." + modules, resources = e2e_plan_runner(FIXTURES_DIR) + assert len(modules) == 9 + assert len(resources) == 84 diff --git a/tests/foundations/environments/test_plan.py b/tests/foundations/environments/test_plan.py index 96fd124e..a2c7c6a3 100644 --- a/tests/foundations/environments/test_plan.py +++ b/tests/foundations/environments/test_plan.py @@ -23,16 +23,16 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') def test_folder_roles(plan_runner): "Test folder roles." _, modules = plan_runner(FIXTURES_DIR, is_module=False) - resources = modules['module.test.module.environment-folders'] - folders = [r for r in resources if r['type'] == 'google_folder'] - assert len(folders) == 2 - assert set(r['values']['display_name'] - for r in folders) == set(['prod', 'test']) - bindings = [r['index'].split('-') - for r in resources if r['type'] == 'google_folder_iam_binding'] - assert len(bindings) == 10 - assert set(b[0] for b in bindings) == set(['prod', 'test']) - assert len(set(b[1] for b in bindings)) == 5 + for env in ['test', 'prod']: + resources = modules[f'module.test.module.environment-folders["{env}"]'] + folders = [r for r in resources if r['type'] == 'google_folder'] + assert len(folders) == 1 + folder = folders[0] + assert folder['values']['display_name'] == env + + bindings = [r['index'] + for r in resources if r['type'] == 'google_folder_iam_binding'] + assert len(bindings) == 5 def test_org_roles(plan_runner): @@ -42,12 +42,17 @@ def test_org_roles(plan_runner): 'iam_xpn_config': '{grant = true, target_org = true}' } _, modules = plan_runner(FIXTURES_DIR, is_module=False, **vars) - resources = modules['module.test.module.environment-folders'] - folder_bindings = [r['index'].split('-') - for r in resources if r['type'] == 'google_folder_iam_binding'] - assert len(folder_bindings) == 8 - resources = modules['module.test.module.tf-service-accounts'] - org_bindings = [r['index'].split('-') - for r in resources if r['type'] == 'google_organization_iam_member'] - assert len(org_bindings) == 4 - assert set(b[0] for b in org_bindings) == set(['prod', 'test']) + for env in ['test', 'prod']: + resources = modules[f'module.test.module.environment-folders["{env}"]'] + folder_bindings = [r['index'] + for r in resources if r['type'] == 'google_folder_iam_binding'] + assert len(folder_bindings) == 4 + + resources = modules[f'module.test.module.tf-service-accounts["{env}"]'] + org_bindings = [r for r in resources + if r['type'] == 'google_organization_iam_member'] + assert len(org_bindings) == 2 + assert {b['values']['role'] for b in org_bindings} == { + 'roles/resourcemanager.organizationViewer', + 'roles/compute.xpnAdmin' + } diff --git a/tests/modules/iam_service_accounts/__init__.py b/tests/modules/bigtable_instance/__init__.py similarity index 100% rename from tests/modules/iam_service_accounts/__init__.py rename to tests/modules/bigtable_instance/__init__.py diff --git a/tests/modules/bigtable_instance/fixture/main.tf b/tests/modules/bigtable_instance/fixture/main.tf new file mode 100644 index 00000000..47aa2ed5 --- /dev/null +++ b/tests/modules/bigtable_instance/fixture/main.tf @@ -0,0 +1,33 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../modules/bigtable-instance" + project_id = "my-project" + name = "test" + iam = { + "roles/bigtable.user" = ["user:me@example.com"] + } + tables = { + test-1 = null, + test-2 = { + split_keys = ["a", "b", "c"] + column_family = null + } + + } + zone = var.zone +} diff --git a/tests/modules/bigtable_instance/fixture/variables.tf b/tests/modules/bigtable_instance/fixture/variables.tf new file mode 100644 index 00000000..2c2d2d03 --- /dev/null +++ b/tests/modules/bigtable_instance/fixture/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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 "zone" { + type = string + default = "europe-west1-b" +} diff --git a/tests/modules/bigtable_instance/test_plan.py b/tests/modules/bigtable_instance/test_plan.py new file mode 100644 index 00000000..875816ff --- /dev/null +++ b/tests/modules/bigtable_instance/test_plan.py @@ -0,0 +1,47 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +@pytest.fixture +def resources(plan_runner): + _, resources = plan_runner(FIXTURES_DIR) + return resources + + +def test_resource_count(resources): + "Test number of resources created." + assert len(resources) == 4 + + +def test_iam(resources): + "Test IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_bigtable_instance_iam_binding'] + assert len(bindings) == 1 + assert bindings[0]['role'] == 'roles/bigtable.user' + + +def test_tables(resources): + "Test table resources." + subs = [r['values'] for r in resources if r['type'] + == 'google_bigtable_table'] + assert len(subs) == 2 + assert set(s['name'] for s in subs) == set(['test-1', 'test-2']) diff --git a/networking/shared-vpc-gke/versions.tf b/tests/modules/cloud_function/__init__.py similarity index 84% rename from networking/shared-vpc-gke/versions.tf rename to tests/modules/cloud_function/__init__.py index de5425c2..6913f02e 100644 --- a/networking/shared-vpc-gke/versions.tf +++ b/tests/modules/cloud_function/__init__.py @@ -4,14 +4,10 @@ # 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 +# 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" -} diff --git a/data-solutions/cmek-via-centralized-kms/versions.tf b/tests/modules/cloud_function/fixture/bundle/main.py similarity index 84% rename from data-solutions/cmek-via-centralized-kms/versions.tf rename to tests/modules/cloud_function/fixture/bundle/main.py index 057095c0..6913f02e 100644 --- a/data-solutions/cmek-via-centralized-kms/versions.tf +++ b/tests/modules/cloud_function/fixture/bundle/main.py @@ -4,14 +4,10 @@ # 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 +# 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/tests/modules/cloud_function/fixture/main.tf b/tests/modules/cloud_function/fixture/main.tf new file mode 100644 index 00000000..54dd23dd --- /dev/null +++ b/tests/modules/cloud_function/fixture/main.tf @@ -0,0 +1,29 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../modules/cloud-function" + project_id = "my-project" + name = "test" + bucket_name = var.bucket_name + bundle_config = { + source_dir = "bundle" + output_path = "bundle.zip" + } + iam = { + "roles/cloudfunctions.invoker" = ["allUsers"] + } +} diff --git a/tests/modules/cloud_function/fixture/variables.tf b/tests/modules/cloud_function/fixture/variables.tf new file mode 100644 index 00000000..8d77df50 --- /dev/null +++ b/tests/modules/cloud_function/fixture/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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 "bucket_name" { + type = string + default = "test" +} diff --git a/tests/modules/cloud_function/test_plan.py b/tests/modules/cloud_function/test_plan.py new file mode 100644 index 00000000..50ce14e8 --- /dev/null +++ b/tests/modules/cloud_function/test_plan.py @@ -0,0 +1,39 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +@pytest.fixture +def resources(plan_runner): + _, resources = plan_runner(FIXTURES_DIR) + return resources + + +def test_resource_count(resources): + "Test number of resources created." + assert len(resources) == 3 + + +def test_iam(resources): + "Test IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_cloudfunctions_function_iam_binding'] + assert len(bindings) == 1 + assert bindings[0]['role'] == 'roles/cloudfunctions.invoker' diff --git a/tests/modules/compute_vm/fixture/main.tf b/tests/modules/compute_vm/fixture/main.tf index b80dcb23..749742d8 100644 --- a/tests/modules/compute_vm/fixture/main.tf +++ b/tests/modules/compute_vm/fixture/main.tf @@ -25,8 +25,7 @@ module "test" { instance_count = var.instance_count use_instance_template = var.use_instance_template group = var.group - iam_roles = var.iam_roles - iam_members = var.iam_members + iam = var.iam metadata = var.metadata metadata_list = var.metadata_list } diff --git a/tests/modules/compute_vm/fixture/variables.tf b/tests/modules/compute_vm/fixture/variables.tf index 6258905b..b1fd91fe 100644 --- a/tests/modules/compute_vm/fixture/variables.tf +++ b/tests/modules/compute_vm/fixture/variables.tf @@ -19,16 +19,11 @@ variable "group" { default = null } -variable "iam_members" { - type = map(list(string)) +variable "iam" { + type = map(set(string)) default = {} } -variable "iam_roles" { - type = list(string) - default = [] -} - variable "instance_count" { type = number default = 1 diff --git a/tests/modules/compute_vm/test_plan.py b/tests/modules/compute_vm/test_plan.py index a37b55ac..698bf33a 100644 --- a/tests/modules/compute_vm/test_plan.py +++ b/tests/modules/compute_vm/test_plan.py @@ -56,13 +56,12 @@ def test_group(plan_runner): def test_iam(plan_runner): - iam_roles = '["roles/compute.instanceAdmin", "roles/iam.serviceAccountUser"]' - iam_members = ( + iam = ( '{"roles/compute.instanceAdmin" = ["user:a@a.com", "user:b@a.com"],' '"roles/iam.serviceAccountUser" = ["user:a@a.com"]}' ) _, resources = plan_runner( - FIXTURES_DIR, instance_count=2, iam_roles=iam_roles, iam_members=iam_members) + FIXTURES_DIR, instance_count=2, iam=iam) assert len(resources) == 6 assert set(r['type'] for r in resources) == set([ 'google_compute_instance', 'google_compute_instance_iam_binding']) diff --git a/tests/modules/compute_vm/test_plan_zones.py b/tests/modules/compute_vm/test_plan_zones.py index f4a00359..83e7898c 100644 --- a/tests/modules/compute_vm/test_plan_zones.py +++ b/tests/modules/compute_vm/test_plan_zones.py @@ -49,11 +49,9 @@ def test_group(plan_runner): def test_iam(plan_runner): - iam_roles = '["roles/a", "roles/b"]' - iam_members = '{"roles/a" = ["user:a@a.com"], "roles/b" = ["user:a@a.com"]}' + iam = '{"roles/a" = ["user:a@a.com"], "roles/b" = ["user:a@a.com"]}' _, resources = plan_runner(FIXTURES_DIR, instance_count=3, - iam_roles=iam_roles, iam_members=iam_members, - zones='["a", "b"]') + iam=iam, zones='["a", "b"]') iam_bindings = dict( (r['index'], r['values']['zone']) for r in resources if r['type'] == 'google_compute_instance_iam_binding' diff --git a/data-solutions/gcs-to-bq-with-dataflow/versions.tf b/tests/modules/container_registry/__init__.py similarity index 84% rename from data-solutions/gcs-to-bq-with-dataflow/versions.tf rename to tests/modules/container_registry/__init__.py index 057095c0..6913f02e 100644 --- a/data-solutions/gcs-to-bq-with-dataflow/versions.tf +++ b/tests/modules/container_registry/__init__.py @@ -4,14 +4,10 @@ # 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 +# 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/tests/modules/container_registry/fixture/main.tf b/tests/modules/container_registry/fixture/main.tf new file mode 100644 index 00000000..a9d6174b --- /dev/null +++ b/tests/modules/container_registry/fixture/main.tf @@ -0,0 +1,24 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../modules/container-registry" + project_id = "my-project" + location = var.location + iam = { + "roles/storage.admin" = ["user:me@example.com"] + } +} diff --git a/modules/iam-service-accounts/versions.tf b/tests/modules/container_registry/fixture/variables.tf similarity index 91% rename from modules/iam-service-accounts/versions.tf rename to tests/modules/container_registry/fixture/variables.tf index bc4c2a9d..f3939cee 100644 --- a/modules/iam-service-accounts/versions.tf +++ b/tests/modules/container_registry/fixture/variables.tf @@ -14,6 +14,7 @@ * limitations under the License. */ -terraform { - required_version = ">= 0.12.6" +variable "location" { + type = string + default = "EU" } diff --git a/tests/modules/container_registry/test_plan.py b/tests/modules/container_registry/test_plan.py new file mode 100644 index 00000000..09a0caa5 --- /dev/null +++ b/tests/modules/container_registry/test_plan.py @@ -0,0 +1,39 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +@pytest.fixture +def resources(plan_runner): + _, resources = plan_runner(FIXTURES_DIR) + return resources + + +def test_resource_count(resources): + "Test number of resources created." + assert len(resources) == 2 + + +def test_iam(resources): + "Test IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_storage_bucket_iam_binding'] + assert len(bindings) == 1 + assert bindings[0]['role'] == 'roles/storage.admin' diff --git a/foundations/environments/versions.tf b/tests/modules/endpoints/__init__.py similarity index 84% rename from foundations/environments/versions.tf rename to tests/modules/endpoints/__init__.py index 057095c0..6913f02e 100644 --- a/foundations/environments/versions.tf +++ b/tests/modules/endpoints/__init__.py @@ -4,14 +4,10 @@ # 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 +# 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/tests/modules/endpoints/fixture/main.tf b/tests/modules/endpoints/fixture/main.tf new file mode 100644 index 00000000..375fc7bd --- /dev/null +++ b/tests/modules/endpoints/fixture/main.tf @@ -0,0 +1,25 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../modules/endpoints" + project_id = "my-project" + service_name = var.service_name + openapi_config = { "yaml_path" = "openapi.yaml" } + iam = { + "roles/servicemanagement.serviceController" = ["user:me@example.com"] + } +} diff --git a/tests/modules/endpoints/fixture/openapi.yaml b/tests/modules/endpoints/fixture/openapi.yaml new file mode 100644 index 00000000..6913f02e --- /dev/null +++ b/tests/modules/endpoints/fixture/openapi.yaml @@ -0,0 +1,13 @@ +# Copyright 2020 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. diff --git a/tests/modules/endpoints/fixture/variables.tf b/tests/modules/endpoints/fixture/variables.tf new file mode 100644 index 00000000..40ffe31a --- /dev/null +++ b/tests/modules/endpoints/fixture/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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 "service_name" { + type = string + default = "foo.endpoints.test.cloud.goog" +} diff --git a/tests/modules/endpoints/test_plan.py b/tests/modules/endpoints/test_plan.py new file mode 100644 index 00000000..84bcda7b --- /dev/null +++ b/tests/modules/endpoints/test_plan.py @@ -0,0 +1,39 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +@pytest.fixture +def resources(plan_runner): + _, resources = plan_runner(FIXTURES_DIR) + return resources + + +def test_resource_count(resources): + "Test number of resources created." + assert len(resources) == 2 + + +def test_iam(resources): + "Test IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_endpoints_service_iam_binding'] + assert len(bindings) == 1 + assert bindings[0]['role'] == 'roles/servicemanagement.serviceController' diff --git a/tests/modules/folder/__init__.py b/tests/modules/folder/__init__.py new file mode 100644 index 00000000..6913f02e --- /dev/null +++ b/tests/modules/folder/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020 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. diff --git a/tests/modules/folders/fixture/main.tf b/tests/modules/folder/fixture/main.tf similarity index 81% rename from tests/modules/folders/fixture/main.tf rename to tests/modules/folder/fixture/main.tf index 71d13cd2..e9a141f9 100644 --- a/tests/modules/folders/fixture/main.tf +++ b/tests/modules/folder/fixture/main.tf @@ -15,11 +15,10 @@ */ module "test" { - source = "../../../../modules/folders" + source = "../../../../modules/folder" parent = "organizations/12345678" - names = ["folder-a", "folder-b"] - iam_members = var.iam_members - iam_roles = var.iam_roles + name = "folder-a" + iam = var.iam policy_boolean = var.policy_boolean policy_list = var.policy_list } diff --git a/tests/modules/folders/fixture/variables.tf b/tests/modules/folder/fixture/variables.tf similarity index 88% rename from tests/modules/folders/fixture/variables.tf rename to tests/modules/folder/fixture/variables.tf index d8a71531..f1cebb93 100644 --- a/tests/modules/folders/fixture/variables.tf +++ b/tests/modules/folder/fixture/variables.tf @@ -14,14 +14,9 @@ * limitations under the License. */ -variable "iam_members" { - type = map(map(list(string))) - default = null -} - -variable "iam_roles" { +variable "iam" { type = map(list(string)) - default = null + default = {} } variable "policy_boolean" { diff --git a/tests/modules/folders/test_plan.py b/tests/modules/folder/test_plan.py similarity index 52% rename from tests/modules/folders/test_plan.py rename to tests/modules/folder/test_plan.py index fcb8fa64..160a6eaf 100644 --- a/tests/modules/folders/test_plan.py +++ b/tests/modules/folder/test_plan.py @@ -23,27 +23,34 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') def test_folder(plan_runner): "Test folder resources." _, resources = plan_runner(FIXTURES_DIR) - assert len(resources) == 2 - assert set(r['type'] for r in resources) == set(['google_folder']) - assert set(r['values']['display_name'] for r in resources) == set([ - 'folder-a', 'folder-b' - ]) - assert set(r['values']['parent'] for r in resources) == set([ - 'organizations/12345678' - ]) - - -def test_iam_roles_only(plan_runner): - "Test folder resources with only iam roles passed." - _, resources = plan_runner( - FIXTURES_DIR, iam_roles='{folder-a = [ "roles/owner"]}') - assert len(resources) == 3 + assert len(resources) == 1 + resource = resources[0] + assert resource['type'] == 'google_folder' + assert resource['values']['display_name'] == 'folder-a' + assert resource['values']['parent'] == 'organizations/12345678' def test_iam(plan_runner): "Test folder resources with iam roles and members." - iam_roles = '{folder-a = ["roles/owner"], folder-b = ["roles/viewer"]}' - iam_members = '{folder-a = { "roles/owner" = ["user:a@b.com"] }}' - _, resources = plan_runner( - FIXTURES_DIR, iam_roles=iam_roles, iam_members=iam_members) - assert len(resources) == 4 + iam = '{"roles/owner" = ["user:a@b.com"] }' + _, resources = plan_runner(FIXTURES_DIR, iam=iam) + assert len(resources) == 2 + + +def test_iam_multiple_members(plan_runner): + "Test folder resources with multiple iam members." + iam = '{"roles/owner" = ["user:a@b.com", "user:c@d.com"] }' + _, resources = plan_runner(FIXTURES_DIR, iam=iam) + assert len(resources) == 2 + + +def test_iam_multiple_roles(plan_runner): + "Test folder resources with multiple iam roles." + iam = ( + '{ ' + '"roles/owner" = ["user:a@b.com"], ' + '"roles/viewer" = ["user:c@d.com"] ' + '} ' + ) + _, resources = plan_runner(FIXTURES_DIR, iam=iam) + assert len(resources) == 3 diff --git a/tests/modules/folders/test_plan_org_policies.py b/tests/modules/folder/test_plan_org_policies.py similarity index 64% rename from tests/modules/folders/test_plan_org_policies.py rename to tests/modules/folder/test_plan_org_policies.py index dde2b30c..09d83cf5 100644 --- a/tests/modules/folders/test_plan_org_policies.py +++ b/tests/modules/folder/test_plan_org_policies.py @@ -24,16 +24,14 @@ def test_policy_boolean(plan_runner): "Test boolean folder policy." policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}' _, resources = plan_runner(FIXTURES_DIR, policy_boolean=policy_boolean) - assert len(resources) == 8 + + assert len(resources) == 4 resources = [r for r in resources if r['type'] == 'google_folder_organization_policy'] assert sorted([r['index'] for r in resources]) == [ - 'folder-a-policy-a', - 'folder-a-policy-b', - 'folder-a-policy-c', - 'folder-b-policy-a', - 'folder-b-policy-b', - 'folder-b-policy-c' + 'policy-a', + 'policy-b', + 'policy-c', ] policy_values = [] for resource in resources: @@ -42,12 +40,9 @@ def test_policy_boolean(plan_runner): if value: policy_values.append((resource['index'], policy,) + value[0].popitem()) assert sorted(policy_values) == [ - ('folder-a-policy-a', 'boolean_policy', 'enforced', True), - ('folder-a-policy-b', 'boolean_policy', 'enforced', False), - ('folder-a-policy-c', 'restore_policy', 'default', True), - ('folder-b-policy-a', 'boolean_policy', 'enforced', True), - ('folder-b-policy-b', 'boolean_policy', 'enforced', False), - ('folder-b-policy-c', 'restore_policy', 'default', True) + ('policy-a', 'boolean_policy', 'enforced', True), + ('policy-b', 'boolean_policy', 'enforced', False), + ('policy-c', 'restore_policy', 'default', True), ] @@ -61,26 +56,20 @@ def test_policy_list(plan_runner): '}' ) _, resources = plan_runner(FIXTURES_DIR, policy_list=policy_list) - assert len(resources) == 8 + assert len(resources) == 4 resources = [r for r in resources if r['type'] == 'google_folder_organization_policy'] assert sorted([r['index'] for r in resources]) == [ - 'folder-a-policy-a', - 'folder-a-policy-b', - 'folder-a-policy-c', - 'folder-b-policy-a', - 'folder-b-policy-b', - 'folder-b-policy-c' + 'policy-a', + 'policy-b', + 'policy-c', ] values = [r['values'] for r in resources] assert [r['constraint'] for r in values] == [ - 'policy-a', 'policy-b', 'policy-c', 'policy-a', 'policy-b', 'policy-c' + 'policy-a', 'policy-b', 'policy-c' ] - for i in (0, 3): - assert values[i]['list_policy'][0]['allow'] == [ - {'all': True, 'values': None}] - for i in (1, 4): - assert values[i]['list_policy'][0]['deny'] == [ - {'all': False, 'values': ["bar"]}] - for i in (2, 5): - assert values[i]['restore_policy'] == [{'default': True}] + assert values[0]['list_policy'][0]['allow'] == [ + {'all': True, 'values': None}] + assert values[1]['list_policy'][0]['deny'] == [ + {'all': False, 'values': ["bar"]}] + assert values[2]['restore_policy'] == [{'default': True}] diff --git a/tests/modules/gcs/fixture/main.tf b/tests/modules/gcs/fixture/main.tf index 6275c616..711196cb 100644 --- a/tests/modules/gcs/fixture/main.tf +++ b/tests/modules/gcs/fixture/main.tf @@ -15,16 +15,15 @@ */ module "test" { - source = "../../../../modules/gcs" - project_id = "my-project" + source = "../../../../modules/gcs" + project_id = "my-project" uniform_bucket_level_access = var.uniform_bucket_level_access - force_destroy = var.force_destroy - iam_members = var.iam_members - iam_roles = var.iam_roles - labels = var.labels - logging_config = var.logging_config - names = ["bucket-a", "bucket-b"] - prefix = var.prefix - retention_policies = var.retention_policies - versioning = var.versioning + force_destroy = var.force_destroy + iam = var.iam + labels = var.labels + logging_config = var.logging_config + name = "bucket-a" + prefix = var.prefix + retention_policy = var.retention_policy + versioning = var.versioning } diff --git a/tests/modules/gcs/fixture/variables.tf b/tests/modules/gcs/fixture/variables.tf index f79485f8..c989a286 100644 --- a/tests/modules/gcs/fixture/variables.tf +++ b/tests/modules/gcs/fixture/variables.tf @@ -15,23 +15,18 @@ */ variable "uniform_bucket_level_access" { - type = map(bool) - default = { bucket-a = false } + type = bool + default = false } variable "force_destroy" { - type = map(bool) - default = { bucket-a = true } + type = bool + default = true } -variable "iam_members" { - type = map(map(list(string))) - default = null -} - -variable "iam_roles" { +variable "iam" { type = map(list(string)) - default = null + default = {} } variable "labels" { @@ -40,12 +35,13 @@ variable "labels" { } variable "logging_config" { - type = map(object({ + type = object({ log_bucket = string log_object_prefix = string - })) + }) default = { - bucket-a = { log_bucket = "foo", log_object_prefix = null } + log_bucket = "foo" + log_object_prefix = null } } @@ -59,13 +55,14 @@ variable "project_id" { default = "my-project" } -variable "retention_policies" { - type = map(object({ +variable "retention_policy" { + type = object({ retention_period = number is_locked = bool - })) + }) default = { - bucket-b = { retention_period = 5, is_locked = false } + retention_period = 5 + is_locked = false } } @@ -75,6 +72,6 @@ variable "storage_class" { } variable "versioning" { - type = map(bool) - default = { bucket-a = true } + type = bool + default = true } diff --git a/tests/modules/gcs/test_plan.py b/tests/modules/gcs/test_plan.py index 051eb042..4749b901 100644 --- a/tests/modules/gcs/test_plan.py +++ b/tests/modules/gcs/test_plan.py @@ -19,75 +19,42 @@ import pytest FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') - def test_buckets(plan_runner): "Test bucket resources." _, resources = plan_runner(FIXTURES_DIR) - assert len(resources) == 2 - assert set(r['type'] for r in resources) == set(['google_storage_bucket']) - assert set(r['values']['name'] for r in resources) == set([ - 'bucket-a', 'bucket-b' - ]) - assert set(r['values']['project'] for r in resources) == set([ - 'my-project' - ]) - + assert len(resources) == 1 + r = resources[0] + assert r['type'] == 'google_storage_bucket' + assert r['values']['name'] == 'bucket-a' + assert r['values']['project'] == 'my-project' def test_prefix(plan_runner): "Test bucket name when prefix is set." _, resources = plan_runner(FIXTURES_DIR, prefix='foo') - assert set(r['values']['name'] for r in resources) == set([ - 'foo-eu-bucket-a', 'foo-eu-bucket-b' - ]) + assert resources[0]['values']['name'] == 'foo-eu-bucket-a' -def test_map_values(plan_runner): - "Test that map values set the correct attributes on buckets." - _, resources = plan_runner(FIXTURES_DIR) - bpo = dict((r['values']['name'], r['values']['uniform_bucket_level_access']) - for r in resources) - assert bpo == {'bucket-a': False, 'bucket-b': True} - force_destroy = dict((r['values']['name'], r['values']['force_destroy']) - for r in resources) - assert force_destroy == {'bucket-a': True, 'bucket-b': False} - versioning = dict((r['values']['name'], r['values']['versioning']) - for r in resources) - assert versioning == { - 'bucket-a': [{'enabled': True}], 'bucket-b': [{'enabled': False}] - } - logging_config = dict((r['values']['name'], r['values']['logging']) - for r in resources) - assert logging_config == { - 'bucket-a': [{'log_bucket': 'foo'}], - 'bucket-b': [] - } - retention_policies = dict((r['values']['name'], r['values']['retention_policy']) - for r in resources) - assert retention_policies == { - 'bucket-a': [], - 'bucket-b': [{'is_locked': False, 'retention_period': 5}] - } - for r in resources: - assert r['values']['labels'] == { - 'environment': 'test', 'location': 'eu', - 'storage_class': 'multi_regional', 'name': r['values']['name'] - } - - -def test_iam_roles_only(plan_runner): - "Test bucket resources with only iam roles passed." - _, resources = plan_runner( - FIXTURES_DIR, iam_roles='{bucket-a = [ "roles/storage.admin"]}') - assert len(resources) == 3 +def test_config_values(plan_runner): + "Test that variables set the correct attributes on buckets." + variables = dict( + uniform_bucket_level_access='true', + force_destroy='true', + versioning='true' + ) + _, resources = plan_runner(FIXTURES_DIR, **variables) + assert len(resources) == 1 + r = resources[0] + assert r['values']['uniform_bucket_level_access'] is True + assert r['values']['force_destroy'] is True + assert r['values']['versioning'] == [{'enabled': True}] + assert r['values']['logging'] == [{'log_bucket': 'foo'}] + assert r['values']['retention_policy'] == [ + {'is_locked': False, 'retention_period': 5} + ] def test_iam(plan_runner): "Test bucket resources with iam roles and members." - iam_roles = ( - '{bucket-a = ["roles/storage.admin"], ' - 'bucket-b = ["roles/storage.objectAdmin"]}' - ) - iam_members = '{folder-a = { "roles/storage.admin" = ["user:a@b.com"] }}' - _, resources = plan_runner( - FIXTURES_DIR, iam_roles=iam_roles, iam_members=iam_members) - assert len(resources) == 4 + iam = '{ "roles/storage.admin" = ["user:a@b.com"] }' + _, resources = plan_runner(FIXTURES_DIR, iam=iam) + assert len(resources) == 2 diff --git a/tests/modules/iam_service_account/__init__.py b/tests/modules/iam_service_account/__init__.py new file mode 100644 index 00000000..6913f02e --- /dev/null +++ b/tests/modules/iam_service_account/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020 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. diff --git a/tests/modules/iam_service_accounts/fixture/main.tf b/tests/modules/iam_service_account/fixture/main.tf similarity index 83% rename from tests/modules/iam_service_accounts/fixture/main.tf rename to tests/modules/iam_service_account/fixture/main.tf index 69188086..a3ec7a81 100644 --- a/tests/modules/iam_service_accounts/fixture/main.tf +++ b/tests/modules/iam_service_account/fixture/main.tf @@ -15,13 +15,12 @@ */ module "test" { - source = "../../../../modules/iam-service-accounts" + source = "../../../../modules/iam-service-account" project_id = var.project_id - names = ["sa-one", "sa-two", "sa-three"] + name = "sa-one" prefix = var.prefix - generate_keys = var.generate_keys - iam_members = var.iam_members - iam_roles = var.iam_roles + generate_key = var.generate_key + iam = var.iam iam_billing_roles = var.iam_billing_roles iam_folder_roles = var.iam_folder_roles iam_organization_roles = var.iam_organization_roles diff --git a/tests/modules/iam_service_accounts/fixture/variables.tf b/tests/modules/iam_service_account/fixture/variables.tf similarity index 91% rename from tests/modules/iam_service_accounts/fixture/variables.tf rename to tests/modules/iam_service_account/fixture/variables.tf index 0a784a94..50f8590a 100644 --- a/tests/modules/iam_service_accounts/fixture/variables.tf +++ b/tests/modules/iam_service_account/fixture/variables.tf @@ -14,21 +14,16 @@ * limitations under the License. */ -variable "generate_keys" { +variable "generate_key" { type = bool default = false } -variable "iam_members" { +variable "iam" { type = map(list(string)) default = {} } -variable "iam_roles" { - type = list(string) - default = [] -} - variable "iam_billing_roles" { type = map(list(string)) default = {} diff --git a/tests/modules/iam_service_account/test_plan.py b/tests/modules/iam_service_account/test_plan.py new file mode 100644 index 00000000..b6c46040 --- /dev/null +++ b/tests/modules/iam_service_account/test_plan.py @@ -0,0 +1,50 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +def test_resources(plan_runner): + "Test service account resource." + _, resources = plan_runner(FIXTURES_DIR) + assert len(resources) == 1 + resource = resources[0] + assert resource['type'] == 'google_service_account' + assert resource['values']['account_id'] == 'sa-one' + + _, resources = plan_runner(FIXTURES_DIR, prefix='foo') + assert len(resources) == 1 + resource = resources[0] + assert resource['values']['account_id'] == 'foo-sa-one' + + +def test_iam_roles(plan_runner): + "Test iam roles with one member." + iam=('{"roles/iam.serviceAccountUser" = ["user:a@b.com"]}') + _, resources = plan_runner(FIXTURES_DIR, iam=iam) + assert len(resources) == 2 + iam_resources = [r for r in resources + if r['type'] != 'google_service_account'] + assert len(iam_resources) == 1 + + iam_resource = iam_resources[0] + assert iam_resource['type'] == 'google_service_account_iam_binding' + assert iam_resource['index'] == 'roles/iam.serviceAccountUser' + assert iam_resource['values']['role'] == 'roles/iam.serviceAccountUser' + assert iam_resource['values']['members'] == ["user:a@b.com"] diff --git a/tests/modules/iam_service_accounts/test_plan.py b/tests/modules/iam_service_accounts/test_plan.py deleted file mode 100644 index dbcb2bef..00000000 --- a/tests/modules/iam_service_accounts/test_plan.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2020 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. - - -import os -import pytest - - -FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') - - -def test_resources(plan_runner): - "Test service account resource." - _, resources = plan_runner(FIXTURES_DIR) - assert len(resources) == 3 - assert set(r['type'] for r in resources) == set(['google_service_account']) - assert set(r['values']['account_id'] for r in resources) == set([ - 'sa-one', 'sa-two', 'sa-three' - ]) - _, resources = plan_runner(FIXTURES_DIR, prefix='foo') - assert set(r['values']['account_id'] for r in resources) == set([ - 'foo-sa-one', 'foo-sa-two', 'foo-sa-three' - ]) - - -def test_iam_roles(plan_runner): - "Test iam roles with no memmbers." - _, resources = plan_runner(FIXTURES_DIR, - iam_roles='["roles/iam.serviceAccountUser"]') - assert len(resources) == 6 - iam_resources = [r for r in resources if r['type'] - != 'google_service_account'] - assert len(iam_resources) == 3 - assert set(r['type'] for r in iam_resources) == set( - ['google_service_account_iam_binding']) - assert [r['index'] for r in iam_resources] == [ - 'sa-one-roles/iam.serviceAccountUser', - 'sa-three-roles/iam.serviceAccountUser', - 'sa-two-roles/iam.serviceAccountUser', - ] diff --git a/tests/modules/kms/fixture/main.tf b/tests/modules/kms/fixture/main.tf index f027f978..c58824f8 100644 --- a/tests/modules/kms/fixture/main.tf +++ b/tests/modules/kms/fixture/main.tf @@ -16,10 +16,8 @@ module "test" { source = "../../../../modules/kms" - iam_members = var.iam_members - iam_roles = var.iam_roles - key_iam_members = var.key_iam_members - key_iam_roles = var.key_iam_roles + iam = var.iam + key_iam = var.key_iam key_purpose = var.key_purpose key_purpose_defaults = var.key_purpose_defaults keyring = var.keyring diff --git a/tests/modules/kms/fixture/variables.tf b/tests/modules/kms/fixture/variables.tf index 10f3f318..65124e67 100644 --- a/tests/modules/kms/fixture/variables.tf +++ b/tests/modules/kms/fixture/variables.tf @@ -14,19 +14,14 @@ * limitations under the License. */ -variable "iam_members" { +variable "iam" { type = map(list(string)) default = { "roles/owner" = ["user:ludo@ludomagno.net"] } } -variable "iam_roles" { - type = list(string) - default = ["roles/owner"] -} - -variable "key_iam_members" { +variable "key_iam" { type = map(map(list(string))) default = { key-a = { @@ -35,13 +30,6 @@ variable "key_iam_members" { } } -variable "key_iam_roles" { - type = map(list(string)) - default = { - key-a = ["roles/owner"] - } -} - variable "key_purpose" { type = map(object({ purpose = string diff --git a/tests/modules/net_vpc/fixture/main.tf b/tests/modules/net_vpc/fixture/main.tf index a9d92d47..03b74124 100644 --- a/tests/modules/net_vpc/fixture/main.tf +++ b/tests/modules/net_vpc/fixture/main.tf @@ -18,8 +18,7 @@ module "test" { source = "../../../../modules/net-vpc" project_id = var.project_id name = var.name - iam_members = var.iam_members - iam_roles = var.iam_roles + iam = var.iam log_configs = var.log_configs log_config_defaults = var.log_config_defaults peering_config = var.peering_config diff --git a/tests/modules/net_vpc/fixture/variables.tf b/tests/modules/net_vpc/fixture/variables.tf index 908548dd..0a19ef07 100644 --- a/tests/modules/net_vpc/fixture/variables.tf +++ b/tests/modules/net_vpc/fixture/variables.tf @@ -29,13 +29,8 @@ variable "auto_create_subnetworks" { default = false } -variable "iam_roles" { - type = map(list(string)) - default = null -} - -variable "iam_members" { - type = map(map(list(string))) +variable "iam" { + type = map(map(set(string))) default = null } diff --git a/tests/modules/organization/fixture/main.tf b/tests/modules/organization/fixture/main.tf index 63d1f466..6c5d0bca 100644 --- a/tests/modules/organization/fixture/main.tf +++ b/tests/modules/organization/fixture/main.tf @@ -15,13 +15,12 @@ */ module "test" { - source = "../../../../modules/organization" - org_id = 1234567890 - custom_roles = var.custom_roles - iam_members = var.iam_members - iam_roles = var.iam_roles - iam_additive_bindings= var.iam_additive_bindings - iam_audit_config = var.iam_audit_config - policy_boolean = var.policy_boolean - policy_list = var.policy_list + source = "../../../../modules/organization" + org_id = 1234567890 + custom_roles = var.custom_roles + iam = var.iam + iam_additive = var.iam_additive + iam_audit_config = var.iam_audit_config + policy_boolean = var.policy_boolean + policy_list = var.policy_list } diff --git a/tests/modules/organization/fixture/variables.tf b/tests/modules/organization/fixture/variables.tf index 148a43b7..887c3345 100644 --- a/tests/modules/organization/fixture/variables.tf +++ b/tests/modules/organization/fixture/variables.tf @@ -19,22 +19,16 @@ variable "custom_roles" { default = {} } -variable "iam_members" { +variable "iam" { type = map(list(string)) default = {} } -variable "iam_roles" { - type = list(string) - default = [] -} - -variable "iam_additive_bindings" { +variable "iam_additive" { type = map(list(string)) default = {} } - variable "iam_audit_config" { type = map(map(list(string))) default = {} diff --git a/tests/modules/project/fixture/main.tf b/tests/modules/project/fixture/main.tf index 924b2648..e7a9fd0e 100644 --- a/tests/modules/project/fixture/main.tf +++ b/tests/modules/project/fixture/main.tf @@ -15,22 +15,21 @@ */ module "test" { - source = "../../../../modules/project" - name = "my-project" - billing_account = "12345-12345-12345" - auto_create_network = var.auto_create_network - custom_roles = var.custom_roles - iam_members = var.iam_members - iam_roles = var.iam_roles - iam_additive_bindings = var.iam_additive_bindings - labels = var.labels - lien_reason = var.lien_reason - oslogin = var.oslogin - oslogin_admins = var.oslogin_admins - oslogin_users = var.oslogin_users - parent = var.parent - policy_boolean = var.policy_boolean - policy_list = var.policy_list - prefix = var.prefix - services = var.services + source = "../../../../modules/project" + name = "my-project" + billing_account = "12345-12345-12345" + auto_create_network = var.auto_create_network + custom_roles = var.custom_roles + iam = var.iam + iam_additive = var.iam_additive + labels = var.labels + lien_reason = var.lien_reason + oslogin = var.oslogin + oslogin_admins = var.oslogin_admins + oslogin_users = var.oslogin_users + parent = var.parent + policy_boolean = var.policy_boolean + policy_list = var.policy_list + prefix = var.prefix + services = var.services } diff --git a/tests/modules/project/fixture/variables.tf b/tests/modules/project/fixture/variables.tf index 3b36a5fd..1a60f856 100644 --- a/tests/modules/project/fixture/variables.tf +++ b/tests/modules/project/fixture/variables.tf @@ -24,17 +24,12 @@ variable "custom_roles" { default = {} } -variable "iam_members" { +variable "iam" { type = map(list(string)) default = {} } -variable "iam_roles" { - type = list(string) - default = [] -} - -variable "iam_additive_bindings" { +variable "iam_additive" { type = map(list(string)) default = {} } diff --git a/tests/modules/pubsub/__init__.py b/tests/modules/pubsub/__init__.py new file mode 100644 index 00000000..6913f02e --- /dev/null +++ b/tests/modules/pubsub/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020 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. diff --git a/tests/modules/pubsub/fixture/main.tf b/tests/modules/pubsub/fixture/main.tf new file mode 100644 index 00000000..aa4468a6 --- /dev/null +++ b/tests/modules/pubsub/fixture/main.tf @@ -0,0 +1,34 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../modules/pubsub" + project_id = "my-project" + regions = ["europe-west1"] + name = "test" + iam = { + "roles/pubsub.publisher" = ["user:me@example.com"] + } + subscriptions = { + test = null + } + subscription_iam = { + test = { + "roles/pubsub.subscriber" = ["user:me@example.com"] + } + } + labels = var.labels +} diff --git a/tests/modules/pubsub/fixture/variables.tf b/tests/modules/pubsub/fixture/variables.tf new file mode 100644 index 00000000..4e0e0396 --- /dev/null +++ b/tests/modules/pubsub/fixture/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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 "labels" { + type = map(string) + default = {} +} diff --git a/tests/modules/pubsub/test_plan.py b/tests/modules/pubsub/test_plan.py new file mode 100644 index 00000000..424f481c --- /dev/null +++ b/tests/modules/pubsub/test_plan.py @@ -0,0 +1,55 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +@pytest.fixture +def resources(plan_runner): + _, resources = plan_runner(FIXTURES_DIR) + return resources + + +def test_resource_count(resources): + "Test number of resources created." + assert len(resources) == 4 + + +def test_iam(resources): + "Test IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_pubsub_topic_iam_binding'] + assert len(bindings) == 1 + assert bindings[0]['role'] == 'roles/pubsub.publisher' + + +def test_subscriptions(resources): + "Test subscription resources." + subs = [r['values'] for r in resources if r['type'] + == 'google_pubsub_subscription'] + assert len(subs) == 1 + assert set(s['name'] for s in subs) == set(['test']) + + +def test_subscription_iam(resources): + "Test subscription IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_pubsub_subscription_iam_binding'] + assert len(bindings) == 1 + assert set(b['role'] for b in bindings) == set(['roles/pubsub.subscriber']) diff --git a/tests/modules/secret_manager/__init__.py b/tests/modules/secret_manager/__init__.py new file mode 100644 index 00000000..6913f02e --- /dev/null +++ b/tests/modules/secret_manager/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020 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. diff --git a/tests/modules/secret_manager/fixture/main.tf b/tests/modules/secret_manager/fixture/main.tf new file mode 100644 index 00000000..42b43102 --- /dev/null +++ b/tests/modules/secret_manager/fixture/main.tf @@ -0,0 +1,42 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../modules/secret-manager" + project_id = "my-project" + iam = { + secret-1 = { + "roles/secretmanager.secretAccessor" = [ + "serviceAccount:service-account.example.com" + ] + } + secret-2 = { + "roles/secretmanager.viewer" = [ + "serviceAccount:service-account.example.com" + ] + } + } + secrets = { + secret-1 = ["europe-west1"], + secret-2 = null + } + versions = { + secret-1 = { + foobar = { enabled = true, data = "foobar" } + } + } + labels = var.labels +} diff --git a/tests/modules/secret_manager/fixture/variables.tf b/tests/modules/secret_manager/fixture/variables.tf new file mode 100644 index 00000000..12460698 --- /dev/null +++ b/tests/modules/secret_manager/fixture/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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 "labels" { + type = map(map(string)) + default = {} +} diff --git a/tests/modules/secret_manager/test_plan.py b/tests/modules/secret_manager/test_plan.py new file mode 100644 index 00000000..44f1f5db --- /dev/null +++ b/tests/modules/secret_manager/test_plan.py @@ -0,0 +1,41 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +@pytest.fixture +def resources(plan_runner): + _, resources = plan_runner(FIXTURES_DIR) + return resources + + +def test_resource_count(resources): + "Test number of resources created." + assert len(resources) == 5 + + +def test_secret_iam(resources): + "Test secret IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_secret_manager_secret_iam_binding'] + assert len(bindings) == 2 + assert set(b['role'] for b in bindings) == set([ + 'roles/secretmanager.secretAccessor', 'roles/secretmanager.viewer' + ]) diff --git a/tests/modules/service_directory/__init__.py b/tests/modules/service_directory/__init__.py new file mode 100644 index 00000000..6913f02e --- /dev/null +++ b/tests/modules/service_directory/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020 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. diff --git a/tests/modules/service_directory/fixture/main.tf b/tests/modules/service_directory/fixture/main.tf new file mode 100644 index 00000000..91835372 --- /dev/null +++ b/tests/modules/service_directory/fixture/main.tf @@ -0,0 +1,55 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../modules/service-directory" + project_id = "my-project" + location = "europe-west1" + name = "ns-test" + iam = { + "roles/servicedirectory.viewer" = [ + "serviceAccount:service-editor.example.com" + ] + } + services = { + srv-one = { + endpoints = ["alpha", "beta"] + metadata = null + } + srv-two = { + endpoints = ["alpha"] + metadata = null + } + } + service_iam = { + srv-one = { + "roles/servicedirectory.editor" = [ + "serviceAccount:service-editor.example.com" + ] + } + srv-two = { + "roles/servicedirectory.admin" = [ + "serviceAccount:service-editor.example.com" + ] + } + } + endpoint_config = { + "srv-one/alpha" = { address = "127.0.0.1", port = 80, metadata = {} } + "srv-one/beta" = { address = "127.0.0.2", port = 80, metadata = {} } + "srv-two/alpha" = { address = "127.0.0.3", port = 80, metadata = {} } + } + labels = var.labels +} diff --git a/tests/modules/service_directory/fixture/variables.tf b/tests/modules/service_directory/fixture/variables.tf new file mode 100644 index 00000000..4e0e0396 --- /dev/null +++ b/tests/modules/service_directory/fixture/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2020 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 "labels" { + type = map(string) + default = {} +} diff --git a/tests/modules/service_directory/test_plan.py b/tests/modules/service_directory/test_plan.py new file mode 100644 index 00000000..52b62abe --- /dev/null +++ b/tests/modules/service_directory/test_plan.py @@ -0,0 +1,57 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +@pytest.fixture +def resources(plan_runner): + _, resources = plan_runner(FIXTURES_DIR) + return resources + + +def test_resource_count(resources): + "Test number of resources created." + assert len(resources) == 9 + + +def test_iam(resources): + "Test IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_service_directory_namespace_iam_binding'] + assert len(bindings) == 1 + assert bindings[0]['role'] == 'roles/servicedirectory.viewer' + + +def test_services(resources): + "Test service resources." + services = [r['values'] for r in resources if r['type'] + == 'google_service_directory_service'] + assert len(services) == 2 + assert set(s['service_id'] for s in services) == set(['srv-one', 'srv-two']) + + +def test_service_iam(resources): + "Test service IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_service_directory_service_iam_binding'] + assert len(bindings) == 2 + assert set(b['role'] for b in bindings) == set([ + 'roles/servicedirectory.admin', 'roles/servicedirectory.editor' + ]) diff --git a/tests/modules/source_repository/__init__.py b/tests/modules/source_repository/__init__.py new file mode 100644 index 00000000..6913f02e --- /dev/null +++ b/tests/modules/source_repository/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020 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. diff --git a/tests/modules/source_repository/fixture/main.tf b/tests/modules/source_repository/fixture/main.tf new file mode 100644 index 00000000..14bb53d9 --- /dev/null +++ b/tests/modules/source_repository/fixture/main.tf @@ -0,0 +1,22 @@ +/** + * Copyright 2020 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 "test" { + source = "../../../../modules/source-repository" + project_id = var.project_id + name = var.name + iam = var.iam +} diff --git a/tests/modules/source_repository/fixture/variables.tf b/tests/modules/source_repository/fixture/variables.tf new file mode 100644 index 00000000..dd58ca52 --- /dev/null +++ b/tests/modules/source_repository/fixture/variables.tf @@ -0,0 +1,32 @@ +/** + * Copyright 2020 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" { + type = string + default = "test-project" +} + +variable "iam" { + type = map(list(string)) + default = { + "roles/source.reader" = ["foo@example.org"] + } +} + +variable "name" { + type = string + default = "test" +} diff --git a/tests/modules/source_repository/test_plan.py b/tests/modules/source_repository/test_plan.py new file mode 100644 index 00000000..88ff4a69 --- /dev/null +++ b/tests/modules/source_repository/test_plan.py @@ -0,0 +1,39 @@ +# Copyright 2020 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + + +@pytest.fixture +def resources(plan_runner): + _, resources = plan_runner(FIXTURES_DIR) + return resources + + +def test_resource_count(resources): + "Test number of resources created." + assert len(resources) == 2 + + +def test_iam(resources): + "Test IAM binding resources." + bindings = [r['values'] for r in resources if r['type'] + == 'google_sourcerepo_repository_iam_binding'] + assert len(bindings) == 1 + assert bindings[0]['role'] == 'roles/source.reader' diff --git a/tests/networking/hub_and_spoke_peering/test_plan.py b/tests/networking/hub_and_spoke_peering/test_plan.py index 34853ffa..fad58fb1 100644 --- a/tests/networking/hub_and_spoke_peering/test_plan.py +++ b/tests/networking/hub_and_spoke_peering/test_plan.py @@ -23,5 +23,7 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture') def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner(FIXTURES_DIR) + import pprint + pprint.pprint(resources) assert len(modules) == 18 - assert len(resources) == 57 + assert len(resources) == 53 diff --git a/tests/requirements.txt b/tests/requirements.txt index 55e326c7..f233348b 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,3 @@ pytest>=4.6.0 PyYAML>=5.3 -tftest>=1.5.1 +tftest>=1.5.2