diff --git a/tests/fixtures.py b/tests/fixtures.py index 5fe02f1bd..88674853b 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -108,8 +108,9 @@ def plan_summary(module_path, basedir, tf_var_files=None, extra_files=None, extra_dirs = [ (module_path / dirname).resolve() for dirname in extra_dirs or [] ] - tf.setup(extra_files=extra_files + extra_dirs, upgrade=True) - # raise SystemExit(extra_dirs) + tf.setup(extra_files=extra_files, upgrade=True) + for extra_dir in extra_dirs: + os.symlink(extra_dir, tf.tfdir / extra_dir.name) tf_var_files = [(basedir / x).resolve() for x in tf_var_files or []] plan = tf.plan(output=True, tf_var_file=tf_var_files, tf_vars=tf_vars) diff --git a/tests/modules/project_factory/bucket_iam.tfvars b/tests/modules/project_factory/bucket_iam.tfvars new file mode 100644 index 000000000..c9163d0f8 --- /dev/null +++ b/tests/modules/project_factory/bucket_iam.tfvars @@ -0,0 +1,54 @@ +# Copyright 2025 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. + +data_defaults = { + billing_account = "1245-5678-9012" + storage_location = "EU" +} +# make sure the environment label and stackdriver service are always added +data_merges = { + labels = { + environment = "test" + } + services = [ + "stackdriver.googleapis.com" + ] +} +# always use this contacts and prefix, regardless of what is in the yaml file +data_overrides = { + contacts = { + "admin@example.org" = ["ALL"] + } + prefix = "test-pf" +} +# location where the yaml files are read from +factories_config = { + folders_data_path = "bucket_iam/hierarchy" + projects_data_path = "bucket_iam/projects" + context = { + folder_ids = { + default = "folders/5678901234" + teams = "folders/5678901234" + } + iam_principals = { + gcp-devops = "group:gcp-devops@example.org" + } + tag_values = { + "org-policies/drs-allow-all" = "tagValues/123456" + } + vpc_host_projects = { + dev-spoke-0 = "test-pf-dev-net-spoke-0" + } + } +} diff --git a/tests/modules/project_factory/bucket_iam.yaml b/tests/modules/project_factory/bucket_iam.yaml new file mode 100644 index 000000000..fc7bf9f95 --- /dev/null +++ b/tests/modules/project_factory/bucket_iam.yaml @@ -0,0 +1,275 @@ +# Copyright 2025 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. + +values: + module.buckets["project1/state"].google_storage_bucket.bucket[0]: + autoclass: [] + cors: [] + custom_placement_config: [] + default_event_based_hold: null + effective_labels: + goog-terraform-provisioned: 'true' + enable_object_retention: null + encryption: [] + force_destroy: false + hierarchical_namespace: [] + labels: null + lifecycle_rule: [] + location: EUROPE-WEST8 + logging: [] + name: test-pf-project1-state + project: test-pf-project1 + requester_pays: null + retention_policy: [] + storage_class: STANDARD + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + uniform_bucket_level_access: true + versioning: + - enabled: false + module.buckets["project1/state"].google_storage_bucket_iam_binding.authoritative["roles/storage.admin"]: + bucket: test-pf-project1-state + condition: [] + members: + - serviceAccount:terraform-rw@test-pf-project1.iam.gserviceaccount.com + role: roles/storage.admin + module.buckets["project2/state"].google_storage_bucket.bucket[0]: + autoclass: [] + cors: [] + custom_placement_config: [] + default_event_based_hold: null + effective_labels: + goog-terraform-provisioned: 'true' + enable_object_retention: null + encryption: [] + force_destroy: false + hierarchical_namespace: [] + labels: null + lifecycle_rule: [] + location: EUROPE-WEST8 + logging: [] + name: test-pf-project2-state + project: test-pf-project2 + requester_pays: null + retention_policy: [] + storage_class: STANDARD + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + uniform_bucket_level_access: true + versioning: + - enabled: false + module.hierarchy-folder-lvl-1["team-a"].google_folder.folder[0]: + deletion_protection: false + display_name: Team A + parent: folders/5678901234 + tags: null + timeouts: null + module.hierarchy-folder-lvl-1["team-a"].google_folder_iam_binding.authoritative["roles/viewer"]: + condition: [] + members: + - group:gcp-devops@example.org + - group:team-a-admins@example.org + role: roles/viewer + module.projects["project1"].data.google_storage_project_service_account.gcs_sa[0]: + project: test-pf-project1 + user_project: null + module.projects["project1"].google_essential_contacts_contact.contact["admin@example.org"]: + email: admin@example.org + language_tag: en + notification_category_subscriptions: + - ALL + parent: projects/test-pf-project1 + timeouts: null + module.projects["project1"].google_project.project[0]: + auto_create_network: false + billing_account: 012345-67890A-BCDEF0 + deletion_policy: DELETE + effective_labels: + environment: test + goog-terraform-provisioned: 'true' + labels: + environment: test + name: test-pf-project1 + project_id: test-pf-project1 + tags: null + terraform_labels: + environment: test + goog-terraform-provisioned: 'true' + timeouts: null + module.projects["project1"].google_project_iam_member.service_agents["container-engine-robot"]: + condition: [] + project: test-pf-project1 + role: roles/container.serviceAgent + module.projects["project1"].google_project_iam_member.service_agents["gkenode"]: + condition: [] + project: test-pf-project1 + role: roles/container.defaultNodeServiceAgent + module.projects["project1"].google_project_service.project_services["container.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-pf-project1 + service: container.googleapis.com + timeouts: null + module.projects["project1"].google_project_service.project_services["stackdriver.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-pf-project1 + service: stackdriver.googleapis.com + timeouts: null + module.projects["project1"].google_project_service.project_services["storage.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-pf-project1 + service: storage.googleapis.com + timeouts: null + module.projects["project1"].google_project_service_identity.default["container.googleapis.com"]: + project: test-pf-project1 + service: container.googleapis.com + timeouts: null + module.projects["project2"].google_essential_contacts_contact.contact["admin@example.org"]: + email: admin@example.org + language_tag: en + notification_category_subscriptions: + - ALL + parent: projects/test-pf-project2 + timeouts: null + module.projects["project2"].google_project.project[0]: + auto_create_network: false + billing_account: 012345-67890A-BCDEF0 + deletion_policy: DELETE + effective_labels: + app: app-0 + environment: test + goog-terraform-provisioned: 'true' + team: team-a + labels: + app: app-0 + environment: test + team: team-a + name: test-pf-project2 + project_id: test-pf-project2 + tags: null + terraform_labels: + app: app-0 + environment: test + goog-terraform-provisioned: 'true' + team: team-a + timeouts: null + module.projects["project2"].google_project_service.project_services["stackdriver.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-pf-project2 + service: stackdriver.googleapis.com + timeouts: null + module.service-accounts["project1/app-be-0"].google_service_account.service_account[0]: + account_id: app-be-0 + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: app-be-0@test-pf-project1.iam.gserviceaccount.com + member: serviceAccount:app-be-0@test-pf-project1.iam.gserviceaccount.com + project: test-pf-project1 + timeouts: null + ? module.service-accounts["project1/app-fe-1"].google_project_iam_member.project-roles["my-host-project-roles/compute.networkUser"] + : condition: [] + project: my-host-project + role: roles/compute.networkUser + ? module.service-accounts["project1/app-fe-1"].google_project_iam_member.project-roles["test-pf-project1-roles/storage.objectViewer"] + : condition: [] + project: test-pf-project1 + role: roles/storage.objectViewer + module.service-accounts["project1/app-fe-1"].google_service_account.service_account[0]: + account_id: app-fe-1 + create_ignore_already_exists: null + description: null + disabled: false + display_name: GCE frontend service account. + email: app-fe-1@test-pf-project1.iam.gserviceaccount.com + member: serviceAccount:app-fe-1@test-pf-project1.iam.gserviceaccount.com + project: test-pf-project1 + timeouts: null + module.service-accounts["project1/terraform-rw"].google_service_account.service_account[0]: + account_id: terraform-rw + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: terraform-rw@test-pf-project1.iam.gserviceaccount.com + member: serviceAccount:terraform-rw@test-pf-project1.iam.gserviceaccount.com + project: test-pf-project1 + timeouts: null + module.service-accounts["project2/app-be-0"].google_service_account.service_account[0]: + account_id: app-be-0 + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: app-be-0@test-pf-project2.iam.gserviceaccount.com + member: serviceAccount:app-be-0@test-pf-project2.iam.gserviceaccount.com + project: test-pf-project2 + timeouts: null + ? module.service-accounts["project2/app-fe-1"].google_project_iam_member.project-roles["my-host-project-roles/compute.networkUser"] + : condition: [] + project: my-host-project + role: roles/compute.networkUser + ? module.service-accounts["project2/app-fe-1"].google_project_iam_member.project-roles["test-pf-project2-roles/storage.objectViewer"] + : condition: [] + project: test-pf-project2 + role: roles/storage.objectViewer + module.service-accounts["project2/app-fe-1"].google_service_account.service_account[0]: + account_id: app-fe-1 + create_ignore_already_exists: null + description: null + disabled: false + display_name: GCE frontend service account. + email: app-fe-1@test-pf-project2.iam.gserviceaccount.com + member: serviceAccount:app-fe-1@test-pf-project2.iam.gserviceaccount.com + project: test-pf-project2 + timeouts: null + module.service-accounts["project2/terraform-rw"].google_service_account.service_account[0]: + account_id: terraform-rw + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: terraform-rw@test-pf-project2.iam.gserviceaccount.com + member: serviceAccount:terraform-rw@test-pf-project2.iam.gserviceaccount.com + project: test-pf-project2 + timeouts: null + +counts: + google_essential_contacts_contact: 2 + google_folder: 1 + google_folder_iam_binding: 1 + google_project: 2 + google_project_iam_member: 6 + google_project_service: 4 + google_project_service_identity: 1 + google_service_account: 6 + google_storage_bucket: 2 + google_storage_bucket_iam_binding: 1 + google_storage_project_service_account: 1 + modules: 11 + resources: 27 + +outputs: + buckets: + project1/state: test-pf-project1-state + project2/state: test-pf-project2-state + folders: __missing__ + projects: __missing__ + service_accounts: __missing__ diff --git a/tests/modules/project_factory/data/bucket_iam/hierarchy/team-a/_config.yaml b/tests/modules/project_factory/data/bucket_iam/hierarchy/team-a/_config.yaml new file mode 100644 index 000000000..906fec0d8 --- /dev/null +++ b/tests/modules/project_factory/data/bucket_iam/hierarchy/team-a/_config.yaml @@ -0,0 +1,20 @@ +# Copyright 2025 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. + +name: Team A +# implicit parent definition via 'default' key +iam: + roles/viewer: + - group:team-a-admins@example.org + - gcp-devops diff --git a/tests/modules/project_factory/data/bucket_iam/hierarchy/team-a/project1.yaml b/tests/modules/project_factory/data/bucket_iam/hierarchy/team-a/project1.yaml new file mode 100644 index 000000000..16965ce89 --- /dev/null +++ b/tests/modules/project_factory/data/bucket_iam/hierarchy/team-a/project1.yaml @@ -0,0 +1,35 @@ +# Copyright 2025 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. + +billing_account: 012345-67890A-BCDEF0 +services: + - container.googleapis.com + - storage.googleapis.com + +service_accounts: + app-be-0: {} + app-fe-1: + display_name: GCE frontend service account. + iam_self_roles: + - roles/storage.objectViewer + iam_project_roles: + my-host-project: + - roles/compute.networkUser + terraform-rw: {} +buckets: + state: + location: europe-west8 + iam: + roles/storage.admin: + - terraform-rw diff --git a/tests/modules/project_factory/data/bucket_iam/projects/project2.yaml b/tests/modules/project_factory/data/bucket_iam/projects/project2.yaml new file mode 100644 index 000000000..d169831d9 --- /dev/null +++ b/tests/modules/project_factory/data/bucket_iam/projects/project2.yaml @@ -0,0 +1,36 @@ +# Copyright 2025 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. + +billing_account: 012345-67890A-BCDEF0 +labels: + app: app-0 + team: team-a +parent: team-a +buckets: + state: + location: europe-west8 +# iam: +# roles/storage.admin: +# - terraform-rw + +service_accounts: + app-be-0: {} + app-fe-1: + display_name: GCE frontend service account. + iam_self_roles: + - roles/storage.objectViewer + iam_project_roles: + my-host-project: + - roles/compute.networkUser + terraform-rw: {} diff --git a/tests/modules/project_factory/data/shared_vpc_network_user/projects/service1.yaml b/tests/modules/project_factory/data/shared_vpc_network_user/projects/service1.yaml new file mode 100644 index 000000000..5f38e4606 --- /dev/null +++ b/tests/modules/project_factory/data/shared_vpc_network_user/projects/service1.yaml @@ -0,0 +1,35 @@ +# Copyright 2025 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. + +billing_account: 012345-67890A-BCDEF0 + +service_accounts: + app-be-0: {} + terraform-rw: {} + +automation: + project: service-iac + service_accounts: + rw: + description: Service read/write automation sa. + ro: + description: Service read-only automation sa. + + +shared_vpc_service_config: + host_project: dev-spoke-0 + network_users: + - terraform-rw + - ro + - rw diff --git a/tests/modules/project_factory/data/shared_vpc_network_user/projects/service2.yaml b/tests/modules/project_factory/data/shared_vpc_network_user/projects/service2.yaml new file mode 100644 index 000000000..64adb5bb8 --- /dev/null +++ b/tests/modules/project_factory/data/shared_vpc_network_user/projects/service2.yaml @@ -0,0 +1,22 @@ +# Copyright 2025 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. + +billing_account: 012345-67890A-BCDEF0 +services: + - compute.googleapis.com + - storage.googleapis.com + +service_accounts: + app-be-0: {} + terraform-rw: {} diff --git a/tests/modules/project_factory/shared_vpc_network_user.tfvars b/tests/modules/project_factory/shared_vpc_network_user.tfvars new file mode 100644 index 000000000..6eff1d58e --- /dev/null +++ b/tests/modules/project_factory/shared_vpc_network_user.tfvars @@ -0,0 +1,39 @@ +# Copyright 2025 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. + +data_defaults = { + billing_account = "1245-5678-9012" + storage_location = "EU" + prefix = "my-prefix" + parent = "folders/1234" +} +# location where the yaml files are read from +factories_config = { + projects_data_path = "projects" + context = { + folder_ids = { + default = "folders/5678901234" + teams = "folders/5678901234" + } + iam_principals = { + gcp-devops = "group:gcp-devops@example.org" + } + tag_values = { + "org-policies/drs-allow-all" = "tagValues/123456" + } + vpc_host_projects = { + dev-spoke-0 = "test-pf-dev-net-spoke-0" + } + } +} diff --git a/tests/modules/project_factory/shared_vpc_network_user.yaml b/tests/modules/project_factory/shared_vpc_network_user.yaml new file mode 100644 index 000000000..e33887f31 --- /dev/null +++ b/tests/modules/project_factory/shared_vpc_network_user.yaml @@ -0,0 +1,161 @@ +# Copyright 2025 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. + + +values: + module.automation-service-accounts["service1/ro"].google_service_account.service_account[0]: + account_id: my-prefix-service1-ro + create_ignore_already_exists: null + description: Service read-only automation sa. + disabled: false + display_name: Service account ro for service1. + email: my-prefix-service1-ro@service-iac.iam.gserviceaccount.com + member: serviceAccount:my-prefix-service1-ro@service-iac.iam.gserviceaccount.com + project: service-iac + timeouts: null + module.automation-service-accounts["service1/rw"].google_service_account.service_account[0]: + account_id: my-prefix-service1-rw + create_ignore_already_exists: null + description: Service read/write automation sa. + disabled: false + display_name: Service account rw for service1. + email: my-prefix-service1-rw@service-iac.iam.gserviceaccount.com + member: serviceAccount:my-prefix-service1-rw@service-iac.iam.gserviceaccount.com + project: service-iac + timeouts: null + module.projects-iam["service1"].google_compute_shared_vpc_service_project.shared_vpc_service[0]: + deletion_policy: null + host_project: test-pf-dev-net-spoke-0 + service_project: my-prefix-service1 + timeouts: null + ? module.projects-iam["service1"].google_project_iam_member.shared_vpc_host_iam["serviceAccount:my-prefix-service1-ro@service-iac.iam.gserviceaccount.com"] + : condition: [] + member: serviceAccount:my-prefix-service1-ro@service-iac.iam.gserviceaccount.com + project: test-pf-dev-net-spoke-0 + role: roles/compute.networkUser + ? module.projects-iam["service1"].google_project_iam_member.shared_vpc_host_iam["serviceAccount:my-prefix-service1-rw@service-iac.iam.gserviceaccount.com"] + : condition: [] + member: serviceAccount:my-prefix-service1-rw@service-iac.iam.gserviceaccount.com + project: test-pf-dev-net-spoke-0 + role: roles/compute.networkUser + ? module.projects-iam["service1"].google_project_iam_member.shared_vpc_host_iam["serviceAccount:terraform-rw@my-prefix-service1.iam.gserviceaccount.com"] + : condition: [] + member: serviceAccount:terraform-rw@my-prefix-service1.iam.gserviceaccount.com + project: test-pf-dev-net-spoke-0 + role: roles/compute.networkUser + module.projects["service1"].google_project.project[0]: + auto_create_network: false + billing_account: 012345-67890A-BCDEF0 + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: 'true' + folder_id: '1234' + labels: null + name: my-prefix-service1 + org_id: null + project_id: my-prefix-service1 + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + module.projects["service2"].data.google_storage_project_service_account.gcs_sa[0]: + project: my-prefix-service2 + user_project: null + module.projects["service2"].google_project.project[0]: + auto_create_network: false + billing_account: 012345-67890A-BCDEF0 + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: 'true' + folder_id: '1234' + labels: null + name: my-prefix-service2 + org_id: null + project_id: my-prefix-service2 + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + module.projects["service2"].google_project_iam_member.service_agents["compute-system"]: + condition: [] + project: my-prefix-service2 + role: roles/compute.serviceAgent + module.projects["service2"].google_project_service.project_services["compute.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: my-prefix-service2 + service: compute.googleapis.com + timeouts: null + module.projects["service2"].google_project_service.project_services["storage.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: my-prefix-service2 + service: storage.googleapis.com + timeouts: null + module.service-accounts["service1/app-be-0"].google_service_account.service_account[0]: + account_id: app-be-0 + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: app-be-0@my-prefix-service1.iam.gserviceaccount.com + member: serviceAccount:app-be-0@my-prefix-service1.iam.gserviceaccount.com + project: my-prefix-service1 + timeouts: null + module.service-accounts["service1/terraform-rw"].google_service_account.service_account[0]: + account_id: terraform-rw + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: terraform-rw@my-prefix-service1.iam.gserviceaccount.com + member: serviceAccount:terraform-rw@my-prefix-service1.iam.gserviceaccount.com + project: my-prefix-service1 + timeouts: null + module.service-accounts["service2/app-be-0"].google_service_account.service_account[0]: + account_id: app-be-0 + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: app-be-0@my-prefix-service2.iam.gserviceaccount.com + member: serviceAccount:app-be-0@my-prefix-service2.iam.gserviceaccount.com + project: my-prefix-service2 + timeouts: null + module.service-accounts["service2/terraform-rw"].google_service_account.service_account[0]: + account_id: terraform-rw + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: terraform-rw@my-prefix-service2.iam.gserviceaccount.com + member: serviceAccount:terraform-rw@my-prefix-service2.iam.gserviceaccount.com + project: my-prefix-service2 + timeouts: null + +counts: + google_compute_shared_vpc_service_project: 1 + google_project: 2 + google_project_iam_member: 4 + google_project_service: 2 + google_service_account: 6 + google_storage_project_service_account: 1 + modules: 9 + resources: 16 + +outputs: + buckets: {} + folders: {} + projects: __missing__ + service_accounts: __missing__ diff --git a/tests/modules/project_factory/tftest.yaml b/tests/modules/project_factory/tftest.yaml new file mode 100644 index 000000000..a33c93964 --- /dev/null +++ b/tests/modules/project_factory/tftest.yaml @@ -0,0 +1,23 @@ +# Copyright 2025 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: modules/project-factory + +tests: + bucket_iam: + extra_dirs: + - ../../tests/modules/project_factory/data/bucket_iam + shared_vpc_network_user: + extra_dirs: + - ../../tests/modules/project_factory/data/shared_vpc_network_user/projects diff --git a/tools/tftest_plan_summary.py b/tools/tftest_plan_summary.py new file mode 100755 index 000000000..65c6bea5a --- /dev/null +++ b/tools/tftest_plan_summary.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +# Copyright 2025 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. + +import click +import sys +import yaml + +from pathlib import Path + +try: + import fixtures +except ImportError: + BASEDIR = Path(__file__).parents[1] + sys.path.append(str(BASEDIR / 'tests')) + import fixtures + + +@click.command() +@click.argument('test_file', type=click.Path(), nargs=1) +@click.argument('test_name', nargs=1) +def main(test_file, test_name): + test_base_dir = Path(test_file).parent + try: + with open(test_file) as f: + raw = yaml.safe_load(f) + module = raw.pop('module') + except (IOError, OSError, yaml.YAMLError) as e: + raise Exception(f'cannot read test spec {test_file}: {e}') + except KeyError as e: + raise Exception(f'`module` key not found in {test_file}: {e}') + + common = raw.pop('common_tfvars', []) + spec = raw.get('tests', {})[test_name] or {} + extra_dirs = spec.get('extra_dirs', []) + extra_files = spec.get('extra_files', []) + tf_var_files = common + [f'{test_name}.tfvars'] + spec.get('tfvars', []) + module_path = BASEDIR / module + summary = fixtures.plan_summary(module_path, test_base_dir, tf_var_files, + extra_files=extra_files, + extra_dirs=extra_dirs) + + print(yaml.dump({'values': summary.values})) + print(yaml.dump({'counts': summary.counts})) + outputs = { + k: v.get('value', '__missing__') for k, v in summary.outputs.items() + } + print(yaml.dump({'outputs': outputs})) + + +if __name__ == '__main__': + main()