From 8c43b72dd40d73b16b3df0fd8d1dfe617f5e9630 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sat, 26 Nov 2022 11:41:53 +0100 Subject: [PATCH 01/28] Remove stale xmark from parellel testing attempt --- tests/blueprints/conftest.py | 21 --------------------- tests/conftest.py | 2 -- tests/fast/conftest.py | 20 -------------------- tests/modules/conftest.py | 5 ----- 4 files changed, 48 deletions(-) delete mode 100644 tests/blueprints/conftest.py delete mode 100644 tests/fast/conftest.py diff --git a/tests/blueprints/conftest.py b/tests/blueprints/conftest.py deleted file mode 100644 index ed29d5bb6..000000000 --- a/tests/blueprints/conftest.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2022 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 pytest - - -def pytest_collection_modifyitems(config, items): - for item in items: - item.add_marker( - pytest.mark.xdist_group(name='/'.join(item.path.parent.parts[-2:]))) diff --git a/tests/conftest.py b/tests/conftest.py index d676bbc00..50eccb0b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -98,8 +98,6 @@ def recursive_e2e_plan_runner(_plan_runner): (nested) modules and resources""" def walk_plan(node, modules, resources): - # TODO(jccb): this would be better with node.get() but - # TerraformPlanOutput objects don't have it new_modules = node.get('child_modules', []) resources += node.get('resources', []) modules += new_modules diff --git a/tests/fast/conftest.py b/tests/fast/conftest.py deleted file mode 100644 index 3d559e6f0..000000000 --- a/tests/fast/conftest.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2022 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 pytest - - -def pytest_collection_modifyitems(config, items): - for item in items: - item.add_marker(pytest.mark.xdist_group(name=item.path.parent.name)) diff --git a/tests/modules/conftest.py b/tests/modules/conftest.py index a34cef656..41f7cd2e5 100644 --- a/tests/modules/conftest.py +++ b/tests/modules/conftest.py @@ -20,11 +20,6 @@ import pytest import yaml -def pytest_collection_modifyitems(config, items): - for item in items: - item.add_marker(pytest.mark.xdist_group(name=item.path.parent.name)) - - @pytest.fixture(scope='session') def tfvars_to_yaml(): From cc1b9fb00bf88902187fe79c03cf03388a8dae65 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Wed, 30 Nov 2022 20:15:46 +0100 Subject: [PATCH 02/28] New fixtures --- tests/conftest.py | 118 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 50eccb0b7..8a4f9dd40 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,13 +13,18 @@ # limitations under the License. "Shared fixtures" +import collections import inspect import os import shutil import tempfile +from pathlib import Path import pytest import tftest +import yaml + +PlanSummary = collections.namedtuple('PlanSummary', 'values counts outputs') BASEDIR = os.path.dirname(os.path.dirname(__file__)) @@ -74,10 +79,10 @@ def plan_runner(_plan_runner): def e2e_plan_runner(_plan_runner): "Returns a function to run Terraform plan on an end-to-end fixture." - def run_plan(fixture_path=None, tf_var_file=None, targets=None, - refresh=True, include_bare_resources=False, **tf_vars): + def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True, + include_bare_resources=False, **tf_vars): "Runs Terraform plan on an end-to-end module using defaults, returns data." - plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, + plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, refresh=refresh, **tf_vars) # skip the fixture root_module = plan.root_module['child_modules'][0] @@ -108,7 +113,7 @@ def recursive_e2e_plan_runner(_plan_runner): include_bare_resources=False, compute_sums=True, tmpdir=True, **tf_vars): "Runs Terraform plan on a root module using defaults, returns data." - plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, + plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, refresh=refresh, tmpdir=tmpdir, **tf_vars) modules = [] resources = [] @@ -150,3 +155,108 @@ def apply_runner(): @pytest.fixture def basedir(): return BASEDIR + + +@pytest.fixture +def generic_plan_summary(): + + def inner(module_path, tf_var_files=None, basedir=None, **tf_vars): + # TODO: + # - trigger copy from env var + basedir = basedir or BASEDIR + + # prepare tftest + tf = tftest.TerraformTest(module_path, basedir=basedir, + binary=os.environ.get('TERRAFORM', 'terraform')) + tf.setup(upgrade=True) + plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, + tf_vars=tf_vars) + + # compute resource type counts and address->values map + values = {} + counts = collections.defaultdict(int) + q = collections.deque([plan.root_module]) + while q: + e = q.popleft() + + if 'type' in e: + counts[e['type']] += 1 + if 'values' in e: + values[e['address']] = e['values'] + + for x in e.get('resources', []): + q.append(x) + for x in e.get('child_modules', []): + q.append(x) + + # extract planned outputs + outputs = plan.get('planned_values', {}).get('outputs', {}) + + return PlanSummary(values, dict(counts), outputs) + + return inner + + +@pytest.fixture +def generic_plan_validator(generic_plan_summary): + + def inner(inventory_path, module_path, tf_var_files=None, basedir=None, + **tf_vars): + + # allow tfvars and inventory to be relative to the caller + caller_path = Path(inspect.stack()[1].filename).parent + tf_var_files = [caller_path / x for x in tf_var_files] + inventory_path = caller_path / inventory_path + inventory = yaml.safe_load(inventory_path.read_text()) + assert inventory is not None, f'Inventory {inventory_path} is empty' + + summary = generic_plan_summary(module_path, tf_var_files=tf_var_files, + basedir=basedir) + + # If you add additional asserts to this function: + # - put the values coming from the plan on the left side of + # any comparison operators + # - put the values coming from user's inventory the right + # side of any comparison operators. + # - include a descriptive error message to the assert + + # for values: + # - verify each address in the user's inventory exists in the plan + # - for those address that exist on both the user's inventory and + # the plan output, ensure the set of keys on the inventory are a + # subset of the keys in the plan, and compare their values by + # equality + if 'values' in inventory: + expected_values = inventory['values'] + for address, expected_value in expected_values.items(): + assert address in summary.values, \ + f'{address} is not a valid address in the plan' + for k, v in expected_value.items(): + assert k in summary.values[address], \ + f'{k} not found at {address}' + plan_value = summary.values[address][k] + assert plan_value == v, \ + f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' + + if 'counts' in inventory: + expected_counts = inventory['counts'] + for type_, expected_count in expected_counts.items(): + assert type_ in summary.counts + plan_count = summary.counts[type_] + assert plan_count == expected_count + + if 'outputs' in inventory: + expected_outputs = inventory['outputs'] + for output_name, expected_output in expected_outputs.items(): + assert output_name in summary.outputs, \ + f'module does not output `{output_name}`' + output = summary.outputs[output_name] + # assert 'value' in output, \ + # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' + plan_output = output.get('value', '__missing__') + assert plan_output == expected_output, \ + f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' + + return summary + + return inner From dc1fda0fd8acc84b9ee999b121c45df131544138 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Wed, 30 Nov 2022 20:33:18 +0100 Subject: [PATCH 03/28] First tests using fast --- .../fast/stages/s00_bootstrap/fixture/main.tf | 29 - tests/fast/stages/s00_bootstrap/simple.tfvars | 10 + tests/fast/stages/s00_bootstrap/simple.yaml | 707 ++++++++++++++++++ tests/fast/stages/s00_bootstrap/test_plan.py | 22 +- 4 files changed, 721 insertions(+), 47 deletions(-) delete mode 100644 tests/fast/stages/s00_bootstrap/fixture/main.tf create mode 100644 tests/fast/stages/s00_bootstrap/simple.tfvars create mode 100644 tests/fast/stages/s00_bootstrap/simple.yaml diff --git a/tests/fast/stages/s00_bootstrap/fixture/main.tf b/tests/fast/stages/s00_bootstrap/fixture/main.tf deleted file mode 100644 index 1f07048ad..000000000 --- a/tests/fast/stages/s00_bootstrap/fixture/main.tf +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2022 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 "stage" { - source = "../../../../../fast/stages/00-bootstrap" - prefix = "fast" - organization = { - domain = "fast.example.com" - id = 123456789012 - customer_id = "C00000000" - } - billing_account = { - id = "000000-111111-222222" - organization_id = 123456789012 - } -} diff --git a/tests/fast/stages/s00_bootstrap/simple.tfvars b/tests/fast/stages/s00_bootstrap/simple.tfvars new file mode 100644 index 000000000..c978998dd --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/simple.tfvars @@ -0,0 +1,10 @@ +organization = { + domain = "fast.example.com" + id = 123456789012 + customer_id = "C00000000" +} +billing_account = { + id = "000000-111111-222222" + organization_id = 123456789012 +} +prefix = "fast" diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml new file mode 100644 index 000000000..4ed7bcbfe --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/simple.yaml @@ -0,0 +1,707 @@ +# TODO: missing all local_file and gcs objects +values: + google_organization_iam_binding.org_admin_delegated: + condition: + - description: Automation service account delegated grants. + expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/billing.admin','roles/billing.costsManager','roles/billing.user']) + title: automation_sa_delegated_grants + members: + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: organizations/123456789012/roles/organizationIamAdmin12 + module.automation-project.data.google_bigquery_default_service_account.bq_sa[0]: + project: fast-prod-iac-core-0 + module.automation-project.data.google_storage_project_service_account.gcs_sa[0]: + project: fast-prod-iac-core-0 + user_project: null + module.automation-project.google_project.project[0]: + auto_create_network: false + billing_account: 000000-111111-222222 + folder_id: null + labels: null + name: fast-prod-iac-core-0 + org_id: '123456789012' + project_id: fast-prod-iac-core-0 + skip_delete: false + module.automation-project.google_project_iam_binding.authoritative["roles/cloudbuild.builds.editor"]: + condition: [] + members: + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + project: fast-prod-iac-core-0 + role: roles/cloudbuild.builds.editor + module.automation-project.google_project_iam_binding.authoritative["roles/iam.serviceAccountAdmin"]: + condition: [] + members: + - group:gcp-devops@fast.example.com + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + project: fast-prod-iac-core-0 + role: roles/iam.serviceAccountAdmin + module.automation-project.google_project_iam_binding.authoritative["roles/iam.serviceAccountTokenCreator"]: + condition: [] + members: + - group:gcp-devops@fast.example.com + - group:gcp-organization-admins@fast.example.com + project: fast-prod-iac-core-0 + role: roles/iam.serviceAccountTokenCreator + module.automation-project.google_project_iam_binding.authoritative["roles/iam.workloadIdentityPoolAdmin"]: + condition: [] + members: + - group:gcp-organization-admins@fast.example.com + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + project: fast-prod-iac-core-0 + role: roles/iam.workloadIdentityPoolAdmin + module.automation-project.google_project_iam_binding.authoritative["roles/owner"]: + condition: [] + members: + - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + project: fast-prod-iac-core-0 + role: roles/owner + module.automation-project.google_project_iam_binding.authoritative["roles/source.admin"]: + condition: [] + members: + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + project: fast-prod-iac-core-0 + role: roles/source.admin + module.automation-project.google_project_iam_binding.authoritative["roles/storage.admin"]: + condition: [] + members: + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + project: fast-prod-iac-core-0 + role: roles/storage.admin + module.automation-project.google_project_iam_member.servicenetworking[0]: + condition: [] + project: fast-prod-iac-core-0 + role: roles/servicenetworking.serviceAgent + module.automation-project.google_project_service.project_services["accesscontextmanager.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: accesscontextmanager.googleapis.com + module.automation-project.google_project_service.project_services["bigquery.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: bigquery.googleapis.com + module.automation-project.google_project_service.project_services["bigqueryreservation.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: bigqueryreservation.googleapis.com + module.automation-project.google_project_service.project_services["bigquerystorage.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: bigquerystorage.googleapis.com + module.automation-project.google_project_service.project_services["billingbudgets.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: billingbudgets.googleapis.com + module.automation-project.google_project_service.project_services["cloudbilling.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: cloudbilling.googleapis.com + module.automation-project.google_project_service.project_services["cloudbuild.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: cloudbuild.googleapis.com + module.automation-project.google_project_service.project_services["cloudkms.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: cloudkms.googleapis.com + module.automation-project.google_project_service.project_services["cloudresourcemanager.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: cloudresourcemanager.googleapis.com + module.automation-project.google_project_service.project_services["compute.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: compute.googleapis.com + module.automation-project.google_project_service.project_services["container.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: container.googleapis.com + module.automation-project.google_project_service.project_services["essentialcontacts.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: essentialcontacts.googleapis.com + module.automation-project.google_project_service.project_services["iam.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: iam.googleapis.com + module.automation-project.google_project_service.project_services["iamcredentials.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: iamcredentials.googleapis.com + module.automation-project.google_project_service.project_services["orgpolicy.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: orgpolicy.googleapis.com + module.automation-project.google_project_service.project_services["pubsub.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: pubsub.googleapis.com + module.automation-project.google_project_service.project_services["servicenetworking.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: servicenetworking.googleapis.com + module.automation-project.google_project_service.project_services["serviceusage.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: serviceusage.googleapis.com + module.automation-project.google_project_service.project_services["sourcerepo.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: sourcerepo.googleapis.com + module.automation-project.google_project_service.project_services["stackdriver.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: stackdriver.googleapis.com + module.automation-project.google_project_service.project_services["storage-component.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: storage-component.googleapis.com + module.automation-project.google_project_service.project_services["storage.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: storage.googleapis.com + module.automation-project.google_project_service.project_services["sts.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-iac-core-0 + service: sts.googleapis.com + module.automation-project.google_project_service_identity.jit_si["pubsub.googleapis.com"]: + project: fast-prod-iac-core-0 + service: pubsub.googleapis.com + module.automation-project.google_project_service_identity.servicenetworking[0]: + project: fast-prod-iac-core-0 + service: servicenetworking.googleapis.com + module.automation-tf-bootstrap-gcs.google_storage_bucket.bucket: + cors: [] + custom_placement_config: [] + default_event_based_hold: null + encryption: [] + force_destroy: false + labels: null + lifecycle_rule: [] + location: EU + logging: [] + name: fast-prod-iac-core-bootstrap-0 + project: fast-prod-iac-core-0 + requester_pays: null + retention_policy: [] + storage_class: MULTI_REGIONAL + uniform_bucket_level_access: true + versioning: + - enabled: true + website: [] + module.automation-tf-bootstrap-sa.google_service_account.service_account[0]: + account_id: fast-prod-bootstrap-0 + description: null + disabled: false + display_name: Terraform organization bootstrap service account. + project: fast-prod-iac-core-0 + module.automation-tf-bootstrap-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: + condition: [] + members: null + role: roles/iam.serviceAccountTokenCreator + module.automation-tf-bootstrap-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: + bucket: fast-prod-iac-core-outputs-0 + condition: [] + role: roles/storage.admin + module.automation-tf-cicd-gcs.google_storage_bucket.bucket: + cors: [] + custom_placement_config: [] + default_event_based_hold: null + encryption: [] + force_destroy: false + labels: null + lifecycle_rule: [] + location: EU + logging: [] + name: fast-prod-iac-core-cicd-0 + project: fast-prod-iac-core-0 + requester_pays: null + retention_policy: [] + storage_class: MULTI_REGIONAL + uniform_bucket_level_access: true + versioning: + - enabled: true + website: [] + module.automation-tf-cicd-gcs.google_storage_bucket_iam_binding.bindings["roles/storage.objectAdmin"]: + bucket: fast-prod-iac-core-cicd-0 + condition: [] + members: + - serviceAccount:fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com + role: roles/storage.objectAdmin + module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]: + account_id: fast-prod-cicd-0 + description: null + disabled: false + display_name: Terraform stage 1 CICD service account. + project: fast-prod-iac-core-0 + module.automation-tf-cicd-provisioning-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: + condition: [] + members: null + role: roles/iam.serviceAccountTokenCreator + module.automation-tf-cicd-provisioning-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: + bucket: fast-prod-iac-core-outputs-0 + condition: [] + role: roles/storage.admin + module.automation-tf-output-gcs.google_storage_bucket.bucket: + cors: [] + custom_placement_config: [] + default_event_based_hold: null + encryption: [] + force_destroy: false + labels: null + lifecycle_rule: [] + location: EU + logging: [] + name: fast-prod-iac-core-outputs-0 + project: fast-prod-iac-core-0 + requester_pays: null + retention_policy: [] + storage_class: MULTI_REGIONAL + uniform_bucket_level_access: true + versioning: + - enabled: true + website: [] + module.automation-tf-resman-gcs.google_storage_bucket.bucket: + cors: [] + custom_placement_config: [] + default_event_based_hold: null + encryption: [] + force_destroy: false + labels: null + lifecycle_rule: [] + location: EU + logging: [] + name: fast-prod-iac-core-resman-0 + project: fast-prod-iac-core-0 + requester_pays: null + retention_policy: [] + storage_class: MULTI_REGIONAL + uniform_bucket_level_access: true + versioning: + - enabled: true + website: [] + module.automation-tf-resman-gcs.google_storage_bucket_iam_binding.bindings["roles/storage.objectAdmin"]: + bucket: fast-prod-iac-core-resman-0 + condition: [] + members: + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + role: roles/storage.objectAdmin + module.automation-tf-resman-sa.google_service_account.service_account[0]: + account_id: fast-prod-resman-0 + description: null + disabled: false + display_name: Terraform stage 1 resman service account. + project: fast-prod-iac-core-0 + module.automation-tf-resman-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: + condition: [] + members: null + role: roles/iam.serviceAccountTokenCreator + module.automation-tf-resman-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: + bucket: fast-prod-iac-core-outputs-0 + condition: [] + role: roles/storage.admin + module.billing-export-dataset[0].google_bigquery_dataset.default: + dataset_id: billing_export + default_encryption_configuration: [] + default_partition_expiration_ms: null + default_table_expiration_ms: null + delete_contents_on_destroy: false + description: Terraform managed. + friendly_name: Billing export. + labels: null + location: EU + max_time_travel_hours: null + project: fast-prod-billing-exp-0 + module.billing-export-project[0].data.google_bigquery_default_service_account.bq_sa[0]: + project: fast-prod-billing-exp-0 + module.billing-export-project[0].data.google_storage_project_service_account.gcs_sa[0]: + project: fast-prod-billing-exp-0 + user_project: null + module.billing-export-project[0].google_project.project[0]: + auto_create_network: false + billing_account: 000000-111111-222222 + folder_id: null + labels: null + name: fast-prod-billing-exp-0 + org_id: '123456789012' + project_id: fast-prod-billing-exp-0 + skip_delete: false + module.billing-export-project[0].google_project_iam_binding.authoritative["roles/owner"]: + condition: [] + members: + - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + project: fast-prod-billing-exp-0 + role: roles/owner + module.billing-export-project[0].google_project_service.project_services["bigquery.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-billing-exp-0 + service: bigquery.googleapis.com + module.billing-export-project[0].google_project_service.project_services["bigquerydatatransfer.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-billing-exp-0 + service: bigquerydatatransfer.googleapis.com + module.billing-export-project[0].google_project_service.project_services["storage.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-billing-exp-0 + service: storage.googleapis.com + module.log-export-dataset[0].google_bigquery_dataset.default: + dataset_id: audit_export + default_encryption_configuration: [] + default_partition_expiration_ms: null + default_table_expiration_ms: null + delete_contents_on_destroy: false + description: Terraform managed. + friendly_name: Audit logs export. + labels: null + location: EU + max_time_travel_hours: null + project: fast-prod-audit-logs-0 + module.log-export-project.data.google_bigquery_default_service_account.bq_sa[0]: + project: fast-prod-audit-logs-0 + module.log-export-project.data.google_storage_project_service_account.gcs_sa[0]: + project: fast-prod-audit-logs-0 + user_project: null + module.log-export-project.google_project.project[0]: + auto_create_network: false + billing_account: 000000-111111-222222 + folder_id: null + labels: null + name: fast-prod-audit-logs-0 + org_id: '123456789012' + project_id: fast-prod-audit-logs-0 + skip_delete: false + module.log-export-project.google_project_iam_binding.authoritative["roles/owner"]: + condition: [] + members: + - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + project: fast-prod-audit-logs-0 + role: roles/owner + module.log-export-project.google_project_service.project_services["bigquery.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-audit-logs-0 + service: bigquery.googleapis.com + module.log-export-project.google_project_service.project_services["stackdriver.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-audit-logs-0 + service: stackdriver.googleapis.com + module.log-export-project.google_project_service.project_services["storage.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: fast-prod-audit-logs-0 + service: storage.googleapis.com + module.organization.google_bigquery_dataset_iam_member.bq-sinks-binding["audit-logs"]: + condition: [] + role: roles/bigquery.dataEditor + module.organization.google_bigquery_dataset_iam_member.bq-sinks-binding["vpc-sc"]: + condition: [] + role: roles/bigquery.dataEditor + module.organization.google_logging_organization_sink.sink["audit-logs"]: + description: audit-logs (Terraform-managed). + disabled: false + exclusions: [] + filter: logName:"/logs/cloudaudit.googleapis.com%2Factivity" OR logName:"/logs/cloudaudit.googleapis.com%2Fsystem_event" + include_children: true + name: audit-logs + org_id: '123456789012' + module.organization.google_logging_organization_sink.sink["vpc-sc"]: + description: vpc-sc (Terraform-managed). + disabled: false + exclusions: [] + filter: protoPayload.metadata.@type="type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata" + include_children: true + name: vpc-sc + org_id: '123456789012' + module.organization.google_organization_iam_binding.authoritative["roles/browser"]: + condition: [] + members: + - domain:fast.example.com + org_id: '123456789012' + role: roles/browser + module.organization.google_organization_iam_binding.authoritative["roles/cloudasset.owner"]: + condition: [] + members: + - group:gcp-network-admins@fast.example.com + - group:gcp-organization-admins@fast.example.com + - group:gcp-security-admins@fast.example.com + org_id: '123456789012' + role: roles/cloudasset.owner + module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.admin"]: + condition: [] + members: + - group:gcp-organization-admins@fast.example.com + org_id: '123456789012' + role: roles/cloudsupport.admin + module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.techSupportEditor"]: + condition: [] + members: + - group:gcp-devops@fast.example.com + - group:gcp-network-admins@fast.example.com + - group:gcp-security-admins@fast.example.com + org_id: '123456789012' + role: roles/cloudsupport.techSupportEditor + module.organization.google_organization_iam_binding.authoritative["roles/compute.osAdminLogin"]: + condition: [] + members: + - group:gcp-organization-admins@fast.example.com + org_id: '123456789012' + role: roles/compute.osAdminLogin + module.organization.google_organization_iam_binding.authoritative["roles/compute.osLoginExternalUser"]: + condition: [] + members: + - group:gcp-organization-admins@fast.example.com + org_id: '123456789012' + role: roles/compute.osLoginExternalUser + module.organization.google_organization_iam_binding.authoritative["roles/iam.securityReviewer"]: + condition: [] + members: + - group:gcp-security-admins@fast.example.com + org_id: '123456789012' + role: roles/iam.securityReviewer + module.organization.google_organization_iam_binding.authoritative["roles/logging.admin"]: + condition: [] + members: + - group:gcp-security-admins@fast.example.com + - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/logging.admin + module.organization.google_organization_iam_binding.authoritative["roles/logging.viewer"]: + condition: [] + members: + - group:gcp-devops@fast.example.com + org_id: '123456789012' + role: roles/logging.viewer + module.organization.google_organization_iam_binding.authoritative["roles/monitoring.viewer"]: + condition: [] + members: + - group:gcp-devops@fast.example.com + org_id: '123456789012' + role: roles/monitoring.viewer + module.organization.google_organization_iam_binding.authoritative["roles/owner"]: + condition: [] + members: + - group:gcp-organization-admins@fast.example.com + org_id: '123456789012' + role: roles/owner + module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.folderAdmin"]: + condition: [] + members: + - group:gcp-organization-admins@fast.example.com + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/resourcemanager.folderAdmin + module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.organizationAdmin"]: + condition: [] + members: + - group:gcp-organization-admins@fast.example.com + - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/resourcemanager.organizationAdmin + module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.projectCreator"]: + condition: [] + members: + - group:gcp-organization-admins@fast.example.com + - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/resourcemanager.projectCreator + module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.projectMover"]: + condition: [] + members: + - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/resourcemanager.projectMover + module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.tagAdmin"]: + condition: [] + members: + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/resourcemanager.tagAdmin + module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.tagUser"]: + condition: [] + members: + - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/resourcemanager.tagUser + module.organization.google_organization_iam_binding.authoritative["roles/securitycenter.admin"]: + condition: [] + members: + - group:gcp-security-admins@fast.example.com + org_id: '123456789012' + role: roles/securitycenter.admin + module.organization.google_organization_iam_custom_role.roles["organizationIamAdmin12"]: + description: Terraform-managed. + org_id: '123456789012' + permissions: + - resourcemanager.organizations.get + - resourcemanager.organizations.getIamPolicy + - resourcemanager.organizations.setIamPolicy + role_id: organizationIamAdmin12 + stage: GA + title: Custom role organizationIamAdmin12 + module.organization.google_organization_iam_custom_role.roles["serviceProjectNetworkAdmin12"]: + description: Terraform-managed. + org_id: '123456789012' + permissions: + - compute.globalOperations.get + - compute.networks.get + - compute.networks.updatePeering + - compute.organizations.disableXpnResource + - compute.organizations.enableXpnResource + - compute.projects.get + - compute.subnetworks.getIamPolicy + - compute.subnetworks.setIamPolicy + - dns.networks.bindPrivateDNSZone + - resourcemanager.projects.get + role_id: serviceProjectNetworkAdmin12 + stage: GA + title: Custom role serviceProjectNetworkAdmin12 + module.organization.google_organization_iam_member.additive["roles/accesscontextmanager.policyAdmin-group:gcp-security-admins@fast.example.com"]: + condition: [] + member: group:gcp-security-admins@fast.example.com + org_id: '123456789012' + role: roles/accesscontextmanager.policyAdmin + module.organization.google_organization_iam_member.additive["roles/billing.admin-group:gcp-billing-admins@fast.example.com"]: + condition: [] + member: group:gcp-billing-admins@fast.example.com + org_id: '123456789012' + role: roles/billing.admin + module.organization.google_organization_iam_member.additive["roles/billing.admin-group:gcp-organization-admins@fast.example.com"]: + condition: [] + member: group:gcp-organization-admins@fast.example.com + org_id: '123456789012' + role: roles/billing.admin + module.organization.google_organization_iam_member.additive["roles/billing.admin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: + condition: [] + member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/billing.admin + module.organization.google_organization_iam_member.additive["roles/billing.admin-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: + condition: [] + member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/billing.admin + module.organization.google_organization_iam_member.additive["roles/billing.costsManager-group:gcp-billing-admins@fast.example.com"]: + condition: [] + member: group:gcp-billing-admins@fast.example.com + org_id: '123456789012' + role: roles/billing.costsManager + module.organization.google_organization_iam_member.additive["roles/billing.costsManager-group:gcp-organization-admins@fast.example.com"]: + condition: [] + member: group:gcp-organization-admins@fast.example.com + org_id: '123456789012' + role: roles/billing.costsManager + module.organization.google_organization_iam_member.additive["roles/billing.costsManager-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: + condition: [] + member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/billing.costsManager + module.organization.google_organization_iam_member.additive["roles/billing.costsManager-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: + condition: [] + member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/billing.costsManager + module.organization.google_organization_iam_member.additive["roles/compute.orgFirewallPolicyAdmin-group:gcp-network-admins@fast.example.com"]: + condition: [] + member: group:gcp-network-admins@fast.example.com + org_id: '123456789012' + role: roles/compute.orgFirewallPolicyAdmin + module.organization.google_organization_iam_member.additive["roles/compute.xpnAdmin-group:gcp-network-admins@fast.example.com"]: + condition: [] + member: group:gcp-network-admins@fast.example.com + org_id: '123456789012' + role: roles/compute.xpnAdmin + module.organization.google_organization_iam_member.additive["roles/iam.organizationRoleAdmin-group:gcp-security-admins@fast.example.com"]: + condition: [] + member: group:gcp-security-admins@fast.example.com + org_id: '123456789012' + role: roles/iam.organizationRoleAdmin + module.organization.google_organization_iam_member.additive["roles/iam.organizationRoleAdmin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: + condition: [] + member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/iam.organizationRoleAdmin + module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-group:gcp-organization-admins@fast.example.com"]: + condition: [] + member: group:gcp-organization-admins@fast.example.com + org_id: '123456789012' + role: roles/orgpolicy.policyAdmin + module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-group:gcp-security-admins@fast.example.com"]: + condition: [] + member: group:gcp-security-admins@fast.example.com + org_id: '123456789012' + role: roles/orgpolicy.policyAdmin + module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: + condition: [] + member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + org_id: '123456789012' + role: roles/orgpolicy.policyAdmin + +counts: + google_bigquery_dataset: 2 + google_bigquery_dataset_iam_member: 2 + google_bigquery_default_service_account: 3 + google_logging_organization_sink: 2 + google_organization_iam_binding: 19 + google_organization_iam_custom_role: 2 + google_organization_iam_member: 16 + google_project: 3 + google_project_iam_binding: 9 + google_project_iam_member: 1 + google_project_service: 29 + google_project_service_identity: 2 + google_service_account: 3 + google_service_account_iam_binding: 3 + google_storage_bucket: 4 + google_storage_bucket_iam_binding: 2 + google_storage_bucket_iam_member: 3 + google_storage_bucket_object: 5 + google_storage_project_service_account: 3 + local_file: 5 + +outputs: + automation: __missing__ + billing_dataset: __missing__ + cicd_repositories: {} + custom_roles: + organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin12 + service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin12 + federated_identity: + pool: null + providers: {} + outputs_bucket: fast-prod-iac-core-outputs-0 + project_ids: + automation: fast-prod-iac-core-0 + billing-export: fast-prod-billing-exp-0 + log-export: fast-prod-audit-logs-0 + service_accounts: + bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com + resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com + tfvars: __missing__ diff --git a/tests/fast/stages/s00_bootstrap/test_plan.py b/tests/fast/stages/s00_bootstrap/test_plan.py index 781469703..7cf59bae5 100644 --- a/tests/fast/stages/s00_bootstrap/test_plan.py +++ b/tests/fast/stages/s00_bootstrap/test_plan.py @@ -12,22 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# _RESOURCE_COUNT = { -# 'module.organization': 28, -# 'module.automation-project': 23, -# 'module.automation-tf-bootstrap-gcs': 1, -# 'module.automation-tf-bootstrap-sa': 1, -# 'module.automation-tf-resman-gcs': 2, -# 'module.automation-tf-resman-sa': 1, -# 'module.billing-export-dataset': 1, -# 'module.billing-export-project': 7, -# 'module.log-export-dataset': 1, -# 'module.log-export-project': 7, -# } - -def test_counts(recursive_e2e_plan_runner): - "Test stage." - # TODO: to re-enable per-module resource count check print _, then test - num_modules, num_resources = recursive_e2e_plan_runner() - assert num_modules > 0 and num_resources > 0 +def test_simple(generic_plan_validator): + s = generic_plan_validator(inventory_path='simple.yaml', + module_path="fast/stages/00-bootstrap", + tf_var_files=['simple.tfvars']) From 61d5758f06cc817425ba5cc32e8356e24018837b Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Wed, 30 Nov 2022 20:34:47 +0100 Subject: [PATCH 04/28] New test example for a module --- tests/modules/net_vpc/common.tfvars | 2 + tests/modules/net_vpc/peering.tfvars | 5 + tests/modules/net_vpc/peering.yaml | 33 ++++++ .../modules/net_vpc/psa_routes_export.tfvars | 7 ++ tests/modules/net_vpc/psa_routes_export.yaml | 46 ++++++++ .../modules/net_vpc/psa_routes_import.tfvars | 7 ++ tests/modules/net_vpc/psa_routes_import.yaml | 46 ++++++++ .../net_vpc/psa_routes_import_export.tfvars | 7 ++ .../net_vpc/psa_routes_import_export.yaml | 46 ++++++++ tests/modules/net_vpc/psa_simple.tfvars | 7 ++ tests/modules/net_vpc/psa_simple.yaml | 56 +++++++++ tests/modules/net_vpc/shared_vpc.tfvars | 2 + tests/modules/net_vpc/shared_vpc.yaml | 32 ++++++ tests/modules/net_vpc/simple.yaml | 22 ++++ tests/modules/net_vpc/subnets.tfvars | 44 ++++++++ tests/modules/net_vpc/subnets.yaml | 106 ++++++++++++++++++ tests/modules/net_vpc/test_plan.py | 50 +++------ tests/modules/net_vpc/test_plan_psa.py | 79 ++++--------- tests/modules/net_vpc/test_plan_subnets.py | 48 +------- 19 files changed, 509 insertions(+), 136 deletions(-) create mode 100644 tests/modules/net_vpc/common.tfvars create mode 100644 tests/modules/net_vpc/peering.tfvars create mode 100644 tests/modules/net_vpc/peering.yaml create mode 100644 tests/modules/net_vpc/psa_routes_export.tfvars create mode 100644 tests/modules/net_vpc/psa_routes_export.yaml create mode 100644 tests/modules/net_vpc/psa_routes_import.tfvars create mode 100644 tests/modules/net_vpc/psa_routes_import.yaml create mode 100644 tests/modules/net_vpc/psa_routes_import_export.tfvars create mode 100644 tests/modules/net_vpc/psa_routes_import_export.yaml create mode 100644 tests/modules/net_vpc/psa_simple.tfvars create mode 100644 tests/modules/net_vpc/psa_simple.yaml create mode 100644 tests/modules/net_vpc/shared_vpc.tfvars create mode 100644 tests/modules/net_vpc/shared_vpc.yaml create mode 100644 tests/modules/net_vpc/simple.yaml create mode 100644 tests/modules/net_vpc/subnets.tfvars create mode 100644 tests/modules/net_vpc/subnets.yaml diff --git a/tests/modules/net_vpc/common.tfvars b/tests/modules/net_vpc/common.tfvars new file mode 100644 index 000000000..8d9d8a957 --- /dev/null +++ b/tests/modules/net_vpc/common.tfvars @@ -0,0 +1,2 @@ +project_id = "test-project" +name = "test" diff --git a/tests/modules/net_vpc/peering.tfvars b/tests/modules/net_vpc/peering.tfvars new file mode 100644 index 000000000..eccd7ae71 --- /dev/null +++ b/tests/modules/net_vpc/peering.tfvars @@ -0,0 +1,5 @@ +peering_config = { + peer_vpc_self_link = "projects/my-project/global/networks/peer" + export_routes = true + import_routes = null +} diff --git a/tests/modules/net_vpc/peering.yaml b/tests/modules/net_vpc/peering.yaml new file mode 100644 index 000000000..659eccff3 --- /dev/null +++ b/tests/modules/net_vpc/peering.yaml @@ -0,0 +1,33 @@ +values: + google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + name: test + project: test-project + routing_mode: GLOBAL + google_compute_network_peering.local[0]: + export_custom_routes: true + import_custom_routes: false + name: test-peer + peer_network: projects/my-project/global/networks/peer + google_compute_network_peering.remote[0]: + export_custom_routes: false + import_custom_routes: true + name: peer-test + network: projects/my-project/global/networks/peer + +counts: + google_compute_network: 1 + google_compute_network_peering: 2 + +outputs: + bindings: {} + project_id: test-project + subnet_ips: {} + subnet_regions: {} + subnet_secondary_ranges: {} + subnet_self_links: {} + subnets: {} + subnets_proxy_only: {} + subnets_psc: {} diff --git a/tests/modules/net_vpc/psa_routes_export.tfvars b/tests/modules/net_vpc/psa_routes_export.tfvars new file mode 100644 index 000000000..9fbe4ddf7 --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_export.tfvars @@ -0,0 +1,7 @@ +psa_config = { + ranges = { + bar = "172.16.100.0/24" + } + export_routes = true + import_routes = false +} diff --git a/tests/modules/net_vpc/psa_routes_export.yaml b/tests/modules/net_vpc/psa_routes_export.yaml new file mode 100644 index 000000000..d49d31380 --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_export.yaml @@ -0,0 +1,46 @@ +values: + google_compute_global_address.psa_ranges["bar"]: + address: 172.16.100.0 + address_type: INTERNAL + description: null + ip_version: null + name: bar + prefix_length: 24 + project: test-project + purpose: VPC_PEERING + google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: test + project: test-project + routing_mode: GLOBAL + google_compute_network_peering_routes_config.psa_routes["1"]: + export_custom_routes: true + import_custom_routes: false + project: test-project + google_service_networking_connection.psa_connection["1"]: + reserved_peering_ranges: + - bar + service: servicenetworking.googleapis.com + +counts: + google_compute_global_address: 1 + google_compute_network: 1 + google_compute_network_peering_routes_config: 1 + google_service_networking_connection: 1 + +outputs: + bindings: {} + name: __missing__ + network: __missing__ + project_id: test-project + self_link: __missing__ + subnet_ips: {} + subnet_regions: {} + subnet_secondary_ranges: {} + subnet_self_links: {} + subnets: {} + subnets_proxy_only: {} + subnets_psc: {} diff --git a/tests/modules/net_vpc/psa_routes_import.tfvars b/tests/modules/net_vpc/psa_routes_import.tfvars new file mode 100644 index 000000000..beeaf433a --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_import.tfvars @@ -0,0 +1,7 @@ +psa_config = { + ranges = { + bar = "172.16.100.0/24" + } + export_routes = false + import_routes = true +} diff --git a/tests/modules/net_vpc/psa_routes_import.yaml b/tests/modules/net_vpc/psa_routes_import.yaml new file mode 100644 index 000000000..06255280f --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_import.yaml @@ -0,0 +1,46 @@ +values: + google_compute_global_address.psa_ranges["bar"]: + address: 172.16.100.0 + address_type: INTERNAL + description: null + ip_version: null + name: bar + prefix_length: 24 + project: test-project + purpose: VPC_PEERING + google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: test + project: test-project + routing_mode: GLOBAL + google_compute_network_peering_routes_config.psa_routes["1"]: + export_custom_routes: false + import_custom_routes: true + project: test-project + google_service_networking_connection.psa_connection["1"]: + reserved_peering_ranges: + - bar + service: servicenetworking.googleapis.com + +counts: + google_compute_global_address: 1 + google_compute_network: 1 + google_compute_network_peering_routes_config: 1 + google_service_networking_connection: 1 + +outputs: + bindings: {} + name: __missing__ + network: __missing__ + project_id: test-project + self_link: __missing__ + subnet_ips: {} + subnet_regions: {} + subnet_secondary_ranges: {} + subnet_self_links: {} + subnets: {} + subnets_proxy_only: {} + subnets_psc: {} diff --git a/tests/modules/net_vpc/psa_routes_import_export.tfvars b/tests/modules/net_vpc/psa_routes_import_export.tfvars new file mode 100644 index 000000000..205922311 --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_import_export.tfvars @@ -0,0 +1,7 @@ +psa_config = { + ranges = { + bar = "172.16.100.0/24" + } + export_routes = true + import_routes = true +} diff --git a/tests/modules/net_vpc/psa_routes_import_export.yaml b/tests/modules/net_vpc/psa_routes_import_export.yaml new file mode 100644 index 000000000..87b795f1f --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_import_export.yaml @@ -0,0 +1,46 @@ +values: + google_compute_global_address.psa_ranges["bar"]: + address: 172.16.100.0 + address_type: INTERNAL + description: null + ip_version: null + name: bar + prefix_length: 24 + project: test-project + purpose: VPC_PEERING + google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: test + project: test-project + routing_mode: GLOBAL + google_compute_network_peering_routes_config.psa_routes["1"]: + export_custom_routes: true + import_custom_routes: true + project: test-project + google_service_networking_connection.psa_connection["1"]: + reserved_peering_ranges: + - bar + service: servicenetworking.googleapis.com + +counts: + google_compute_global_address: 1 + google_compute_network: 1 + google_compute_network_peering_routes_config: 1 + google_service_networking_connection: 1 + +outputs: + bindings: {} + name: __missing__ + network: __missing__ + project_id: test-project + self_link: __missing__ + subnet_ips: {} + subnet_regions: {} + subnet_secondary_ranges: {} + subnet_self_links: {} + subnets: {} + subnets_proxy_only: {} + subnets_psc: {} diff --git a/tests/modules/net_vpc/psa_simple.tfvars b/tests/modules/net_vpc/psa_simple.tfvars new file mode 100644 index 000000000..51289fe04 --- /dev/null +++ b/tests/modules/net_vpc/psa_simple.tfvars @@ -0,0 +1,7 @@ +psa_config = { + ranges = { + bar = "172.16.100.0/24" + foo = "172.16.101.0/24" + } + routes = null +} diff --git a/tests/modules/net_vpc/psa_simple.yaml b/tests/modules/net_vpc/psa_simple.yaml new file mode 100644 index 000000000..8b66711cc --- /dev/null +++ b/tests/modules/net_vpc/psa_simple.yaml @@ -0,0 +1,56 @@ +values: + google_compute_global_address.psa_ranges["bar"]: + address: 172.16.100.0 + address_type: INTERNAL + description: null + ip_version: null + name: bar + prefix_length: 24 + project: test-project + purpose: VPC_PEERING + google_compute_global_address.psa_ranges["foo"]: + address: 172.16.101.0 + address_type: INTERNAL + description: null + ip_version: null + name: foo + prefix_length: 24 + project: test-project + purpose: VPC_PEERING + google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: test + project: test-project + routing_mode: GLOBAL + google_compute_network_peering_routes_config.psa_routes["1"]: + export_custom_routes: false + import_custom_routes: false + project: test-project + google_service_networking_connection.psa_connection["1"]: + reserved_peering_ranges: + - bar + - foo + service: servicenetworking.googleapis.com + +counts: + google_compute_global_address: 2 + google_compute_network: 1 + google_compute_network_peering_routes_config: 1 + google_service_networking_connection: 1 + +outputs: + bindings: {} + name: __missing__ + network: __missing__ + project_id: test-project + self_link: __missing__ + subnet_ips: {} + subnet_regions: {} + subnet_secondary_ranges: {} + subnet_self_links: {} + subnets: {} + subnets_proxy_only: {} + subnets_psc: {} diff --git a/tests/modules/net_vpc/shared_vpc.tfvars b/tests/modules/net_vpc/shared_vpc.tfvars new file mode 100644 index 000000000..3171539aa --- /dev/null +++ b/tests/modules/net_vpc/shared_vpc.tfvars @@ -0,0 +1,2 @@ +shared_vpc_host = true +shared_vpc_service_projects = ["tf-a", "tf-b"] diff --git a/tests/modules/net_vpc/shared_vpc.yaml b/tests/modules/net_vpc/shared_vpc.yaml new file mode 100644 index 000000000..78e6d5043 --- /dev/null +++ b/tests/modules/net_vpc/shared_vpc.yaml @@ -0,0 +1,32 @@ +values: + google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + name: test + project: test-project + routing_mode: GLOBAL + google_compute_shared_vpc_host_project.shared_vpc_host[0]: + project: test-project + google_compute_shared_vpc_service_project.service_projects["tf-a"]: + host_project: test-project + service_project: tf-a + google_compute_shared_vpc_service_project.service_projects["tf-b"]: + host_project: test-project + service_project: tf-b + +counts: + google_compute_network: 1 + google_compute_shared_vpc_host_project: 1 + google_compute_shared_vpc_service_project: 2 + +outputs: + bindings: {} + project_id: test-project + subnet_ips: {} + subnet_regions: {} + subnet_secondary_ranges: {} + subnet_self_links: {} + subnets: {} + subnets_proxy_only: {} + subnets_psc: {} diff --git a/tests/modules/net_vpc/simple.yaml b/tests/modules/net_vpc/simple.yaml new file mode 100644 index 000000000..1c2d57eec --- /dev/null +++ b/tests/modules/net_vpc/simple.yaml @@ -0,0 +1,22 @@ +values: + google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + name: test + project: test-project + routing_mode: GLOBAL + +counts: + google_compute_network: 1 + +outputs: + bindings: {} + project_id: test-project + subnet_ips: {} + subnet_regions: {} + subnet_secondary_ranges: {} + subnet_self_links: {} + subnets: {} + subnets_proxy_only: {} + subnets_psc: {} diff --git a/tests/modules/net_vpc/subnets.tfvars b/tests/modules/net_vpc/subnets.tfvars new file mode 100644 index 000000000..499e498f4 --- /dev/null +++ b/tests/modules/net_vpc/subnets.tfvars @@ -0,0 +1,44 @@ +subnet_iam = { + "europe-west1/a" = { + "roles/compute.networkUser" = [ + "user:a@example.com", "group:g-a@example.com" + ] + } + "europe-west1/c" = { + "roles/compute.networkUser" = [ + "user:c@example.com", "group:g-c@example.com" + ] + } +} +subnets = [ + { + name = "a" + region = "europe-west1" + ip_cidr_range = "10.0.0.0/24" + }, + { + name = "b" + region = "europe-west1" + ip_cidr_range = "10.0.1.0/24", + description = "Subnet b" + enable_private_access = false + }, + { + name = "c" + region = "europe-west1" + ip_cidr_range = "10.0.2.0/24" + secondary_ip_ranges = { + a = "192.168.0.0/24" + b = "192.168.1.0/24" + } + }, + { + name = "d" + region = "europe-west1" + ip_cidr_range = "10.0.3.0/24" + flow_logs_config = { + flow_sampling = 0.5 + aggregation_interval = "INTERVAL_10_MIN" + } + } +] diff --git a/tests/modules/net_vpc/subnets.yaml b/tests/modules/net_vpc/subnets.yaml new file mode 100644 index 000000000..1e44fe80c --- /dev/null +++ b/tests/modules/net_vpc/subnets.yaml @@ -0,0 +1,106 @@ +values: + google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + name: test + project: test-project + routing_mode: GLOBAL + google_compute_subnetwork.subnetwork["europe-west1/a"]: + description: Terraform-managed. + ip_cidr_range: 10.0.0.0/24 + log_config: [] + name: a + private_ip_google_access: true + project: test-project + region: europe-west1 + role: null + secondary_ip_range: [] + google_compute_subnetwork.subnetwork["europe-west1/b"]: + description: Subnet b + ip_cidr_range: 10.0.1.0/24 + log_config: [] + name: b + private_ip_google_access: false + project: test-project + region: europe-west1 + role: null + secondary_ip_range: [] + google_compute_subnetwork.subnetwork["europe-west1/c"]: + description: Terraform-managed. + ip_cidr_range: 10.0.2.0/24 + ipv6_access_type: null + log_config: [] + name: c + private_ip_google_access: true + project: test-project + region: europe-west1 + role: null + secondary_ip_range: + - ip_cidr_range: 192.168.0.0/24 + range_name: a + - ip_cidr_range: 192.168.1.0/24 + range_name: b + google_compute_subnetwork.subnetwork["europe-west1/d"]: + description: Terraform-managed. + ip_cidr_range: 10.0.3.0/24 + log_config: + - aggregation_interval: INTERVAL_10_MIN + filter_expr: 'true' + flow_sampling: 0.5 + metadata: INCLUDE_ALL_METADATA + metadata_fields: null + name: d + private_ip_google_access: true + project: test-project + region: europe-west1 + role: null + secondary_ip_range: [] + google_compute_subnetwork_iam_binding.binding["europe-west1/a.roles/compute.networkUser"]: + condition: [] + members: + - group:g-a@example.com + - user:a@example.com + project: test-project + region: europe-west1 + role: roles/compute.networkUser + subnetwork: a + google_compute_subnetwork_iam_binding.binding["europe-west1/c.roles/compute.networkUser"]: + condition: [] + members: + - group:g-c@example.com + - user:c@example.com + project: test-project + region: europe-west1 + role: roles/compute.networkUser + subnetwork: c + +counts: + google_compute_network: 1 + google_compute_subnetwork: 4 + google_compute_subnetwork_iam_binding: 2 + +outputs: + bindings: __missing__ + project_id: test-project + subnet_ips: + europe-west1/a: 10.0.0.0/24 + europe-west1/b: 10.0.1.0/24 + europe-west1/c: 10.0.2.0/24 + europe-west1/d: 10.0.3.0/24 + subnet_regions: + europe-west1/a: europe-west1 + europe-west1/b: europe-west1 + europe-west1/c: europe-west1 + europe-west1/d: europe-west1 + subnet_secondary_ranges: + europe-west1/a: {} + europe-west1/b: {} + europe-west1/c: + a: 192.168.0.0/24 + b: 192.168.1.0/24 + europe-west1/d: {} + subnet_self_links: __missing__ + subnets: __missing__ + subnets_proxy_only: {} + subnets_psc: {} diff --git a/tests/modules/net_vpc/test_plan.py b/tests/modules/net_vpc/test_plan.py index 2f49f801b..d700b38a8 100644 --- a/tests/modules/net_vpc/test_plan.py +++ b/tests/modules/net_vpc/test_plan.py @@ -12,10 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -_VAR_PEER_VPC_CONFIG = '''{ - peer_vpc_self_link="projects/my-project/global/networks/peer", - export_routes=true, import_routes=null -}''' _VAR_ROUTES_TEMPLATE = '''{ next-hop = { dest_range="192.168.128.0/24", tags=null, @@ -33,41 +29,25 @@ _VAR_ROUTES_NEXT_HOPS = { 'vpn_tunnel': 'regions/europe-west1/vpnTunnels/foo' } - -def test_vpc_simple(plan_runner): - "Test vpc with no extra options." - _, resources = plan_runner() - assert len(resources) == 1 - assert [r['type'] for r in resources] == ['google_compute_network'] - assert [r['values']['name'] for r in resources] == ['test'] - assert [r['values']['project'] for r in resources] == ['test-project'] +import yaml -def test_vpc_shared(plan_runner): - "Test shared vpc variables." - _, resources = plan_runner(shared_vpc_host='true', - shared_vpc_service_projects='["tf-a", "tf-b"]') - assert len(resources) == 4 - assert set(r['type'] for r in resources) == set([ - 'google_compute_network', 'google_compute_shared_vpc_host_project', - 'google_compute_shared_vpc_service_project' - ]) +def test_simple(generic_plan_validator): + generic_plan_validator(inventory_path='simple.yaml', + module_path="modules/net-vpc", + tf_var_files=['common.tfvars']) -def test_vpc_peering(plan_runner): - "Test vpc peering variables." - _, resources = plan_runner(peering_config=_VAR_PEER_VPC_CONFIG) - assert len(resources) == 3 - assert set(r['type'] for r in resources) == set( - ['google_compute_network', 'google_compute_network_peering']) - peerings = [ - r['values'] - for r in resources - if r['type'] == 'google_compute_network_peering' - ] - assert [p['name'] for p in peerings] == ['test-peer', 'peer-test'] - assert [p['export_custom_routes'] for p in peerings] == [True, False] - assert [p['import_custom_routes'] for p in peerings] == [False, True] +def test_vpc_shared(generic_plan_validator): + generic_plan_validator(inventory_path='shared_vpc.yaml', + module_path="modules/net-vpc", + tf_var_files=['common.tfvars', 'shared_vpc.tfvars']) + + +def test_vpc_peering(generic_plan_validator): + generic_plan_validator(inventory_path='peering.yaml', + module_path="modules/net-vpc", + tf_var_files=['common.tfvars', 'peering.tfvars']) def test_vpc_routes(plan_runner): diff --git a/tests/modules/net_vpc/test_plan_psa.py b/tests/modules/net_vpc/test_plan_psa.py index 359977d65..9ae7cdb4d 100644 --- a/tests/modules/net_vpc/test_plan_psa.py +++ b/tests/modules/net_vpc/test_plan_psa.py @@ -12,68 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. - -def test_single_range(plan_runner): - "Test single PSA range." - psa_config = '''{ - ranges = { - bar = "172.16.100.0/24" - foo = "172.16.101.0/24" - }, - routes = null - }''' - _, resources = plan_runner(psa_config=psa_config) - assert len(resources) == 5 - for r in resources: - if r['type'] == 'google_compute_network_peering_routes_config': - assert not r['values']['export_custom_routes'] - assert not r['values']['import_custom_routes'] +import yaml -def test_routes_export(plan_runner): - "Test routes export." - psa_config = '''{ - ranges = { - bar = "172.16.100.0/24" - } - export_routes = true - import_routes = false - }''' - _, resources = plan_runner(psa_config=psa_config) - assert len(resources) == 4 - for r in resources: - if r['type'] == 'google_compute_network_peering_routes_config': - assert r['values']['export_custom_routes'] - assert not r['values']['import_custom_routes'] +def test_simple(generic_plan_validator): + generic_plan_validator(inventory_path='psa_simple.yaml', + module_path="modules/net-vpc", + tf_var_files=['common.tfvars', 'psa_simple.tfvars']) -def test_routes_import(plan_runner): - "Test routes import." - psa_config = '''{ - ranges = { - bar = "172.16.100.0/24" - }, - export_routes = false - import_routes = true - }''' - _, resources = plan_runner(psa_config=psa_config) - for r in resources: - if r['type'] == 'google_compute_network_peering_routes_config': - assert not r['values']['export_custom_routes'] - assert r['values']['import_custom_routes'] +def test_routes_export(generic_plan_validator): + generic_plan_validator( + inventory_path='psa_routes_export.yaml', module_path="modules/net-vpc", + tf_var_files=['common.tfvars', 'psa_routes_export.tfvars']) -def test_routes_export_import(plan_runner): - "Test routes export and import." - psa_config = '''{ - ranges = { - bar = "172.16.100.0/24" - }, - export_routes = true - import_routes = true - }''' - _, resources = plan_runner(psa_config=psa_config) - for r in resources: - if r['type'] == 'google_compute_network_peering_routes_config': - assert r['values']['export_custom_routes'] - assert r['values']['import_custom_routes'] +def test_routes_import(generic_plan_validator): + generic_plan_validator( + inventory_path='psa_routes_import.yaml', module_path="modules/net-vpc", + tf_var_files=['common.tfvars', 'psa_routes_import.tfvars']) + + +def test_routes_import_export(generic_plan_validator): + generic_plan_validator( + inventory_path='psa_routes_import_export.yaml', + module_path="modules/net-vpc", + tf_var_files=['common.tfvars', 'psa_routes_import_export.tfvars']) diff --git a/tests/modules/net_vpc/test_plan_subnets.py b/tests/modules/net_vpc/test_plan_subnets.py index affea44c2..803715343 100644 --- a/tests/modules/net_vpc/test_plan_subnets.py +++ b/tests/modules/net_vpc/test_plan_subnets.py @@ -14,6 +14,8 @@ DATA_FOLDER = "data" +import yaml + def test_subnet_factory(plan_runner): "Test subnet factory." @@ -27,45 +29,7 @@ def test_subnet_factory(plan_runner): assert {s['private_ip_google_access'] for s in subnets} == {True, False} -def test_subnets(plan_runner): - "Test subnets variable." - _, resources = plan_runner(tf_var_file='test.subnets.tfvars') - assert len(resources) == 7 - subnets = [ - r['values'] for r in resources if r['type'] == 'google_compute_subnetwork' - ] - assert {s['name'] for s in subnets} == {'a', 'b', 'c', 'd'} - assert {len(s['secondary_ip_range']) for s in subnets} == {0, 0, 2, 0} - log_config = {s['name']: s['log_config'] for s in subnets if s['log_config']} - assert log_config == { - 'd': [{ - 'aggregation_interval': 'INTERVAL_10_MIN', - 'filter_expr': 'true', - 'flow_sampling': 0.5, - 'metadata': 'INCLUDE_ALL_METADATA', - 'metadata_fields': None - }] - } - bindings = { - r['index']: r['values'] - for r in resources - if r['type'] == 'google_compute_subnetwork_iam_binding' - } - assert bindings == { - 'europe-west1/a.roles/compute.networkUser': { - 'condition': [], - 'members': ['group:g-a@example.com', 'user:a@example.com'], - 'project': 'test-project', - 'region': 'europe-west1', - 'role': 'roles/compute.networkUser', - 'subnetwork': 'a' - }, - 'europe-west1/c.roles/compute.networkUser': { - 'condition': [], - 'members': ['group:g-c@example.com', 'user:c@example.com'], - 'project': 'test-project', - 'region': 'europe-west1', - 'role': 'roles/compute.networkUser', - 'subnetwork': 'c' - }, - } +def test_subnets(generic_plan_validator): + generic_plan_validator(inventory_path='subnets.yaml', + module_path="modules/net-vpc", + tf_var_files=['common.tfvars', 'subnets.tfvars']) From b88f0cf8de43df7ae39daefc0db8d3450310b8cc Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Wed, 30 Nov 2022 21:06:40 +0100 Subject: [PATCH 05/28] Bring back parallel tests --- tests/conftest.py | 60 ++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8a4f9dd40..71b05de96 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -163,36 +163,48 @@ def generic_plan_summary(): def inner(module_path, tf_var_files=None, basedir=None, **tf_vars): # TODO: # - trigger copy from env var - basedir = basedir or BASEDIR + basedir = Path(basedir or BASEDIR) - # prepare tftest - tf = tftest.TerraformTest(module_path, basedir=basedir, - binary=os.environ.get('TERRAFORM', 'terraform')) - tf.setup(upgrade=True) - plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, - tf_vars=tf_vars) + with tempfile.TemporaryDirectory() as tmp_path: + # if TFTEST_COPY is set, copy the fixture to a temporary + # directory before running the plan. This is needed if you want + # to run tests in parallel + if os.environ.get('TFTEST_COPY'): + shutil.copytree(basedir / module_path, tmp_path, dirs_exist_ok=True) + test_path = tmp_path + else: + test_path = module_path - # compute resource type counts and address->values map - values = {} - counts = collections.defaultdict(int) - q = collections.deque([plan.root_module]) - while q: - e = q.popleft() + # prepare tftest + tf = tftest.TerraformTest(test_path, basedir=basedir, + binary=os.environ.get('TERRAFORM', 'terraform')) - if 'type' in e: - counts[e['type']] += 1 - if 'values' in e: - values[e['address']] = e['values'] + tf.setup(upgrade=True) + plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, + tf_vars=tf_vars) - for x in e.get('resources', []): - q.append(x) - for x in e.get('child_modules', []): - q.append(x) + # compute resource type counts and address->values map - # extract planned outputs - outputs = plan.get('planned_values', {}).get('outputs', {}) + values = {} + counts = collections.defaultdict(int) + q = collections.deque([plan.root_module]) + while q: + e = q.popleft() - return PlanSummary(values, dict(counts), outputs) + if 'type' in e: + counts[e['type']] += 1 + if 'values' in e: + values[e['address']] = e['values'] + + for x in e.get('resources', []): + q.append(x) + for x in e.get('child_modules', []): + q.append(x) + + # extract planned outputs + outputs = plan.get('planned_values', {}).get('outputs', {}) + + return PlanSummary(values, dict(counts), outputs) return inner From ace43b75c508b1582537b98acb92aa2ca94efaee Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 1 Dec 2022 09:35:46 +0100 Subject: [PATCH 06/28] Update requirements for tests --- tests/conftest.py | 5 +++-- tests/requirements.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 71b05de96..a85a8722d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -165,10 +165,12 @@ def generic_plan_summary(): # - trigger copy from env var basedir = Path(basedir or BASEDIR) + # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not + # in the environment with tempfile.TemporaryDirectory() as tmp_path: # if TFTEST_COPY is set, copy the fixture to a temporary # directory before running the plan. This is needed if you want - # to run tests in parallel + # to run multiple tests for the same module in parallel if os.environ.get('TFTEST_COPY'): shutil.copytree(basedir / module_path, tmp_path, dirs_exist_ok=True) test_path = tmp_path @@ -184,7 +186,6 @@ def generic_plan_summary(): tf_vars=tf_vars) # compute resource type counts and address->values map - values = {} counts = collections.defaultdict(int) q = collections.deque([plan.root_module]) diff --git a/tests/requirements.txt b/tests/requirements.txt index 3eb583abf..a6f82d750 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,6 +1,6 @@ pytest>=6.2.5 PyYAML>=6.0 -tftest>=1.7.6 +tftest>=1.8.1 marko>=1.2.0 deepdiff>=5.7.0 python-hcl2>=3.0.5 From 0619b35ae60e5c51189841adea47ef9fd83117df Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 1 Dec 2022 11:09:08 +0100 Subject: [PATCH 07/28] Fix fast test --- tests/fast/stages/s00_bootstrap/simple.tfvars | 3 ++- tests/fast/stages/s00_bootstrap/simple.yaml | 18 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/fast/stages/s00_bootstrap/simple.tfvars b/tests/fast/stages/s00_bootstrap/simple.tfvars index c978998dd..f8ef5735b 100644 --- a/tests/fast/stages/s00_bootstrap/simple.tfvars +++ b/tests/fast/stages/s00_bootstrap/simple.tfvars @@ -7,4 +7,5 @@ billing_account = { id = "000000-111111-222222" organization_id = 123456789012 } -prefix = "fast" +prefix = "fast" +outputs_location = "/fast-config" diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml index 4ed7bcbfe..f3a21912f 100644 --- a/tests/fast/stages/s00_bootstrap/simple.yaml +++ b/tests/fast/stages/s00_bootstrap/simple.yaml @@ -8,7 +8,7 @@ values: members: - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com org_id: '123456789012' - role: organizations/123456789012/roles/organizationIamAdmin12 + role: organizations/123456789012/roles/organizationIamAdmin module.automation-project.data.google_bigquery_default_service_account.bq_sa[0]: project: fast-prod-iac-core-0 module.automation-project.data.google_storage_project_service_account.gcs_sa[0]: @@ -555,17 +555,17 @@ values: - group:gcp-security-admins@fast.example.com org_id: '123456789012' role: roles/securitycenter.admin - module.organization.google_organization_iam_custom_role.roles["organizationIamAdmin12"]: + module.organization.google_organization_iam_custom_role.roles["organizationIamAdmin"]: description: Terraform-managed. org_id: '123456789012' permissions: - resourcemanager.organizations.get - resourcemanager.organizations.getIamPolicy - resourcemanager.organizations.setIamPolicy - role_id: organizationIamAdmin12 + role_id: organizationIamAdmin stage: GA - title: Custom role organizationIamAdmin12 - module.organization.google_organization_iam_custom_role.roles["serviceProjectNetworkAdmin12"]: + title: Custom role organizationIamAdmin + module.organization.google_organization_iam_custom_role.roles["serviceProjectNetworkAdmin"]: description: Terraform-managed. org_id: '123456789012' permissions: @@ -579,9 +579,9 @@ values: - compute.subnetworks.setIamPolicy - dns.networks.bindPrivateDNSZone - resourcemanager.projects.get - role_id: serviceProjectNetworkAdmin12 + role_id: serviceProjectNetworkAdmin stage: GA - title: Custom role serviceProjectNetworkAdmin12 + title: Custom role serviceProjectNetworkAdmin module.organization.google_organization_iam_member.additive["roles/accesscontextmanager.policyAdmin-group:gcp-security-admins@fast.example.com"]: condition: [] member: group:gcp-security-admins@fast.example.com @@ -690,8 +690,8 @@ outputs: billing_dataset: __missing__ cicd_repositories: {} custom_roles: - organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin12 - service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin12 + organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin + service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin federated_identity: pool: null providers: {} From 25a3f8687c9c4184ae3cdb7056ead617f5f1c872 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 1 Dec 2022 11:09:46 +0100 Subject: [PATCH 08/28] If copying fixture, remove any auto vars files read by terraform --- tests/conftest.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a85a8722d..594b0227e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,6 +15,7 @@ import collections import inspect +import itertools import os import shutil import tempfile @@ -161,26 +162,33 @@ def basedir(): def generic_plan_summary(): def inner(module_path, tf_var_files=None, basedir=None, **tf_vars): - # TODO: - # - trigger copy from env var basedir = Path(basedir or BASEDIR) + module_path = basedir / module_path # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not # in the environment - with tempfile.TemporaryDirectory() as tmp_path: + with tempfile.TemporaryDirectory(dir=module_path.parent) as tmp_path: # if TFTEST_COPY is set, copy the fixture to a temporary # directory before running the plan. This is needed if you want # to run multiple tests for the same module in parallel if os.environ.get('TFTEST_COPY'): - shutil.copytree(basedir / module_path, tmp_path, dirs_exist_ok=True) - test_path = tmp_path + test_path = Path(tmp_path) + shutil.copytree(module_path, test_path, dirs_exist_ok=True) + + # if we're copying, we might as well remove any auto.tfvars + # files from the destintion directory to avoid surprises (e.g. + # if you have an active fast deployment with links to configs) + autofiles = itertools.chain(test_path.glob("*.auto.tfvars"), + test_path.glob("*.auto.tfvars.json"), + test_path.glob("terraform.tfvars")) + for f in autofiles: + f.unlink() else: test_path = module_path - # prepare tftest - tf = tftest.TerraformTest(test_path, basedir=basedir, - binary=os.environ.get('TERRAFORM', 'terraform')) - + # prepare tftest and run plan + binary = os.environ.get('TERRAFORM', 'terraform') + tf = tftest.TerraformTest(test_path, basedir=basedir, binary=binary) tf.setup(upgrade=True) plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, tf_vars=tf_vars) @@ -254,9 +262,11 @@ def generic_plan_validator(generic_plan_summary): if 'counts' in inventory: expected_counts = inventory['counts'] for type_, expected_count in expected_counts.items(): - assert type_ in summary.counts + assert type_ in summary.counts, \ + f'module does not create any resources of type `{type_}`' plan_count = summary.counts[type_] - assert plan_count == expected_count + assert plan_count == expected_count, \ + f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' if 'outputs' in inventory: expected_outputs = inventory['outputs'] From 181533786b78a8202efda293b1c22f49288e976d Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 1 Dec 2022 11:23:12 +0100 Subject: [PATCH 09/28] remove key from fast values inventory --- tests/fast/stages/s00_bootstrap/simple.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml index f3a21912f..3f2f01506 100644 --- a/tests/fast/stages/s00_bootstrap/simple.yaml +++ b/tests/fast/stages/s00_bootstrap/simple.yaml @@ -333,7 +333,6 @@ values: friendly_name: Billing export. labels: null location: EU - max_time_travel_hours: null project: fast-prod-billing-exp-0 module.billing-export-project[0].data.google_bigquery_default_service_account.bq_sa[0]: project: fast-prod-billing-exp-0 @@ -380,7 +379,6 @@ values: friendly_name: Audit logs export. labels: null location: EU - max_time_travel_hours: null project: fast-prod-audit-logs-0 module.log-export-project.data.google_bigquery_default_service_account.bq_sa[0]: project: fast-prod-audit-logs-0 From 354ab110f8af8f8ff6b5001eeeb985bbb62ea519 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 1 Dec 2022 16:36:03 +0100 Subject: [PATCH 10/28] Simplify path handling --- tests/conftest.py | 81 +++++++++++++++----- tests/fast/stages/s00_bootstrap/test_plan.py | 8 +- tests/modules/net_vpc/test_plan.py | 61 +++++++++------ tests/modules/net_vpc/test_plan_subnets.py | 9 ++- 4 files changed, 111 insertions(+), 48 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 594b0227e..b36e04a64 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -159,11 +159,43 @@ def basedir(): @pytest.fixture -def generic_plan_summary(): +def generic_plan_summary(request): + 'Returns a function to generate a PlanSummary' - def inner(module_path, tf_var_files=None, basedir=None, **tf_vars): - basedir = Path(basedir or BASEDIR) - module_path = basedir / module_path + def inner(module_path, basedir=None, tf_var_files=None, **tf_vars): + '''Run a Terraform plan on the module located at `module_path`.\ + + - module_path: terraform root module to run. Can be an absolute + path or relative to the root of the repository + + - basedir: directory root to use for relative paths in + tf_var_files. If None, then paths are relative to the calling + test function + + - tf_var_files: set of terraform variable files (tfvars) to pass + in to terraform + + Returns a PlanSummary object containing 3 attributes: + - values: dictionary where the keys are terraform plan addresses + and values are the JSON representation (converted to python + types) of the attribute values of the resource. + + - counts: dictionary where the keys are the terraform resource + types and the values are the number of times that type appears + in the plan + + - outputs: dictionary of the modules outputs that can be + determined at plan type. + + Consult [1] for mode details on the structure of values and outputs + + [1] https://developer.hashicorp.com/terraform/internals/json-format + + ''' + + if basedir is None: + basedir = Path(request.fspath).parent + module_path = Path(BASEDIR) / module_path # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not # in the environment @@ -175,21 +207,32 @@ def generic_plan_summary(): test_path = Path(tmp_path) shutil.copytree(module_path, test_path, dirs_exist_ok=True) - # if we're copying, we might as well remove any auto.tfvars - # files from the destintion directory to avoid surprises (e.g. - # if you have an active fast deployment with links to configs) - autofiles = itertools.chain(test_path.glob("*.auto.tfvars"), - test_path.glob("*.auto.tfvars.json"), - test_path.glob("terraform.tfvars")) - for f in autofiles: - f.unlink() + # if we're copying the module, we might as well remove any + # files and directories from the test directory that are + # automatically read by terraform. Useful to avoid surprises + # with (e.g. if you have an active fast deployment with links + # to configs) + autopaths = itertools.chain( + test_path.glob("*.auto.tfvars"), + test_path.glob("*.auto.tfvars.json"), + test_path.glob("terraform.tfstate*"), + test_path.glob("terraform.tfvars"), + test_path.glob(".terraform"), + # any symlinks? + ) + for p in autopaths: + if p.is_dir(): + shutil.rmtree(p) + else: + p.unlink() else: test_path = module_path # prepare tftest and run plan binary = os.environ.get('TERRAFORM', 'terraform') - tf = tftest.TerraformTest(test_path, basedir=basedir, binary=binary) + tf = tftest.TerraformTest(test_path, binary=binary) tf.setup(upgrade=True) + tf_var_files = [basedir / x for x in tf_var_files or []] plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, tf_vars=tf_vars) @@ -219,15 +262,17 @@ def generic_plan_summary(): @pytest.fixture -def generic_plan_validator(generic_plan_summary): +def generic_plan_validator(generic_plan_summary, request): + 'Return a function that builds a PlanSummary and compares it to an yaml inventory' - def inner(inventory_path, module_path, tf_var_files=None, basedir=None, + def inner(inventory_path, module_path, basedir=None, tf_var_files=None, **tf_vars): + if basedir is None: + basedir = Path(request.fspath).parent + # allow tfvars and inventory to be relative to the caller - caller_path = Path(inspect.stack()[1].filename).parent - tf_var_files = [caller_path / x for x in tf_var_files] - inventory_path = caller_path / inventory_path + inventory_path = basedir / inventory_path inventory = yaml.safe_load(inventory_path.read_text()) assert inventory is not None, f'Inventory {inventory_path} is empty' diff --git a/tests/fast/stages/s00_bootstrap/test_plan.py b/tests/fast/stages/s00_bootstrap/test_plan.py index 7cf59bae5..d2c0d533f 100644 --- a/tests/fast/stages/s00_bootstrap/test_plan.py +++ b/tests/fast/stages/s00_bootstrap/test_plan.py @@ -14,6 +14,8 @@ def test_simple(generic_plan_validator): - s = generic_plan_validator(inventory_path='simple.yaml', - module_path="fast/stages/00-bootstrap", - tf_var_files=['simple.tfvars']) + generic_plan_validator( + inventory_path='simple.yaml', + module_path="fast/stages/00-bootstrap", + tf_var_files=['simple.tfvars'], + ) diff --git a/tests/modules/net_vpc/test_plan.py b/tests/modules/net_vpc/test_plan.py index d700b38a8..e50c81979 100644 --- a/tests/modules/net_vpc/test_plan.py +++ b/tests/modules/net_vpc/test_plan.py @@ -14,12 +14,18 @@ _VAR_ROUTES_TEMPLATE = '''{ next-hop = { - dest_range="192.168.128.0/24", tags=null, - next_hop_type="%s", next_hop="%s"}, + dest_range = "192.168.128.0/24" + tags = null + next_hop_type = "%s" + next_hop = "%s" + } gateway = { - dest_range="0.0.0.0/0", priority=100, tags=["tag-a"], - next_hop_type="gateway", - next_hop="global/gateways/default-internet-gateway"} + dest_range = "0.0.0.0/0", + priority = 100 + tags = ["tag-a"] + next_hop_type = "gateway", + next_hop = "global/gateways/default-internet-gateway" + } }''' _VAR_ROUTES_NEXT_HOPS = { 'gateway': 'global/gateways/default-internet-gateway', @@ -29,33 +35,40 @@ _VAR_ROUTES_NEXT_HOPS = { 'vpn_tunnel': 'regions/europe-west1/vpnTunnels/foo' } -import yaml - def test_simple(generic_plan_validator): - generic_plan_validator(inventory_path='simple.yaml', - module_path="modules/net-vpc", - tf_var_files=['common.tfvars']) + generic_plan_validator( + inventory_path='simple.yaml', + module_path='modules/net-vpc', + tf_var_files=['common.tfvars'], + ) def test_vpc_shared(generic_plan_validator): - generic_plan_validator(inventory_path='shared_vpc.yaml', - module_path="modules/net-vpc", - tf_var_files=['common.tfvars', 'shared_vpc.tfvars']) + generic_plan_validator( + inventory_path='shared_vpc.yaml', + module_path='modules/net-vpc', + tf_var_files=['common.tfvars', 'shared_vpc.tfvars'], + ) def test_vpc_peering(generic_plan_validator): - generic_plan_validator(inventory_path='peering.yaml', - module_path="modules/net-vpc", - tf_var_files=['common.tfvars', 'peering.tfvars']) + generic_plan_validator( + inventory_path='peering.yaml', + module_path='modules/net-vpc', + tf_var_files=['common.tfvars', 'peering.tfvars'], + ) -def test_vpc_routes(plan_runner): - "Test vpc routes." +def test_vpc_routes(generic_plan_summary): + 'Test vpc routes.' for next_hop_type, next_hop in _VAR_ROUTES_NEXT_HOPS.items(): - _var_routes = _VAR_ROUTES_TEMPLATE % (next_hop_type, next_hop) - _, resources = plan_runner(routes=_var_routes) - assert len(resources) == 3 - resource = [r for r in resources if r['values']['name'] == 'test-next-hop' - ][0] - assert resource['values']['next_hop_%s' % next_hop_type] + var_routes = _VAR_ROUTES_TEMPLATE % (next_hop_type, next_hop) + summary = generic_plan_summary( + module_path='modules/net-vpc', + tf_var_files=['common.tfvars'], + routes=var_routes, + ) + assert len(summary.values) == 3 + route = summary.values[f'google_compute_route.{next_hop_type}["next-hop"]'] + assert route[f'next_hop_{next_hop_type}'] == next_hop diff --git a/tests/modules/net_vpc/test_plan_subnets.py b/tests/modules/net_vpc/test_plan_subnets.py index 803715343..ef155c7c8 100644 --- a/tests/modules/net_vpc/test_plan_subnets.py +++ b/tests/modules/net_vpc/test_plan_subnets.py @@ -15,6 +15,7 @@ DATA_FOLDER = "data" import yaml +from pathlib import Path def test_subnet_factory(plan_runner): @@ -30,6 +31,8 @@ def test_subnet_factory(plan_runner): def test_subnets(generic_plan_validator): - generic_plan_validator(inventory_path='subnets.yaml', - module_path="modules/net-vpc", - tf_var_files=['common.tfvars', 'subnets.tfvars']) + generic_plan_validator( + inventory_path='subnets.yaml', + module_path="modules/net-vpc", + tf_var_files=['common.tfvars', 'subnets.tfvars'], + ) From 8631d698cb8a88e6a54e1b8a8d33d8bd5ff643ca Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 1 Dec 2022 17:31:25 +0100 Subject: [PATCH 11/28] Reorder fixture parameters --- tests/conftest.py | 110 ++++++++++--------- tests/fast/stages/s00_bootstrap/test_plan.py | 7 +- tests/modules/net_vpc/test_plan.py | 27 ++--- tests/modules/net_vpc/test_plan_psa.py | 21 ++-- tests/modules/net_vpc/test_plan_subnets.py | 7 +- 5 files changed, 77 insertions(+), 95 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b36e04a64..0b23ed084 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -162,7 +162,7 @@ def basedir(): def generic_plan_summary(request): 'Returns a function to generate a PlanSummary' - def inner(module_path, basedir=None, tf_var_files=None, **tf_vars): + def inner(module_path, tf_var_files=None, basedir=None, **tf_vars): '''Run a Terraform plan on the module located at `module_path`.\ - module_path: terraform root module to run. Can be an absolute @@ -210,8 +210,8 @@ def generic_plan_summary(request): # if we're copying the module, we might as well remove any # files and directories from the test directory that are # automatically read by terraform. Useful to avoid surprises - # with (e.g. if you have an active fast deployment with links - # to configs) + # surprises if, for example, you have an active fast + # deployment with links to configs) autopaths = itertools.chain( test_path.glob("*.auto.tfvars"), test_path.glob("*.auto.tfvars.json"), @@ -265,65 +265,71 @@ def generic_plan_summary(request): def generic_plan_validator(generic_plan_summary, request): 'Return a function that builds a PlanSummary and compares it to an yaml inventory' - def inner(inventory_path, module_path, basedir=None, tf_var_files=None, + def inner(module_path, inventory_paths, tf_var_files=None, basedir=None, **tf_vars): if basedir is None: basedir = Path(request.fspath).parent - # allow tfvars and inventory to be relative to the caller - inventory_path = basedir / inventory_path - inventory = yaml.safe_load(inventory_path.read_text()) - assert inventory is not None, f'Inventory {inventory_path} is empty' + summary = generic_plan_summary(module_path=module_path, + tf_var_files=tf_var_files, basedir=basedir, + **tf_vars) - summary = generic_plan_summary(module_path, tf_var_files=tf_var_files, - basedir=basedir) + # allow single single string for inventory_paths + if not isinstance(inventory_paths, list): + inventory_paths = [inventory_paths] - # If you add additional asserts to this function: - # - put the values coming from the plan on the left side of - # any comparison operators - # - put the values coming from user's inventory the right - # side of any comparison operators. - # - include a descriptive error message to the assert + for path in inventory_paths: + # allow tfvars and inventory to be relative to the caller + path = basedir / path + inventory = yaml.safe_load(path.read_text()) + assert inventory is not None, f'Inventory {path} is empty' - # for values: - # - verify each address in the user's inventory exists in the plan - # - for those address that exist on both the user's inventory and - # the plan output, ensure the set of keys on the inventory are a - # subset of the keys in the plan, and compare their values by - # equality - if 'values' in inventory: - expected_values = inventory['values'] - for address, expected_value in expected_values.items(): - assert address in summary.values, \ - f'{address} is not a valid address in the plan' - for k, v in expected_value.items(): - assert k in summary.values[address], \ - f'{k} not found at {address}' - plan_value = summary.values[address][k] - assert plan_value == v, \ - f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' + # If you add additional asserts to this function: + # - put the values coming from the plan on the left side of + # any comparison operators + # - put the values coming from user's inventory the right + # side of any comparison operators. + # - include a descriptive error message to the assert - if 'counts' in inventory: - expected_counts = inventory['counts'] - for type_, expected_count in expected_counts.items(): - assert type_ in summary.counts, \ - f'module does not create any resources of type `{type_}`' - plan_count = summary.counts[type_] - assert plan_count == expected_count, \ - f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' + # for values: + # - verify each address in the user's inventory exists in the plan + # - for those address that exist on both the user's inventory and + # the plan output, ensure the set of keys on the inventory are a + # subset of the keys in the plan, and compare their values by + # equality + if 'values' in inventory: + expected_values = inventory['values'] + for address, expected_value in expected_values.items(): + assert address in summary.values, \ + f'{address} is not a valid address in the plan' + for k, v in expected_value.items(): + assert k in summary.values[address], \ + f'{k} not found at {address}' + plan_value = summary.values[address][k] + assert plan_value == v, \ + f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' - if 'outputs' in inventory: - expected_outputs = inventory['outputs'] - for output_name, expected_output in expected_outputs.items(): - assert output_name in summary.outputs, \ - f'module does not output `{output_name}`' - output = summary.outputs[output_name] - # assert 'value' in output, \ - # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' - plan_output = output.get('value', '__missing__') - assert plan_output == expected_output, \ - f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' + if 'counts' in inventory: + expected_counts = inventory['counts'] + for type_, expected_count in expected_counts.items(): + assert type_ in summary.counts, \ + f'module does not create any resources of type `{type_}`' + plan_count = summary.counts[type_] + assert plan_count == expected_count, \ + f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' + + if 'outputs' in inventory: + expected_outputs = inventory['outputs'] + for output_name, expected_output in expected_outputs.items(): + assert output_name in summary.outputs, \ + f'module does not output `{output_name}`' + output = summary.outputs[output_name] + # assert 'value' in output, \ + # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' + plan_output = output.get('value', '__missing__') + assert plan_output == expected_output, \ + f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' return summary diff --git a/tests/fast/stages/s00_bootstrap/test_plan.py b/tests/fast/stages/s00_bootstrap/test_plan.py index d2c0d533f..206b394a5 100644 --- a/tests/fast/stages/s00_bootstrap/test_plan.py +++ b/tests/fast/stages/s00_bootstrap/test_plan.py @@ -14,8 +14,5 @@ def test_simple(generic_plan_validator): - generic_plan_validator( - inventory_path='simple.yaml', - module_path="fast/stages/00-bootstrap", - tf_var_files=['simple.tfvars'], - ) + generic_plan_validator("fast/stages/00-bootstrap", 'simple.yaml', + ['simple.tfvars']) diff --git a/tests/modules/net_vpc/test_plan.py b/tests/modules/net_vpc/test_plan.py index e50c81979..5deec3004 100644 --- a/tests/modules/net_vpc/test_plan.py +++ b/tests/modules/net_vpc/test_plan.py @@ -37,38 +37,25 @@ _VAR_ROUTES_NEXT_HOPS = { def test_simple(generic_plan_validator): - generic_plan_validator( - inventory_path='simple.yaml', - module_path='modules/net-vpc', - tf_var_files=['common.tfvars'], - ) + generic_plan_validator('modules/net-vpc', 'simple.yaml', ['common.tfvars']) def test_vpc_shared(generic_plan_validator): - generic_plan_validator( - inventory_path='shared_vpc.yaml', - module_path='modules/net-vpc', - tf_var_files=['common.tfvars', 'shared_vpc.tfvars'], - ) + generic_plan_validator('modules/net-vpc', 'shared_vpc.yaml', + ['common.tfvars', 'shared_vpc.tfvars']) def test_vpc_peering(generic_plan_validator): - generic_plan_validator( - inventory_path='peering.yaml', - module_path='modules/net-vpc', - tf_var_files=['common.tfvars', 'peering.tfvars'], - ) + generic_plan_validator('modules/net-vpc', 'peering.yaml', + ['common.tfvars', 'peering.tfvars']) def test_vpc_routes(generic_plan_summary): 'Test vpc routes.' for next_hop_type, next_hop in _VAR_ROUTES_NEXT_HOPS.items(): var_routes = _VAR_ROUTES_TEMPLATE % (next_hop_type, next_hop) - summary = generic_plan_summary( - module_path='modules/net-vpc', - tf_var_files=['common.tfvars'], - routes=var_routes, - ) + summary = generic_plan_summary('modules/net-vpc', ['common.tfvars'], + routes=var_routes) assert len(summary.values) == 3 route = summary.values[f'google_compute_route.{next_hop_type}["next-hop"]'] assert route[f'next_hop_{next_hop_type}'] == next_hop diff --git a/tests/modules/net_vpc/test_plan_psa.py b/tests/modules/net_vpc/test_plan_psa.py index 9ae7cdb4d..ad26e559e 100644 --- a/tests/modules/net_vpc/test_plan_psa.py +++ b/tests/modules/net_vpc/test_plan_psa.py @@ -16,25 +16,20 @@ import yaml def test_simple(generic_plan_validator): - generic_plan_validator(inventory_path='psa_simple.yaml', - module_path="modules/net-vpc", - tf_var_files=['common.tfvars', 'psa_simple.tfvars']) + generic_plan_validator("modules/net-vpc", 'psa_simple.yaml', + ['common.tfvars', 'psa_simple.tfvars']) def test_routes_export(generic_plan_validator): - generic_plan_validator( - inventory_path='psa_routes_export.yaml', module_path="modules/net-vpc", - tf_var_files=['common.tfvars', 'psa_routes_export.tfvars']) + generic_plan_validator("modules/net-vpc", 'psa_routes_export.yaml', + ['common.tfvars', 'psa_routes_export.tfvars']) def test_routes_import(generic_plan_validator): - generic_plan_validator( - inventory_path='psa_routes_import.yaml', module_path="modules/net-vpc", - tf_var_files=['common.tfvars', 'psa_routes_import.tfvars']) + generic_plan_validator("modules/net-vpc", 'psa_routes_import.yaml', + ['common.tfvars', 'psa_routes_import.tfvars']) def test_routes_import_export(generic_plan_validator): - generic_plan_validator( - inventory_path='psa_routes_import_export.yaml', - module_path="modules/net-vpc", - tf_var_files=['common.tfvars', 'psa_routes_import_export.tfvars']) + generic_plan_validator("modules/net-vpc", 'psa_routes_import_export.yaml', + ['common.tfvars', 'psa_routes_import_export.tfvars']) diff --git a/tests/modules/net_vpc/test_plan_subnets.py b/tests/modules/net_vpc/test_plan_subnets.py index ef155c7c8..f9af768f3 100644 --- a/tests/modules/net_vpc/test_plan_subnets.py +++ b/tests/modules/net_vpc/test_plan_subnets.py @@ -31,8 +31,5 @@ def test_subnet_factory(plan_runner): def test_subnets(generic_plan_validator): - generic_plan_validator( - inventory_path='subnets.yaml', - module_path="modules/net-vpc", - tf_var_files=['common.tfvars', 'subnets.tfvars'], - ) + generic_plan_validator("modules/net-vpc", 'subnets.yaml', + ['common.tfvars', 'subnets.tfvars']) From 553ca3fcdf5a4b29c8056bf9100305515638ee6b Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 2 Dec 2022 15:28:15 +0100 Subject: [PATCH 12/28] Allow defining tests via yaml (yes, more yaml) --- tests/conftest.py | 352 ++++++++++++++----------- tests/modules/net_vpc/test_plan.py | 61 ----- tests/modules/net_vpc/test_plan_psa.py | 35 --- tests/modules/net_vpc/test_routes.py | 47 ++++ tests/modules/net_vpc/tftest.psa.yaml | 22 ++ tests/modules/net_vpc/tftest.yaml | 27 ++ 6 files changed, 293 insertions(+), 251 deletions(-) delete mode 100644 tests/modules/net_vpc/test_plan.py delete mode 100644 tests/modules/net_vpc/test_plan_psa.py create mode 100644 tests/modules/net_vpc/test_routes.py create mode 100644 tests/modules/net_vpc/tftest.psa.yaml create mode 100644 tests/modules/net_vpc/tftest.yaml diff --git a/tests/conftest.py b/tests/conftest.py index 0b23ed084..fc2ea4aa3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -158,179 +158,221 @@ def basedir(): return BASEDIR +def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, + **tf_vars): + '''Run a Terraform plan on the module located at `module_path`.\ + + - module_path: terraform root module to run. Can be an absolute + path or relative to the root of the repository + + - basedir: directory root to use for relative paths in + tf_var_files. If None, then paths are relative to the calling + test function + + - tf_var_files: set of terraform variable files (tfvars) to pass + in to terraform + + Returns a PlanSummary object containing 3 attributes: + - values: dictionary where the keys are terraform plan addresses + and values are the JSON representation (converted to python + types) of the attribute values of the resource. + + - counts: dictionary where the keys are the terraform resource + types and the values are the number of times that type appears + in the plan + + - outputs: dictionary of the modules outputs that can be + determined at plan type. + + Consult [1] for mode details on the structure of values and outputs + + [1] https://developer.hashicorp.com/terraform/internals/json-format + + ''' + + module_path = Path(BASEDIR) / module_path + + # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not + # in the environment + with tempfile.TemporaryDirectory(dir=module_path.parent) as tmp_path: + # if TFTEST_COPY is set, copy the fixture to a temporary + # directory before running the plan. This is needed if you want + # to run multiple tests for the same module in parallel + if os.environ.get('TFTEST_COPY'): + test_path = Path(tmp_path) + shutil.copytree(module_path, test_path, dirs_exist_ok=True) + + # if we're copying the module, we might as well remove any + # files and directories from the test directory that are + # automatically read by terraform. Useful to avoid surprises + # surprises if, for example, you have an active fast + # deployment with links to configs) + autopaths = itertools.chain( + test_path.glob("*.auto.tfvars"), + test_path.glob("*.auto.tfvars.json"), + test_path.glob("terraform.tfstate*"), + test_path.glob("terraform.tfvars"), + test_path.glob(".terraform"), + # any symlinks? + ) + for p in autopaths: + if p.is_dir(): + shutil.rmtree(p) + else: + p.unlink() + else: + test_path = module_path + + # prepare tftest and run plan + binary = os.environ.get('TERRAFORM', 'terraform') + tf = tftest.TerraformTest(test_path, binary=binary) + tf.setup(upgrade=True) + tf_var_files = [basedir / x for x in tf_var_files or []] + plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, + tf_vars=tf_vars) + + # compute resource type counts and address->values map + values = {} + counts = collections.defaultdict(int) + q = collections.deque([plan.root_module]) + while q: + e = q.popleft() + + if 'type' in e: + counts[e['type']] += 1 + if 'values' in e: + values[e['address']] = e['values'] + + for x in e.get('resources', []): + q.append(x) + for x in e.get('child_modules', []): + q.append(x) + + # extract planned outputs + outputs = plan.get('planned_values', {}).get('outputs', {}) + + return PlanSummary(values, dict(counts), outputs) + + @pytest.fixture def generic_plan_summary(request): 'Returns a function to generate a PlanSummary' def inner(module_path, tf_var_files=None, basedir=None, **tf_vars): - '''Run a Terraform plan on the module located at `module_path`.\ - - - module_path: terraform root module to run. Can be an absolute - path or relative to the root of the repository - - - basedir: directory root to use for relative paths in - tf_var_files. If None, then paths are relative to the calling - test function - - - tf_var_files: set of terraform variable files (tfvars) to pass - in to terraform - - Returns a PlanSummary object containing 3 attributes: - - values: dictionary where the keys are terraform plan addresses - and values are the JSON representation (converted to python - types) of the attribute values of the resource. - - - counts: dictionary where the keys are the terraform resource - types and the values are the number of times that type appears - in the plan - - - outputs: dictionary of the modules outputs that can be - determined at plan type. - - Consult [1] for mode details on the structure of values and outputs - - [1] https://developer.hashicorp.com/terraform/internals/json-format - - ''' - if basedir is None: basedir = Path(request.fspath).parent - module_path = Path(BASEDIR) / module_path - - # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not - # in the environment - with tempfile.TemporaryDirectory(dir=module_path.parent) as tmp_path: - # if TFTEST_COPY is set, copy the fixture to a temporary - # directory before running the plan. This is needed if you want - # to run multiple tests for the same module in parallel - if os.environ.get('TFTEST_COPY'): - test_path = Path(tmp_path) - shutil.copytree(module_path, test_path, dirs_exist_ok=True) - - # if we're copying the module, we might as well remove any - # files and directories from the test directory that are - # automatically read by terraform. Useful to avoid surprises - # surprises if, for example, you have an active fast - # deployment with links to configs) - autopaths = itertools.chain( - test_path.glob("*.auto.tfvars"), - test_path.glob("*.auto.tfvars.json"), - test_path.glob("terraform.tfstate*"), - test_path.glob("terraform.tfvars"), - test_path.glob(".terraform"), - # any symlinks? - ) - for p in autopaths: - if p.is_dir(): - shutil.rmtree(p) - else: - p.unlink() - else: - test_path = module_path - - # prepare tftest and run plan - binary = os.environ.get('TERRAFORM', 'terraform') - tf = tftest.TerraformTest(test_path, binary=binary) - tf.setup(upgrade=True) - tf_var_files = [basedir / x for x in tf_var_files or []] - plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, - tf_vars=tf_vars) - - # compute resource type counts and address->values map - values = {} - counts = collections.defaultdict(int) - q = collections.deque([plan.root_module]) - while q: - e = q.popleft() - - if 'type' in e: - counts[e['type']] += 1 - if 'values' in e: - values[e['address']] = e['values'] - - for x in e.get('resources', []): - q.append(x) - for x in e.get('child_modules', []): - q.append(x) - - # extract planned outputs - outputs = plan.get('planned_values', {}).get('outputs', {}) - - return PlanSummary(values, dict(counts), outputs) + return _generic_plan_summary(module_path, tf_var_files, basedir, **tf_vars) return inner +def _generic_plan_validator(module_path, inventory_paths, tf_var_files=None, + basedir=None, **tf_vars): + summary = _generic_plan_summary(module_path=module_path, + tf_var_files=tf_var_files, basedir=basedir, + **tf_vars) + + # allow single single string for inventory_paths + if not isinstance(inventory_paths, list): + inventory_paths = [inventory_paths] + + for path in inventory_paths: + # allow tfvars and inventory to be relative to the caller + path = basedir / path + inventory = yaml.safe_load(path.read_text()) + assert inventory is not None, f'Inventory {path} is empty' + + # If you add additional asserts to this function: + # - put the values coming from the plan on the left side of + # any comparison operators + # - put the values coming from user's inventory the right + # side of any comparison operators. + # - include a descriptive error message to the assert + + # for values: + # - verify each address in the user's inventory exists in the plan + # - for those address that exist on both the user's inventory and + # the plan output, ensure the set of keys on the inventory are a + # subset of the keys in the plan, and compare their values by + # equality + if 'values' in inventory: + expected_values = inventory['values'] + for address, expected_value in expected_values.items(): + assert address in summary.values, \ + f'{address} is not a valid address in the plan' + for k, v in expected_value.items(): + assert k in summary.values[address], \ + f'{k} not found at {address}' + plan_value = summary.values[address][k] + assert plan_value == v, \ + f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' + + if 'counts' in inventory: + expected_counts = inventory['counts'] + for type_, expected_count in expected_counts.items(): + assert type_ in summary.counts, \ + f'module does not create any resources of type `{type_}`' + plan_count = summary.counts[type_] + assert plan_count == expected_count, \ + f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' + + if 'outputs' in inventory: + expected_outputs = inventory['outputs'] + for output_name, expected_output in expected_outputs.items(): + assert output_name in summary.outputs, \ + f'module does not output `{output_name}`' + output = summary.outputs[output_name] + # assert 'value' in output, \ + # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' + plan_output = output.get('value', '__missing__') + assert plan_output == expected_output, \ + f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' + + return summary + + @pytest.fixture def generic_plan_validator(generic_plan_summary, request): 'Return a function that builds a PlanSummary and compares it to an yaml inventory' def inner(module_path, inventory_paths, tf_var_files=None, basedir=None, **tf_vars): - if basedir is None: basedir = Path(request.fspath).parent - - summary = generic_plan_summary(module_path=module_path, - tf_var_files=tf_var_files, basedir=basedir, - **tf_vars) - - # allow single single string for inventory_paths - if not isinstance(inventory_paths, list): - inventory_paths = [inventory_paths] - - for path in inventory_paths: - # allow tfvars and inventory to be relative to the caller - path = basedir / path - inventory = yaml.safe_load(path.read_text()) - assert inventory is not None, f'Inventory {path} is empty' - - # If you add additional asserts to this function: - # - put the values coming from the plan on the left side of - # any comparison operators - # - put the values coming from user's inventory the right - # side of any comparison operators. - # - include a descriptive error message to the assert - - # for values: - # - verify each address in the user's inventory exists in the plan - # - for those address that exist on both the user's inventory and - # the plan output, ensure the set of keys on the inventory are a - # subset of the keys in the plan, and compare their values by - # equality - if 'values' in inventory: - expected_values = inventory['values'] - for address, expected_value in expected_values.items(): - assert address in summary.values, \ - f'{address} is not a valid address in the plan' - for k, v in expected_value.items(): - assert k in summary.values[address], \ - f'{k} not found at {address}' - plan_value = summary.values[address][k] - assert plan_value == v, \ - f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' - - if 'counts' in inventory: - expected_counts = inventory['counts'] - for type_, expected_count in expected_counts.items(): - assert type_ in summary.counts, \ - f'module does not create any resources of type `{type_}`' - plan_count = summary.counts[type_] - assert plan_count == expected_count, \ - f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' - - if 'outputs' in inventory: - expected_outputs = inventory['outputs'] - for output_name, expected_output in expected_outputs.items(): - assert output_name in summary.outputs, \ - f'module does not output `{output_name}`' - output = summary.outputs[output_name] - # assert 'value' in output, \ - # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' - plan_output = output.get('value', '__missing__') - assert plan_output == expected_output, \ - f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' - - return summary + return _generic_plan_validator(module_path, inventory_paths, tf_var_files, + basedir, **tf_vars) return inner + + +def pytest_collect_file(parent, file_path): + if file_path.suffix == ".yaml" and file_path.name.startswith("tftest"): + return YamlFile.from_parent(parent, path=file_path) + + +class YamlFile(pytest.File): + + def collect(self): + raw = yaml.safe_load(self.path.open()) + module = raw['module'] + for test_name, spec in raw['tests'].items(): + inventory = spec.get('inventory', f'{test_name}.yaml') + tfvars = spec['tfvars'] + yield YamlItem.from_parent(self, name=test_name, module=module, + inventory=inventory, tfvars=tfvars) + + +class YamlItem(pytest.Item): + + def __init__(self, name, parent, module, inventory, tfvars): + super().__init__(name, parent) + self.module = module + self.inventory = inventory + self.tfvars = tfvars + + def runtest(self): + _generic_plan_validator(self.module, self.inventory, self.tfvars, + self.parent.path.parent) + + def reportinfo(self): + return self.path, None, self.name diff --git a/tests/modules/net_vpc/test_plan.py b/tests/modules/net_vpc/test_plan.py deleted file mode 100644 index 5deec3004..000000000 --- a/tests/modules/net_vpc/test_plan.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2022 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. - -_VAR_ROUTES_TEMPLATE = '''{ - next-hop = { - dest_range = "192.168.128.0/24" - tags = null - next_hop_type = "%s" - next_hop = "%s" - } - gateway = { - dest_range = "0.0.0.0/0", - priority = 100 - tags = ["tag-a"] - next_hop_type = "gateway", - next_hop = "global/gateways/default-internet-gateway" - } -}''' -_VAR_ROUTES_NEXT_HOPS = { - 'gateway': 'global/gateways/default-internet-gateway', - 'instance': 'zones/europe-west1-b/test', - 'ip': '192.168.0.128', - 'ilb': 'regions/europe-west1/forwardingRules/test', - 'vpn_tunnel': 'regions/europe-west1/vpnTunnels/foo' -} - - -def test_simple(generic_plan_validator): - generic_plan_validator('modules/net-vpc', 'simple.yaml', ['common.tfvars']) - - -def test_vpc_shared(generic_plan_validator): - generic_plan_validator('modules/net-vpc', 'shared_vpc.yaml', - ['common.tfvars', 'shared_vpc.tfvars']) - - -def test_vpc_peering(generic_plan_validator): - generic_plan_validator('modules/net-vpc', 'peering.yaml', - ['common.tfvars', 'peering.tfvars']) - - -def test_vpc_routes(generic_plan_summary): - 'Test vpc routes.' - for next_hop_type, next_hop in _VAR_ROUTES_NEXT_HOPS.items(): - var_routes = _VAR_ROUTES_TEMPLATE % (next_hop_type, next_hop) - summary = generic_plan_summary('modules/net-vpc', ['common.tfvars'], - routes=var_routes) - assert len(summary.values) == 3 - route = summary.values[f'google_compute_route.{next_hop_type}["next-hop"]'] - assert route[f'next_hop_{next_hop_type}'] == next_hop diff --git a/tests/modules/net_vpc/test_plan_psa.py b/tests/modules/net_vpc/test_plan_psa.py deleted file mode 100644 index ad26e559e..000000000 --- a/tests/modules/net_vpc/test_plan_psa.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2022 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 yaml - - -def test_simple(generic_plan_validator): - generic_plan_validator("modules/net-vpc", 'psa_simple.yaml', - ['common.tfvars', 'psa_simple.tfvars']) - - -def test_routes_export(generic_plan_validator): - generic_plan_validator("modules/net-vpc", 'psa_routes_export.yaml', - ['common.tfvars', 'psa_routes_export.tfvars']) - - -def test_routes_import(generic_plan_validator): - generic_plan_validator("modules/net-vpc", 'psa_routes_import.yaml', - ['common.tfvars', 'psa_routes_import.tfvars']) - - -def test_routes_import_export(generic_plan_validator): - generic_plan_validator("modules/net-vpc", 'psa_routes_import_export.yaml', - ['common.tfvars', 'psa_routes_import_export.tfvars']) diff --git a/tests/modules/net_vpc/test_routes.py b/tests/modules/net_vpc/test_routes.py new file mode 100644 index 000000000..8ee81ad4e --- /dev/null +++ b/tests/modules/net_vpc/test_routes.py @@ -0,0 +1,47 @@ +# Copyright 2022 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 pytest + +_route_parameters = [('gateway', 'global/gateways/default-internet-gateway'), + ('instance', 'zones/europe-west1-b/test'), + ('ip', '192.168.0.128'), + ('ilb', 'regions/europe-west1/forwardingRules/test'), + ('vpn_tunnel', 'regions/europe-west1/vpnTunnels/foo')] + + +@pytest.mark.parametrize('next_hop_type,next_hop', _route_parameters) +def test_vpc_routes(generic_plan_summary, next_hop_type, next_hop): + 'Test vpc routes.' + + var_routes = '''{ + next-hop = { + dest_range = "192.168.128.0/24" + tags = null + next_hop_type = "%s" + next_hop = "%s" + } + gateway = { + dest_range = "0.0.0.0/0", + priority = 100 + tags = ["tag-a"] + next_hop_type = "gateway", + next_hop = "global/gateways/default-internet-gateway" + } + }''' % (next_hop_type, next_hop) + summary = generic_plan_summary('modules/net-vpc', ['common.tfvars'], + routes=var_routes) + assert len(summary.values) == 3 + route = summary.values[f'google_compute_route.{next_hop_type}["next-hop"]'] + assert route[f'next_hop_{next_hop_type}'] == next_hop diff --git a/tests/modules/net_vpc/tftest.psa.yaml b/tests/modules/net_vpc/tftest.psa.yaml new file mode 100644 index 000000000..1d59c861f --- /dev/null +++ b/tests/modules/net_vpc/tftest.psa.yaml @@ -0,0 +1,22 @@ +module: modules/net-vpc + +tests: + psa_simple: + tfvars: + - common.tfvars + - psa_simple.tfvars + + psa_routes_export: + tfvars: + - common.tfvars + - psa_routes_export.tfvars + + psa_routes_import: + tfvars: + - common.tfvars + - psa_routes_import.tfvars + + psa_routes_import_export: + tfvars: + - common.tfvars + - psa_routes_import_export.tfvars diff --git a/tests/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml new file mode 100644 index 000000000..387ca9ee0 --- /dev/null +++ b/tests/modules/net_vpc/tftest.yaml @@ -0,0 +1,27 @@ +module: modules/net-vpc + +tests: + simple: + tfvars: + - common.tfvars + inventory: + - simple.yaml + + subnets: + tfvars: + - common.tfvars + - subnets.tfvars + + peering: + tfvars: + - common.tfvars + - peering.tfvars + inventory: + - peering.yaml + + shared_vpc: + tfvars: + - common.tfvars + - shared_vpc.tfvars + inventory: + - shared_vpc.yaml From 188ad23035f84a27c9bd50d6558136132a1b2fb2 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 2 Dec 2022 16:26:44 +0100 Subject: [PATCH 13/28] Add tests for subnet factory --- tests/conftest.py | 78 +++++++++---------- .../{fixture => }/data/factory-subnet.yaml | 0 .../{fixture => }/data/factory-subnet2.yaml | 0 tests/modules/net_vpc/factory.tfvars | 1 + tests/modules/net_vpc/factory.yaml | 30 +++++++ tests/modules/net_vpc/test_plan_subnets.py | 35 --------- tests/modules/net_vpc/tftest.psa.yaml | 22 ------ tests/modules/net_vpc/tftest.yaml | 31 ++++++-- 8 files changed, 96 insertions(+), 101 deletions(-) rename tests/modules/net_vpc/{fixture => }/data/factory-subnet.yaml (100%) rename tests/modules/net_vpc/{fixture => }/data/factory-subnet2.yaml (100%) create mode 100644 tests/modules/net_vpc/factory.tfvars create mode 100644 tests/modules/net_vpc/factory.yaml delete mode 100644 tests/modules/net_vpc/test_plan_subnets.py delete mode 100644 tests/modules/net_vpc/tftest.psa.yaml diff --git a/tests/conftest.py b/tests/conftest.py index fc2ea4aa3..d519c3d01 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,7 @@ # 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. -"Shared fixtures" +'Shared fixtures.' import collections import inspect @@ -32,18 +32,18 @@ BASEDIR = os.path.dirname(os.path.dirname(__file__)) @pytest.fixture(scope='session') def _plan_runner(): - "Returns a function to run Terraform plan on a fixture." + 'Return a function to run Terraform plan on a fixture.' def run_plan(fixture_path=None, extra_files=None, tf_var_file=None, targets=None, refresh=True, tmpdir=True, **tf_vars): - "Runs Terraform plan and returns parsed output." + 'Run Terraform plan and returns parsed output.' if fixture_path is None: # find out the fixture directory from the caller's directory caller = inspect.stack()[2] - fixture_path = os.path.join(os.path.dirname(caller.filename), "fixture") + fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture') fixture_parent = os.path.dirname(fixture_path) - fixture_prefix = os.path.basename(fixture_path) + "_" + fixture_prefix = os.path.basename(fixture_path) + '_' with tempfile.TemporaryDirectory(prefix=fixture_prefix, dir=fixture_parent) as tmp_path: # copy fixture to a temporary directory so we can execute @@ -62,11 +62,11 @@ def _plan_runner(): @pytest.fixture(scope='session') def plan_runner(_plan_runner): - "Returns a function to run Terraform plan on a module fixture." + 'Return a function to run Terraform plan on a module fixture.' def run_plan(fixture_path=None, extra_files=None, tf_var_file=None, targets=None, **tf_vars): - "Runs Terraform plan and returns plan and module resources." + 'Run Terraform plan and returns plan and module resources.' plan = _plan_runner(fixture_path, extra_files=extra_files, tf_var_file=tf_var_file, targets=targets, **tf_vars) # skip the fixture @@ -78,11 +78,11 @@ def plan_runner(_plan_runner): @pytest.fixture(scope='session') def e2e_plan_runner(_plan_runner): - "Returns a function to run Terraform plan on an end-to-end fixture." + 'Return a function to run Terraform plan on an end-to-end fixture.' def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True, include_bare_resources=False, **tf_vars): - "Runs Terraform plan on an end-to-end module using defaults, returns data." + 'Run Terraform plan on an end-to-end module using defaults, returns data.' plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, refresh=refresh, **tf_vars) # skip the fixture @@ -100,8 +100,10 @@ def e2e_plan_runner(_plan_runner): @pytest.fixture(scope='session') def recursive_e2e_plan_runner(_plan_runner): - """Plan runner for end-to-end root module, returns total number of - (nested) modules and resources""" + """ + Plan runner for end-to-end root module, returns total number of + (nested) modules and resources + """ def walk_plan(node, modules, resources): new_modules = node.get('child_modules', []) @@ -113,7 +115,7 @@ def recursive_e2e_plan_runner(_plan_runner): def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True, include_bare_resources=False, compute_sums=True, tmpdir=True, **tf_vars): - "Runs Terraform plan on a root module using defaults, returns data." + 'Run Terraform plan on a root module using defaults, returns data.' plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, refresh=refresh, tmpdir=tmpdir, **tf_vars) modules = [] @@ -126,17 +128,17 @@ def recursive_e2e_plan_runner(_plan_runner): @pytest.fixture(scope='session') def apply_runner(): - "Returns a function to run Terraform apply on a fixture." + 'Return a function to run Terraform apply on a fixture.' def run_apply(fixture_path=None, **tf_vars): - "Runs Terraform plan and returns parsed output." + 'Run Terraform plan and returns parsed output.' if fixture_path is None: # find out the fixture directory from the caller's directory caller = inspect.stack()[1] - fixture_path = os.path.join(os.path.dirname(caller.filename), "fixture") + fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture') fixture_parent = os.path.dirname(fixture_path) - fixture_prefix = os.path.basename(fixture_path) + "_" + fixture_prefix = os.path.basename(fixture_path) + '_' with tempfile.TemporaryDirectory(prefix=fixture_prefix, dir=fixture_parent) as tmp_path: @@ -160,7 +162,8 @@ def basedir(): def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, **tf_vars): - '''Run a Terraform plan on the module located at `module_path`.\ + """ + Run a Terraform plan on the module located at `module_path`. - module_path: terraform root module to run. Can be an absolute path or relative to the root of the repository @@ -187,9 +190,7 @@ def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, Consult [1] for mode details on the structure of values and outputs [1] https://developer.hashicorp.com/terraform/internals/json-format - - ''' - + """ module_path = Path(BASEDIR) / module_path # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not @@ -202,17 +203,16 @@ def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, test_path = Path(tmp_path) shutil.copytree(module_path, test_path, dirs_exist_ok=True) - # if we're copying the module, we might as well remove any - # files and directories from the test directory that are - # automatically read by terraform. Useful to avoid surprises - # surprises if, for example, you have an active fast - # deployment with links to configs) + # if we're copying the module, we might as well remove any files + # and directories from the test directory that are automatically + # read by terraform. Useful to avoid surprises if, for example, + # you have an active fast deployment with links to configs) autopaths = itertools.chain( - test_path.glob("*.auto.tfvars"), - test_path.glob("*.auto.tfvars.json"), - test_path.glob("terraform.tfstate*"), - test_path.glob("terraform.tfvars"), - test_path.glob(".terraform"), + test_path.glob('*.auto.tfvars'), + test_path.glob('*.auto.tfvars.json'), + test_path.glob('terraform.tfstate*'), + test_path.glob('terraform.tfvars'), + test_path.glob('.terraform'), # any symlinks? ) for p in autopaths: @@ -256,7 +256,7 @@ def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, @pytest.fixture def generic_plan_summary(request): - 'Returns a function to generate a PlanSummary' + 'Return a function to generate a PlanSummary.' def inner(module_path, tf_var_files=None, basedir=None, **tf_vars): if basedir is None: @@ -332,8 +332,8 @@ def _generic_plan_validator(module_path, inventory_paths, tf_var_files=None, @pytest.fixture -def generic_plan_validator(generic_plan_summary, request): - 'Return a function that builds a PlanSummary and compares it to an yaml inventory' +def generic_plan_validator(request): + 'Return a function that builds a PlanSummary and compares it to an yaml inventory.' def inner(module_path, inventory_paths, tf_var_files=None, basedir=None, **tf_vars): @@ -346,11 +346,11 @@ def generic_plan_validator(generic_plan_summary, request): def pytest_collect_file(parent, file_path): - if file_path.suffix == ".yaml" and file_path.name.startswith("tftest"): - return YamlFile.from_parent(parent, path=file_path) + if file_path.suffix == '.yaml' and file_path.name.startswith('tftest'): + return FabricTestFile.from_parent(parent, path=file_path) -class YamlFile(pytest.File): +class FabricTestFile(pytest.File): def collect(self): raw = yaml.safe_load(self.path.open()) @@ -358,11 +358,11 @@ class YamlFile(pytest.File): for test_name, spec in raw['tests'].items(): inventory = spec.get('inventory', f'{test_name}.yaml') tfvars = spec['tfvars'] - yield YamlItem.from_parent(self, name=test_name, module=module, - inventory=inventory, tfvars=tfvars) + yield FabricTestItem.from_parent(self, name=test_name, module=module, + inventory=inventory, tfvars=tfvars) -class YamlItem(pytest.Item): +class FabricTestItem(pytest.Item): def __init__(self, name, parent, module, inventory, tfvars): super().__init__(name, parent) diff --git a/tests/modules/net_vpc/fixture/data/factory-subnet.yaml b/tests/modules/net_vpc/data/factory-subnet.yaml similarity index 100% rename from tests/modules/net_vpc/fixture/data/factory-subnet.yaml rename to tests/modules/net_vpc/data/factory-subnet.yaml diff --git a/tests/modules/net_vpc/fixture/data/factory-subnet2.yaml b/tests/modules/net_vpc/data/factory-subnet2.yaml similarity index 100% rename from tests/modules/net_vpc/fixture/data/factory-subnet2.yaml rename to tests/modules/net_vpc/data/factory-subnet2.yaml diff --git a/tests/modules/net_vpc/factory.tfvars b/tests/modules/net_vpc/factory.tfvars new file mode 100644 index 000000000..8c4d4a28c --- /dev/null +++ b/tests/modules/net_vpc/factory.tfvars @@ -0,0 +1 @@ +data_folder = "../../tests/modules/net_vpc/data" diff --git a/tests/modules/net_vpc/factory.yaml b/tests/modules/net_vpc/factory.yaml new file mode 100644 index 000000000..e38adfd11 --- /dev/null +++ b/tests/modules/net_vpc/factory.yaml @@ -0,0 +1,30 @@ +values: + google_compute_subnetwork.subnetwork["europe-west1/factory-subnet"]: + description: 'Sample description' + ip_cidr_range: '10.128.0.0/24' + ipv6_access_type: null + log_config: [] + name: 'factory-subnet' + private_ip_google_access: false + project: 'test-project' + region: 'europe-west1' + role: null + secondary_ip_range: + - ip_cidr_range: '192.168.128.0/24' + range_name: 'secondary-range-a' + google_compute_subnetwork.subnetwork["europe-west4/factory-subnet2"]: + description: 'Sample description' + ip_cidr_range: '10.129.0.0/24' + log_config: [] + name: 'factory-subnet2' + private_ip_google_access: true + project: 'test-project' + region: 'europe-west4' + role: null + secondary_ip_range: [] + + # FIXME: should we have some bindings here? + +counts: + google_compute_network: 1 + google_compute_subnetwork: 2 diff --git a/tests/modules/net_vpc/test_plan_subnets.py b/tests/modules/net_vpc/test_plan_subnets.py deleted file mode 100644 index f9af768f3..000000000 --- a/tests/modules/net_vpc/test_plan_subnets.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2022 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_FOLDER = "data" - -import yaml -from pathlib import Path - - -def test_subnet_factory(plan_runner): - "Test subnet factory." - _, resources = plan_runner(data_folder=DATA_FOLDER) - assert len(resources) == 3 - subnets = [ - r['values'] for r in resources if r['type'] == 'google_compute_subnetwork' - ] - assert {s['name'] for s in subnets} == {'factory-subnet', 'factory-subnet2'} - assert {len(s['secondary_ip_range']) for s in subnets} == {0, 1} - assert {s['private_ip_google_access'] for s in subnets} == {True, False} - - -def test_subnets(generic_plan_validator): - generic_plan_validator("modules/net-vpc", 'subnets.yaml', - ['common.tfvars', 'subnets.tfvars']) diff --git a/tests/modules/net_vpc/tftest.psa.yaml b/tests/modules/net_vpc/tftest.psa.yaml deleted file mode 100644 index 1d59c861f..000000000 --- a/tests/modules/net_vpc/tftest.psa.yaml +++ /dev/null @@ -1,22 +0,0 @@ -module: modules/net-vpc - -tests: - psa_simple: - tfvars: - - common.tfvars - - psa_simple.tfvars - - psa_routes_export: - tfvars: - - common.tfvars - - psa_routes_export.tfvars - - psa_routes_import: - tfvars: - - common.tfvars - - psa_routes_import.tfvars - - psa_routes_import_export: - tfvars: - - common.tfvars - - psa_routes_import_export.tfvars diff --git a/tests/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml index 387ca9ee0..07caeb40a 100644 --- a/tests/modules/net_vpc/tftest.yaml +++ b/tests/modules/net_vpc/tftest.yaml @@ -4,8 +4,6 @@ tests: simple: tfvars: - common.tfvars - inventory: - - simple.yaml subnets: tfvars: @@ -16,12 +14,35 @@ tests: tfvars: - common.tfvars - peering.tfvars - inventory: - - peering.yaml shared_vpc: tfvars: - common.tfvars - shared_vpc.tfvars + + factory: + tfvars: + - common.tfvars + - factory.tfvars inventory: - - shared_vpc.yaml + - factory.yaml + + psa_simple: + tfvars: + - common.tfvars + - psa_simple.tfvars + + psa_routes_export: + tfvars: + - common.tfvars + - psa_routes_export.tfvars + + psa_routes_import: + tfvars: + - common.tfvars + - psa_routes_import.tfvars + + psa_routes_import_export: + tfvars: + - common.tfvars + - psa_routes_import_export.tfvars From b4d3aa205564050c537c05d8720642cc08fac987 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 2 Dec 2022 20:07:15 +0100 Subject: [PATCH 14/28] Migrate organizations tests --- tests/conftest.py | 32 +- tests/fast/stages/s00_bootstrap/simple.yaml | 705 ------------------ tests/fast/stages/s00_bootstrap/test_plan.py | 18 - tests/modules/conftest.py | 6 +- .../modules/folder/test_plan_org_policies.py | 5 +- tests/modules/net_vpc/test_routes.py | 3 +- .../modules/organization/audit_config.tfvars | 6 + tests/modules/organization/audit_config.yaml | 2 + tests/modules/organization/common.tfvars | 1 + .../{fixture => }/data/firewall-cidrs.yaml | 0 .../{fixture => }/data/firewall-rules.yaml | 0 .../organization/firewall_policies.tfvars | 45 ++ .../organization/firewall_policies.yaml | 59 ++ .../firewall_policies_factory.tfvars | 5 + .../firewall_policies_factory.yaml | 47 ++ .../firewall_policies_factory_combined.yaml | 13 + tests/modules/organization/iam.tfvars | 18 + tests/modules/organization/iam.yaml | 30 + .../modules/organization/iam_additive.tfvars | 4 + tests/modules/organization/iam_additive.yaml | 17 + tests/modules/organization/logging.tfvars | 29 + tests/modules/organization/logging.yaml | 72 ++ .../organization/logging_exclusions.tfvars | 4 + .../organization/logging_exclusions.yaml | 16 + ...etwork_tags.tfvars => network_tags.tfvars} | 0 ...ean.tfvars => org_policies_boolean.tfvars} | 0 .../organization/org_policies_boolean.yaml | 39 + ...=> org_policies_custom_constraints.tfvars} | 0 .../org_policies_custom_constraints.yaml | 23 + ...s-list.tfvars => org_policies_list.tfvars} | 1 + .../organization/org_policies_list.yaml | 71 ++ ...ource_tags.tfvars => resource_tags.tfvars} | 0 tests/modules/organization/tags.yaml | 62 ++ tests/modules/organization/test_plan.py | 62 -- .../organization/test_plan_firewall.py | 130 ---- .../modules/organization/test_plan_logging.py | 126 ---- .../organization/test_plan_org_policies.py | 64 +- tests/modules/organization/test_plan_tags.py | 52 -- tests/modules/organization/tftest.yaml | 66 ++ .../modules/project/test_plan_org_policies.py | 5 +- 40 files changed, 685 insertions(+), 1153 deletions(-) delete mode 100644 tests/fast/stages/s00_bootstrap/simple.yaml delete mode 100644 tests/fast/stages/s00_bootstrap/test_plan.py create mode 100644 tests/modules/organization/audit_config.tfvars create mode 100644 tests/modules/organization/audit_config.yaml create mode 100644 tests/modules/organization/common.tfvars rename tests/modules/organization/{fixture => }/data/firewall-cidrs.yaml (100%) rename tests/modules/organization/{fixture => }/data/firewall-rules.yaml (100%) create mode 100644 tests/modules/organization/firewall_policies.tfvars create mode 100644 tests/modules/organization/firewall_policies.yaml create mode 100644 tests/modules/organization/firewall_policies_factory.tfvars create mode 100644 tests/modules/organization/firewall_policies_factory.yaml create mode 100644 tests/modules/organization/firewall_policies_factory_combined.yaml create mode 100644 tests/modules/organization/iam.tfvars create mode 100644 tests/modules/organization/iam.yaml create mode 100644 tests/modules/organization/iam_additive.tfvars create mode 100644 tests/modules/organization/iam_additive.yaml create mode 100644 tests/modules/organization/logging.tfvars create mode 100644 tests/modules/organization/logging.yaml create mode 100644 tests/modules/organization/logging_exclusions.tfvars create mode 100644 tests/modules/organization/logging_exclusions.yaml rename tests/modules/organization/{fixture/test.network_tags.tfvars => network_tags.tfvars} (100%) rename tests/modules/organization/{fixture/test.orgpolicies-boolean.tfvars => org_policies_boolean.tfvars} (100%) create mode 100644 tests/modules/organization/org_policies_boolean.yaml rename tests/modules/organization/{fixture/test.orgpolicy-custom-constraints.tfvars => org_policies_custom_constraints.tfvars} (100%) create mode 100644 tests/modules/organization/org_policies_custom_constraints.yaml rename tests/modules/organization/{fixture/test.orgpolicies-list.tfvars => org_policies_list.tfvars} (96%) create mode 100644 tests/modules/organization/org_policies_list.yaml rename tests/modules/organization/{fixture/test.resource_tags.tfvars => resource_tags.tfvars} (100%) create mode 100644 tests/modules/organization/tags.yaml delete mode 100644 tests/modules/organization/test_plan.py delete mode 100644 tests/modules/organization/test_plan_firewall.py delete mode 100644 tests/modules/organization/test_plan_logging.py delete mode 100644 tests/modules/organization/test_plan_tags.py create mode 100644 tests/modules/organization/tftest.yaml diff --git a/tests/conftest.py b/tests/conftest.py index d519c3d01..abd05dbd1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -160,8 +160,7 @@ def basedir(): return BASEDIR -def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, - **tf_vars): +def _generic_plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): """ Run a Terraform plan on the module located at `module_path`. @@ -169,8 +168,7 @@ def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, path or relative to the root of the repository - basedir: directory root to use for relative paths in - tf_var_files. If None, then paths are relative to the calling - test function + tf_var_files. - tf_var_files: set of terraform variable files (tfvars) to pass in to terraform @@ -227,7 +225,7 @@ def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, binary = os.environ.get('TERRAFORM', 'terraform') tf = tftest.TerraformTest(test_path, binary=binary) tf.setup(upgrade=True) - tf_var_files = [basedir / x for x in tf_var_files or []] + tf_var_files = [(basedir / x).resolve() for x in tf_var_files or []] plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, tf_vars=tf_vars) @@ -258,16 +256,17 @@ def _generic_plan_summary(module_path, tf_var_files=None, basedir=None, def generic_plan_summary(request): 'Return a function to generate a PlanSummary.' - def inner(module_path, tf_var_files=None, basedir=None, **tf_vars): + def inner(module_path, basedir=None, tf_var_files=None, **tf_vars): if basedir is None: basedir = Path(request.fspath).parent - return _generic_plan_summary(module_path, tf_var_files, basedir, **tf_vars) + return _generic_plan_summary(module_path=module_path, basedir=basedir, + tf_var_files=tf_var_files, **tf_vars) return inner -def _generic_plan_validator(module_path, inventory_paths, tf_var_files=None, - basedir=None, **tf_vars): +def _generic_plan_validator(module_path, inventory_paths, basedir, + tf_var_files=None, **tf_vars): summary = _generic_plan_summary(module_path=module_path, tf_var_files=tf_var_files, basedir=basedir, **tf_vars) @@ -335,12 +334,14 @@ def _generic_plan_validator(module_path, inventory_paths, tf_var_files=None, def generic_plan_validator(request): 'Return a function that builds a PlanSummary and compares it to an yaml inventory.' - def inner(module_path, inventory_paths, tf_var_files=None, basedir=None, + def inner(module_path, inventory_paths, basedir=None, tf_var_files=None, **tf_vars): if basedir is None: basedir = Path(request.fspath).parent - return _generic_plan_validator(module_path, inventory_paths, tf_var_files, - basedir, **tf_vars) + return _generic_plan_validator(module_path=module_path, + inventory_paths=inventory_paths, + basedir=basedir, tf_var_files=tf_var_paths, + **tf_vars) return inner @@ -362,6 +363,9 @@ class FabricTestFile(pytest.File): inventory=inventory, tfvars=tfvars) +from icecream import ic + + class FabricTestItem(pytest.Item): def __init__(self, name, parent, module, inventory, tfvars): @@ -371,8 +375,8 @@ class FabricTestItem(pytest.Item): self.tfvars = tfvars def runtest(self): - _generic_plan_validator(self.module, self.inventory, self.tfvars, - self.parent.path.parent) + s = _generic_plan_validator(self.module, self.inventory, + self.parent.path.parent, self.tfvars) def reportinfo(self): return self.path, None, self.name diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml deleted file mode 100644 index 3f2f01506..000000000 --- a/tests/fast/stages/s00_bootstrap/simple.yaml +++ /dev/null @@ -1,705 +0,0 @@ -# TODO: missing all local_file and gcs objects -values: - google_organization_iam_binding.org_admin_delegated: - condition: - - description: Automation service account delegated grants. - expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/billing.admin','roles/billing.costsManager','roles/billing.user']) - title: automation_sa_delegated_grants - members: - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: organizations/123456789012/roles/organizationIamAdmin - module.automation-project.data.google_bigquery_default_service_account.bq_sa[0]: - project: fast-prod-iac-core-0 - module.automation-project.data.google_storage_project_service_account.gcs_sa[0]: - project: fast-prod-iac-core-0 - user_project: null - module.automation-project.google_project.project[0]: - auto_create_network: false - billing_account: 000000-111111-222222 - folder_id: null - labels: null - name: fast-prod-iac-core-0 - org_id: '123456789012' - project_id: fast-prod-iac-core-0 - skip_delete: false - module.automation-project.google_project_iam_binding.authoritative["roles/cloudbuild.builds.editor"]: - condition: [] - members: - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - project: fast-prod-iac-core-0 - role: roles/cloudbuild.builds.editor - module.automation-project.google_project_iam_binding.authoritative["roles/iam.serviceAccountAdmin"]: - condition: [] - members: - - group:gcp-devops@fast.example.com - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - project: fast-prod-iac-core-0 - role: roles/iam.serviceAccountAdmin - module.automation-project.google_project_iam_binding.authoritative["roles/iam.serviceAccountTokenCreator"]: - condition: [] - members: - - group:gcp-devops@fast.example.com - - group:gcp-organization-admins@fast.example.com - project: fast-prod-iac-core-0 - role: roles/iam.serviceAccountTokenCreator - module.automation-project.google_project_iam_binding.authoritative["roles/iam.workloadIdentityPoolAdmin"]: - condition: [] - members: - - group:gcp-organization-admins@fast.example.com - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - project: fast-prod-iac-core-0 - role: roles/iam.workloadIdentityPoolAdmin - module.automation-project.google_project_iam_binding.authoritative["roles/owner"]: - condition: [] - members: - - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - project: fast-prod-iac-core-0 - role: roles/owner - module.automation-project.google_project_iam_binding.authoritative["roles/source.admin"]: - condition: [] - members: - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - project: fast-prod-iac-core-0 - role: roles/source.admin - module.automation-project.google_project_iam_binding.authoritative["roles/storage.admin"]: - condition: [] - members: - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - project: fast-prod-iac-core-0 - role: roles/storage.admin - module.automation-project.google_project_iam_member.servicenetworking[0]: - condition: [] - project: fast-prod-iac-core-0 - role: roles/servicenetworking.serviceAgent - module.automation-project.google_project_service.project_services["accesscontextmanager.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: accesscontextmanager.googleapis.com - module.automation-project.google_project_service.project_services["bigquery.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: bigquery.googleapis.com - module.automation-project.google_project_service.project_services["bigqueryreservation.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: bigqueryreservation.googleapis.com - module.automation-project.google_project_service.project_services["bigquerystorage.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: bigquerystorage.googleapis.com - module.automation-project.google_project_service.project_services["billingbudgets.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: billingbudgets.googleapis.com - module.automation-project.google_project_service.project_services["cloudbilling.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: cloudbilling.googleapis.com - module.automation-project.google_project_service.project_services["cloudbuild.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: cloudbuild.googleapis.com - module.automation-project.google_project_service.project_services["cloudkms.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: cloudkms.googleapis.com - module.automation-project.google_project_service.project_services["cloudresourcemanager.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: cloudresourcemanager.googleapis.com - module.automation-project.google_project_service.project_services["compute.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: compute.googleapis.com - module.automation-project.google_project_service.project_services["container.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: container.googleapis.com - module.automation-project.google_project_service.project_services["essentialcontacts.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: essentialcontacts.googleapis.com - module.automation-project.google_project_service.project_services["iam.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: iam.googleapis.com - module.automation-project.google_project_service.project_services["iamcredentials.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: iamcredentials.googleapis.com - module.automation-project.google_project_service.project_services["orgpolicy.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: orgpolicy.googleapis.com - module.automation-project.google_project_service.project_services["pubsub.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: pubsub.googleapis.com - module.automation-project.google_project_service.project_services["servicenetworking.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: servicenetworking.googleapis.com - module.automation-project.google_project_service.project_services["serviceusage.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: serviceusage.googleapis.com - module.automation-project.google_project_service.project_services["sourcerepo.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: sourcerepo.googleapis.com - module.automation-project.google_project_service.project_services["stackdriver.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: stackdriver.googleapis.com - module.automation-project.google_project_service.project_services["storage-component.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: storage-component.googleapis.com - module.automation-project.google_project_service.project_services["storage.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: storage.googleapis.com - module.automation-project.google_project_service.project_services["sts.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-iac-core-0 - service: sts.googleapis.com - module.automation-project.google_project_service_identity.jit_si["pubsub.googleapis.com"]: - project: fast-prod-iac-core-0 - service: pubsub.googleapis.com - module.automation-project.google_project_service_identity.servicenetworking[0]: - project: fast-prod-iac-core-0 - service: servicenetworking.googleapis.com - module.automation-tf-bootstrap-gcs.google_storage_bucket.bucket: - cors: [] - custom_placement_config: [] - default_event_based_hold: null - encryption: [] - force_destroy: false - labels: null - lifecycle_rule: [] - location: EU - logging: [] - name: fast-prod-iac-core-bootstrap-0 - project: fast-prod-iac-core-0 - requester_pays: null - retention_policy: [] - storage_class: MULTI_REGIONAL - uniform_bucket_level_access: true - versioning: - - enabled: true - website: [] - module.automation-tf-bootstrap-sa.google_service_account.service_account[0]: - account_id: fast-prod-bootstrap-0 - description: null - disabled: false - display_name: Terraform organization bootstrap service account. - project: fast-prod-iac-core-0 - module.automation-tf-bootstrap-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: - condition: [] - members: null - role: roles/iam.serviceAccountTokenCreator - module.automation-tf-bootstrap-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: - bucket: fast-prod-iac-core-outputs-0 - condition: [] - role: roles/storage.admin - module.automation-tf-cicd-gcs.google_storage_bucket.bucket: - cors: [] - custom_placement_config: [] - default_event_based_hold: null - encryption: [] - force_destroy: false - labels: null - lifecycle_rule: [] - location: EU - logging: [] - name: fast-prod-iac-core-cicd-0 - project: fast-prod-iac-core-0 - requester_pays: null - retention_policy: [] - storage_class: MULTI_REGIONAL - uniform_bucket_level_access: true - versioning: - - enabled: true - website: [] - module.automation-tf-cicd-gcs.google_storage_bucket_iam_binding.bindings["roles/storage.objectAdmin"]: - bucket: fast-prod-iac-core-cicd-0 - condition: [] - members: - - serviceAccount:fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com - role: roles/storage.objectAdmin - module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]: - account_id: fast-prod-cicd-0 - description: null - disabled: false - display_name: Terraform stage 1 CICD service account. - project: fast-prod-iac-core-0 - module.automation-tf-cicd-provisioning-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: - condition: [] - members: null - role: roles/iam.serviceAccountTokenCreator - module.automation-tf-cicd-provisioning-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: - bucket: fast-prod-iac-core-outputs-0 - condition: [] - role: roles/storage.admin - module.automation-tf-output-gcs.google_storage_bucket.bucket: - cors: [] - custom_placement_config: [] - default_event_based_hold: null - encryption: [] - force_destroy: false - labels: null - lifecycle_rule: [] - location: EU - logging: [] - name: fast-prod-iac-core-outputs-0 - project: fast-prod-iac-core-0 - requester_pays: null - retention_policy: [] - storage_class: MULTI_REGIONAL - uniform_bucket_level_access: true - versioning: - - enabled: true - website: [] - module.automation-tf-resman-gcs.google_storage_bucket.bucket: - cors: [] - custom_placement_config: [] - default_event_based_hold: null - encryption: [] - force_destroy: false - labels: null - lifecycle_rule: [] - location: EU - logging: [] - name: fast-prod-iac-core-resman-0 - project: fast-prod-iac-core-0 - requester_pays: null - retention_policy: [] - storage_class: MULTI_REGIONAL - uniform_bucket_level_access: true - versioning: - - enabled: true - website: [] - module.automation-tf-resman-gcs.google_storage_bucket_iam_binding.bindings["roles/storage.objectAdmin"]: - bucket: fast-prod-iac-core-resman-0 - condition: [] - members: - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - role: roles/storage.objectAdmin - module.automation-tf-resman-sa.google_service_account.service_account[0]: - account_id: fast-prod-resman-0 - description: null - disabled: false - display_name: Terraform stage 1 resman service account. - project: fast-prod-iac-core-0 - module.automation-tf-resman-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: - condition: [] - members: null - role: roles/iam.serviceAccountTokenCreator - module.automation-tf-resman-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: - bucket: fast-prod-iac-core-outputs-0 - condition: [] - role: roles/storage.admin - module.billing-export-dataset[0].google_bigquery_dataset.default: - dataset_id: billing_export - default_encryption_configuration: [] - default_partition_expiration_ms: null - default_table_expiration_ms: null - delete_contents_on_destroy: false - description: Terraform managed. - friendly_name: Billing export. - labels: null - location: EU - project: fast-prod-billing-exp-0 - module.billing-export-project[0].data.google_bigquery_default_service_account.bq_sa[0]: - project: fast-prod-billing-exp-0 - module.billing-export-project[0].data.google_storage_project_service_account.gcs_sa[0]: - project: fast-prod-billing-exp-0 - user_project: null - module.billing-export-project[0].google_project.project[0]: - auto_create_network: false - billing_account: 000000-111111-222222 - folder_id: null - labels: null - name: fast-prod-billing-exp-0 - org_id: '123456789012' - project_id: fast-prod-billing-exp-0 - skip_delete: false - module.billing-export-project[0].google_project_iam_binding.authoritative["roles/owner"]: - condition: [] - members: - - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - project: fast-prod-billing-exp-0 - role: roles/owner - module.billing-export-project[0].google_project_service.project_services["bigquery.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-billing-exp-0 - service: bigquery.googleapis.com - module.billing-export-project[0].google_project_service.project_services["bigquerydatatransfer.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-billing-exp-0 - service: bigquerydatatransfer.googleapis.com - module.billing-export-project[0].google_project_service.project_services["storage.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-billing-exp-0 - service: storage.googleapis.com - module.log-export-dataset[0].google_bigquery_dataset.default: - dataset_id: audit_export - default_encryption_configuration: [] - default_partition_expiration_ms: null - default_table_expiration_ms: null - delete_contents_on_destroy: false - description: Terraform managed. - friendly_name: Audit logs export. - labels: null - location: EU - project: fast-prod-audit-logs-0 - module.log-export-project.data.google_bigquery_default_service_account.bq_sa[0]: - project: fast-prod-audit-logs-0 - module.log-export-project.data.google_storage_project_service_account.gcs_sa[0]: - project: fast-prod-audit-logs-0 - user_project: null - module.log-export-project.google_project.project[0]: - auto_create_network: false - billing_account: 000000-111111-222222 - folder_id: null - labels: null - name: fast-prod-audit-logs-0 - org_id: '123456789012' - project_id: fast-prod-audit-logs-0 - skip_delete: false - module.log-export-project.google_project_iam_binding.authoritative["roles/owner"]: - condition: [] - members: - - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - project: fast-prod-audit-logs-0 - role: roles/owner - module.log-export-project.google_project_service.project_services["bigquery.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-audit-logs-0 - service: bigquery.googleapis.com - module.log-export-project.google_project_service.project_services["stackdriver.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-audit-logs-0 - service: stackdriver.googleapis.com - module.log-export-project.google_project_service.project_services["storage.googleapis.com"]: - disable_dependent_services: false - disable_on_destroy: false - project: fast-prod-audit-logs-0 - service: storage.googleapis.com - module.organization.google_bigquery_dataset_iam_member.bq-sinks-binding["audit-logs"]: - condition: [] - role: roles/bigquery.dataEditor - module.organization.google_bigquery_dataset_iam_member.bq-sinks-binding["vpc-sc"]: - condition: [] - role: roles/bigquery.dataEditor - module.organization.google_logging_organization_sink.sink["audit-logs"]: - description: audit-logs (Terraform-managed). - disabled: false - exclusions: [] - filter: logName:"/logs/cloudaudit.googleapis.com%2Factivity" OR logName:"/logs/cloudaudit.googleapis.com%2Fsystem_event" - include_children: true - name: audit-logs - org_id: '123456789012' - module.organization.google_logging_organization_sink.sink["vpc-sc"]: - description: vpc-sc (Terraform-managed). - disabled: false - exclusions: [] - filter: protoPayload.metadata.@type="type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata" - include_children: true - name: vpc-sc - org_id: '123456789012' - module.organization.google_organization_iam_binding.authoritative["roles/browser"]: - condition: [] - members: - - domain:fast.example.com - org_id: '123456789012' - role: roles/browser - module.organization.google_organization_iam_binding.authoritative["roles/cloudasset.owner"]: - condition: [] - members: - - group:gcp-network-admins@fast.example.com - - group:gcp-organization-admins@fast.example.com - - group:gcp-security-admins@fast.example.com - org_id: '123456789012' - role: roles/cloudasset.owner - module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.admin"]: - condition: [] - members: - - group:gcp-organization-admins@fast.example.com - org_id: '123456789012' - role: roles/cloudsupport.admin - module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.techSupportEditor"]: - condition: [] - members: - - group:gcp-devops@fast.example.com - - group:gcp-network-admins@fast.example.com - - group:gcp-security-admins@fast.example.com - org_id: '123456789012' - role: roles/cloudsupport.techSupportEditor - module.organization.google_organization_iam_binding.authoritative["roles/compute.osAdminLogin"]: - condition: [] - members: - - group:gcp-organization-admins@fast.example.com - org_id: '123456789012' - role: roles/compute.osAdminLogin - module.organization.google_organization_iam_binding.authoritative["roles/compute.osLoginExternalUser"]: - condition: [] - members: - - group:gcp-organization-admins@fast.example.com - org_id: '123456789012' - role: roles/compute.osLoginExternalUser - module.organization.google_organization_iam_binding.authoritative["roles/iam.securityReviewer"]: - condition: [] - members: - - group:gcp-security-admins@fast.example.com - org_id: '123456789012' - role: roles/iam.securityReviewer - module.organization.google_organization_iam_binding.authoritative["roles/logging.admin"]: - condition: [] - members: - - group:gcp-security-admins@fast.example.com - - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/logging.admin - module.organization.google_organization_iam_binding.authoritative["roles/logging.viewer"]: - condition: [] - members: - - group:gcp-devops@fast.example.com - org_id: '123456789012' - role: roles/logging.viewer - module.organization.google_organization_iam_binding.authoritative["roles/monitoring.viewer"]: - condition: [] - members: - - group:gcp-devops@fast.example.com - org_id: '123456789012' - role: roles/monitoring.viewer - module.organization.google_organization_iam_binding.authoritative["roles/owner"]: - condition: [] - members: - - group:gcp-organization-admins@fast.example.com - org_id: '123456789012' - role: roles/owner - module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.folderAdmin"]: - condition: [] - members: - - group:gcp-organization-admins@fast.example.com - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/resourcemanager.folderAdmin - module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.organizationAdmin"]: - condition: [] - members: - - group:gcp-organization-admins@fast.example.com - - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/resourcemanager.organizationAdmin - module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.projectCreator"]: - condition: [] - members: - - group:gcp-organization-admins@fast.example.com - - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/resourcemanager.projectCreator - module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.projectMover"]: - condition: [] - members: - - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/resourcemanager.projectMover - module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.tagAdmin"]: - condition: [] - members: - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/resourcemanager.tagAdmin - module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.tagUser"]: - condition: [] - members: - - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/resourcemanager.tagUser - module.organization.google_organization_iam_binding.authoritative["roles/securitycenter.admin"]: - condition: [] - members: - - group:gcp-security-admins@fast.example.com - org_id: '123456789012' - role: roles/securitycenter.admin - module.organization.google_organization_iam_custom_role.roles["organizationIamAdmin"]: - description: Terraform-managed. - org_id: '123456789012' - permissions: - - resourcemanager.organizations.get - - resourcemanager.organizations.getIamPolicy - - resourcemanager.organizations.setIamPolicy - role_id: organizationIamAdmin - stage: GA - title: Custom role organizationIamAdmin - module.organization.google_organization_iam_custom_role.roles["serviceProjectNetworkAdmin"]: - description: Terraform-managed. - org_id: '123456789012' - permissions: - - compute.globalOperations.get - - compute.networks.get - - compute.networks.updatePeering - - compute.organizations.disableXpnResource - - compute.organizations.enableXpnResource - - compute.projects.get - - compute.subnetworks.getIamPolicy - - compute.subnetworks.setIamPolicy - - dns.networks.bindPrivateDNSZone - - resourcemanager.projects.get - role_id: serviceProjectNetworkAdmin - stage: GA - title: Custom role serviceProjectNetworkAdmin - module.organization.google_organization_iam_member.additive["roles/accesscontextmanager.policyAdmin-group:gcp-security-admins@fast.example.com"]: - condition: [] - member: group:gcp-security-admins@fast.example.com - org_id: '123456789012' - role: roles/accesscontextmanager.policyAdmin - module.organization.google_organization_iam_member.additive["roles/billing.admin-group:gcp-billing-admins@fast.example.com"]: - condition: [] - member: group:gcp-billing-admins@fast.example.com - org_id: '123456789012' - role: roles/billing.admin - module.organization.google_organization_iam_member.additive["roles/billing.admin-group:gcp-organization-admins@fast.example.com"]: - condition: [] - member: group:gcp-organization-admins@fast.example.com - org_id: '123456789012' - role: roles/billing.admin - module.organization.google_organization_iam_member.additive["roles/billing.admin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: - condition: [] - member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/billing.admin - module.organization.google_organization_iam_member.additive["roles/billing.admin-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: - condition: [] - member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/billing.admin - module.organization.google_organization_iam_member.additive["roles/billing.costsManager-group:gcp-billing-admins@fast.example.com"]: - condition: [] - member: group:gcp-billing-admins@fast.example.com - org_id: '123456789012' - role: roles/billing.costsManager - module.organization.google_organization_iam_member.additive["roles/billing.costsManager-group:gcp-organization-admins@fast.example.com"]: - condition: [] - member: group:gcp-organization-admins@fast.example.com - org_id: '123456789012' - role: roles/billing.costsManager - module.organization.google_organization_iam_member.additive["roles/billing.costsManager-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: - condition: [] - member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/billing.costsManager - module.organization.google_organization_iam_member.additive["roles/billing.costsManager-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: - condition: [] - member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/billing.costsManager - module.organization.google_organization_iam_member.additive["roles/compute.orgFirewallPolicyAdmin-group:gcp-network-admins@fast.example.com"]: - condition: [] - member: group:gcp-network-admins@fast.example.com - org_id: '123456789012' - role: roles/compute.orgFirewallPolicyAdmin - module.organization.google_organization_iam_member.additive["roles/compute.xpnAdmin-group:gcp-network-admins@fast.example.com"]: - condition: [] - member: group:gcp-network-admins@fast.example.com - org_id: '123456789012' - role: roles/compute.xpnAdmin - module.organization.google_organization_iam_member.additive["roles/iam.organizationRoleAdmin-group:gcp-security-admins@fast.example.com"]: - condition: [] - member: group:gcp-security-admins@fast.example.com - org_id: '123456789012' - role: roles/iam.organizationRoleAdmin - module.organization.google_organization_iam_member.additive["roles/iam.organizationRoleAdmin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: - condition: [] - member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/iam.organizationRoleAdmin - module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-group:gcp-organization-admins@fast.example.com"]: - condition: [] - member: group:gcp-organization-admins@fast.example.com - org_id: '123456789012' - role: roles/orgpolicy.policyAdmin - module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-group:gcp-security-admins@fast.example.com"]: - condition: [] - member: group:gcp-security-admins@fast.example.com - org_id: '123456789012' - role: roles/orgpolicy.policyAdmin - module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: - condition: [] - member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - org_id: '123456789012' - role: roles/orgpolicy.policyAdmin - -counts: - google_bigquery_dataset: 2 - google_bigquery_dataset_iam_member: 2 - google_bigquery_default_service_account: 3 - google_logging_organization_sink: 2 - google_organization_iam_binding: 19 - google_organization_iam_custom_role: 2 - google_organization_iam_member: 16 - google_project: 3 - google_project_iam_binding: 9 - google_project_iam_member: 1 - google_project_service: 29 - google_project_service_identity: 2 - google_service_account: 3 - google_service_account_iam_binding: 3 - google_storage_bucket: 4 - google_storage_bucket_iam_binding: 2 - google_storage_bucket_iam_member: 3 - google_storage_bucket_object: 5 - google_storage_project_service_account: 3 - local_file: 5 - -outputs: - automation: __missing__ - billing_dataset: __missing__ - cicd_repositories: {} - custom_roles: - organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin - service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin - federated_identity: - pool: null - providers: {} - outputs_bucket: fast-prod-iac-core-outputs-0 - project_ids: - automation: fast-prod-iac-core-0 - billing-export: fast-prod-billing-exp-0 - log-export: fast-prod-audit-logs-0 - service_accounts: - bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com - cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com - resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com - tfvars: __missing__ diff --git a/tests/fast/stages/s00_bootstrap/test_plan.py b/tests/fast/stages/s00_bootstrap/test_plan.py deleted file mode 100644 index 206b394a5..000000000 --- a/tests/fast/stages/s00_bootstrap/test_plan.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2022 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. - - -def test_simple(generic_plan_validator): - generic_plan_validator("fast/stages/00-bootstrap", 'simple.yaml', - ['simple.tfvars']) diff --git a/tests/modules/conftest.py b/tests/modules/conftest.py index 41f7cd2e5..c199cff7b 100644 --- a/tests/modules/conftest.py +++ b/tests/modules/conftest.py @@ -20,11 +20,11 @@ import pytest import yaml -@pytest.fixture(scope='session') -def tfvars_to_yaml(): +@pytest.fixture() +def tfvars_to_yaml(request): def converter(source, dest, from_var, to_var=None): - p_fixture = pathlib.Path(inspect.stack()[1].filename).parent / 'fixture' + p_fixture = pathlib.Path(request.path).parent p_source = p_fixture / source if not p_source.exists(): raise ValueError(f"tfvars '{source}' not found") diff --git a/tests/modules/folder/test_plan_org_policies.py b/tests/modules/folder/test_plan_org_policies.py index d4e4559d0..8463761e8 100644 --- a/tests/modules/folder/test_plan_org_policies.py +++ b/tests/modules/folder/test_plan_org_policies.py @@ -31,13 +31,14 @@ def test_policy_list(plan_runner): def test_factory_policy_boolean(plan_runner, tfvars_to_yaml, tmp_path): dest = tmp_path / 'policies.yaml' - tfvars_to_yaml('test.orgpolicies-boolean.tfvars', dest, 'org_policies') + tfvars_to_yaml('fixture/test.orgpolicies-boolean.tfvars', dest, + 'org_policies') _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"') validate_policy_boolean(resources) def test_factory_policy_list(plan_runner, tfvars_to_yaml, tmp_path): dest = tmp_path / 'policies.yaml' - tfvars_to_yaml('test.orgpolicies-list.tfvars', dest, 'org_policies') + tfvars_to_yaml('fixture/test.orgpolicies-list.tfvars', dest, 'org_policies') _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"') validate_policy_list(resources) diff --git a/tests/modules/net_vpc/test_routes.py b/tests/modules/net_vpc/test_routes.py index 8ee81ad4e..e4df8d6bd 100644 --- a/tests/modules/net_vpc/test_routes.py +++ b/tests/modules/net_vpc/test_routes.py @@ -40,7 +40,8 @@ def test_vpc_routes(generic_plan_summary, next_hop_type, next_hop): next_hop = "global/gateways/default-internet-gateway" } }''' % (next_hop_type, next_hop) - summary = generic_plan_summary('modules/net-vpc', ['common.tfvars'], + summary = generic_plan_summary('modules/net-vpc', + tf_var_files=['common.tfvars'], routes=var_routes) assert len(summary.values) == 3 route = summary.values[f'google_compute_route.{next_hop_type}["next-hop"]'] diff --git a/tests/modules/organization/audit_config.tfvars b/tests/modules/organization/audit_config.tfvars new file mode 100644 index 000000000..b071033a1 --- /dev/null +++ b/tests/modules/organization/audit_config.tfvars @@ -0,0 +1,6 @@ +iam_audit_config = { + allServices = { + DATA_READ = [], + DATA_WRITE = ["user:me@example.org"] + } +} diff --git a/tests/modules/organization/audit_config.yaml b/tests/modules/organization/audit_config.yaml new file mode 100644 index 000000000..712478f7a --- /dev/null +++ b/tests/modules/organization/audit_config.yaml @@ -0,0 +1,2 @@ +counts: + google_organization_iam_audit_config: 1 diff --git a/tests/modules/organization/common.tfvars b/tests/modules/organization/common.tfvars new file mode 100644 index 000000000..d5f1c3d7d --- /dev/null +++ b/tests/modules/organization/common.tfvars @@ -0,0 +1 @@ +organization_id = "organizations/1234567890" diff --git a/tests/modules/organization/fixture/data/firewall-cidrs.yaml b/tests/modules/organization/data/firewall-cidrs.yaml similarity index 100% rename from tests/modules/organization/fixture/data/firewall-cidrs.yaml rename to tests/modules/organization/data/firewall-cidrs.yaml diff --git a/tests/modules/organization/fixture/data/firewall-rules.yaml b/tests/modules/organization/data/firewall-rules.yaml similarity index 100% rename from tests/modules/organization/fixture/data/firewall-rules.yaml rename to tests/modules/organization/data/firewall-rules.yaml diff --git a/tests/modules/organization/firewall_policies.tfvars b/tests/modules/organization/firewall_policies.tfvars new file mode 100644 index 000000000..603cd3a47 --- /dev/null +++ b/tests/modules/organization/firewall_policies.tfvars @@ -0,0 +1,45 @@ +firewall_policies = { + policy1 = { + allow-ingress = { + description = "" + direction = "INGRESS" + action = "allow" + priority = 100 + ranges = ["10.0.0.0/8"] + ports = { + tcp = ["22"] + } + target_service_accounts = null + target_resources = null + logging = false + } + deny-egress = { + description = "" + direction = "EGRESS" + action = "deny" + priority = 200 + ranges = ["192.168.0.0/24"] + ports = { + tcp = ["443"] + } + target_service_accounts = null + target_resources = null + logging = false + } + } + policy2 = { + allow-ingress = { + description = "" + direction = "INGRESS" + action = "allow" + priority = 100 + ranges = ["10.0.0.0/8"] + ports = { + tcp = ["22"] + } + target_service_accounts = null + target_resources = null + logging = false + } + } +} diff --git a/tests/modules/organization/firewall_policies.yaml b/tests/modules/organization/firewall_policies.yaml new file mode 100644 index 000000000..1c64ceaa3 --- /dev/null +++ b/tests/modules/organization/firewall_policies.yaml @@ -0,0 +1,59 @@ +values: + google_compute_firewall_policy.policy["policy1"]: + parent: organizations/1234567890 + short_name: policy1 + google_compute_firewall_policy.policy["policy2"]: + parent: organizations/1234567890 + short_name: policy2 + google_compute_firewall_policy_rule.rule["policy1-allow-ingress"]: + action: allow + direction: INGRESS + disabled: null + enable_logging: false + match: + - dest_ip_ranges: null + layer4_configs: + - ip_protocol: tcp + ports: + - '22' + src_ip_ranges: + - 10.0.0.0/8 + priority: 100 + target_resources: null + target_service_accounts: null + google_compute_firewall_policy_rule.rule["policy1-deny-egress"]: + action: deny + direction: EGRESS + disabled: null + enable_logging: false + match: + - dest_ip_ranges: + - 192.168.0.0/24 + layer4_configs: + - ip_protocol: tcp + ports: + - '443' + src_ip_ranges: null + priority: 200 + target_resources: null + target_service_accounts: null + google_compute_firewall_policy_rule.rule["policy2-allow-ingress"]: + action: allow + direction: INGRESS + disabled: null + enable_logging: false + match: + - dest_ip_ranges: null + layer4_configs: + - ip_protocol: tcp + ports: + - '22' + src_ip_ranges: + - 10.0.0.0/8 + priority: 100 + target_resources: null + target_service_accounts: null + +counts: + google_compute_firewall_policy: 2 + google_compute_firewall_policy_rule: 3 diff --git a/tests/modules/organization/firewall_policies_factory.tfvars b/tests/modules/organization/firewall_policies_factory.tfvars new file mode 100644 index 000000000..3e1cf1813 --- /dev/null +++ b/tests/modules/organization/firewall_policies_factory.tfvars @@ -0,0 +1,5 @@ +firewall_policy_factory = { + cidr_file = "../../tests/modules/organization/data/firewall-cidrs.yaml" + policy_name = "factory-1" + rules_file = "../../tests/modules/organization/data/firewall-rules.yaml" +} diff --git a/tests/modules/organization/firewall_policies_factory.yaml b/tests/modules/organization/firewall_policies_factory.yaml new file mode 100644 index 000000000..f5e8bd986 --- /dev/null +++ b/tests/modules/organization/firewall_policies_factory.yaml @@ -0,0 +1,47 @@ +values: + google_compute_firewall_policy.policy["factory-1"]: + description: null + parent: organizations/1234567890 + short_name: factory-1 + timeouts: null + google_compute_firewall_policy_rule.rule["factory-1-allow-admins"]: + action: allow + description: Access from the admin subnet to all subnets + direction: INGRESS + disabled: null + enable_logging: null + match: + - dest_ip_ranges: null + layer4_configs: + - ip_protocol: all + ports: [] + src_ip_ranges: + - 10.0.0.0/8 + - 172.168.0.0/12 + - 192.168.0.0/16 + priority: 1000 + target_resources: null + target_service_accounts: null + timeouts: null + google_compute_firewall_policy_rule.rule["factory-1-allow-ssh-from-iap"]: + action: allow + description: Enable SSH from IAP + direction: INGRESS + disabled: null + enable_logging: null + match: + - dest_ip_ranges: null + layer4_configs: + - ip_protocol: tcp + ports: + - '22' + src_ip_ranges: + - 35.235.240.0/20 + priority: 1002 + target_resources: null + target_service_accounts: null + timeouts: null + +counts: + google_compute_firewall_policy: 1 + google_compute_firewall_policy_rule: 2 diff --git a/tests/modules/organization/firewall_policies_factory_combined.yaml b/tests/modules/organization/firewall_policies_factory_combined.yaml new file mode 100644 index 000000000..6d3f8976b --- /dev/null +++ b/tests/modules/organization/firewall_policies_factory_combined.yaml @@ -0,0 +1,13 @@ +values: + google_compute_firewall_policy.policy["factory-1"]: {} + google_compute_firewall_policy.policy["policy1"]: {} + google_compute_firewall_policy.policy["policy2"]: {} + google_compute_firewall_policy_rule.rule["factory-1-allow-admins"]: {} + google_compute_firewall_policy_rule.rule["factory-1-allow-ssh-from-iap"]: {} + google_compute_firewall_policy_rule.rule["policy1-allow-ingress"]: {} + google_compute_firewall_policy_rule.rule["policy1-deny-egress"]: {} + google_compute_firewall_policy_rule.rule["policy2-allow-ingress"]: {} + +counts: + google_compute_firewall_policy: 3 + google_compute_firewall_policy_rule: 5 diff --git a/tests/modules/organization/iam.tfvars b/tests/modules/organization/iam.tfvars new file mode 100644 index 000000000..699631277 --- /dev/null +++ b/tests/modules/organization/iam.tfvars @@ -0,0 +1,18 @@ +group_iam = { + "owners@example.org" = [ + "roles/owner", + "roles/resourcemanager.folderAdmin" + ], + "viewers@example.org" = [ + "roles/viewer" + ] +} +iam = { + "roles/owner" = [ + "user:one@example.org", + "user:two@example.org" + ], + "roles/browser" = [ + "domain:example.org" + ] +} diff --git a/tests/modules/organization/iam.yaml b/tests/modules/organization/iam.yaml new file mode 100644 index 000000000..b735131fa --- /dev/null +++ b/tests/modules/organization/iam.yaml @@ -0,0 +1,30 @@ +values: + google_organization_iam_binding.authoritative["roles/browser"]: + condition: [] + members: + - domain:example.org + org_id: '1234567890' + role: roles/browser + google_organization_iam_binding.authoritative["roles/owner"]: + condition: [] + members: + - group:owners@example.org + - user:one@example.org + - user:two@example.org + org_id: '1234567890' + role: roles/owner + google_organization_iam_binding.authoritative["roles/resourcemanager.folderAdmin"]: + condition: [] + members: + - group:owners@example.org + org_id: '1234567890' + role: roles/resourcemanager.folderAdmin + google_organization_iam_binding.authoritative["roles/viewer"]: + condition: [] + members: + - group:viewers@example.org + org_id: '1234567890' + role: roles/viewer + +counts: + google_organization_iam_binding: 4 diff --git a/tests/modules/organization/iam_additive.tfvars b/tests/modules/organization/iam_additive.tfvars new file mode 100644 index 000000000..823d70ad3 --- /dev/null +++ b/tests/modules/organization/iam_additive.tfvars @@ -0,0 +1,4 @@ +iam = { + "user:one@example.org" = ["roles/owner"], + "user:two@example.org" = ["roles/owner", "roles/editor"] +} diff --git a/tests/modules/organization/iam_additive.yaml b/tests/modules/organization/iam_additive.yaml new file mode 100644 index 000000000..6e2a7875c --- /dev/null +++ b/tests/modules/organization/iam_additive.yaml @@ -0,0 +1,17 @@ +values: + google_organization_iam_binding.authoritative["user:one@example.org"]: + condition: [] + members: + - roles/owner + org_id: '1234567890' + role: user:one@example.org + google_organization_iam_binding.authoritative["user:two@example.org"]: + condition: [] + members: + - roles/editor + - roles/owner + org_id: '1234567890' + role: user:two@example.org + +counts: + google_organization_iam_binding: 2 diff --git a/tests/modules/organization/logging.tfvars b/tests/modules/organization/logging.tfvars new file mode 100644 index 000000000..95a272e1f --- /dev/null +++ b/tests/modules/organization/logging.tfvars @@ -0,0 +1,29 @@ +logging_sinks = { + warning = { + destination = "mybucket" + type = "storage" + filter = "severity=WARNING" + } + info = { + destination = "projects/myproject/datasets/mydataset" + type = "bigquery" + filter = "severity=INFO" + disabled = true + } + notice = { + destination = "projects/myproject/topics/mytopic" + type = "pubsub" + filter = "severity=NOTICE" + include_children = false + } + debug = { + destination = "projects/myproject/locations/global/buckets/mybucket" + type = "logging" + filter = "severity=DEBUG" + include_children = false + exclusions = { + no-compute = "logName:compute" + no-container = "logName:container" + } + } +} diff --git a/tests/modules/organization/logging.yaml b/tests/modules/organization/logging.yaml new file mode 100644 index 000000000..dea783ec3 --- /dev/null +++ b/tests/modules/organization/logging.yaml @@ -0,0 +1,72 @@ +values: + google_bigquery_dataset_iam_member.bq-sinks-binding["info"]: + condition: [] + dataset_id: mydataset + project: myproject + role: roles/bigquery.dataEditor + google_logging_organization_sink.sink["debug"]: + description: debug (Terraform-managed). + destination: logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket + disabled: false + exclusions: + - description: null + disabled: false + filter: logName:compute + name: no-compute + - description: null + disabled: false + filter: logName:container + name: no-container + filter: severity=DEBUG + include_children: false + name: debug + org_id: '1234567890' + google_logging_organization_sink.sink["info"]: + description: info (Terraform-managed). + destination: bigquery.googleapis.com/projects/myproject/datasets/mydataset + disabled: true + exclusions: [] + filter: severity=INFO + include_children: true + name: info + org_id: '1234567890' + google_logging_organization_sink.sink["notice"]: + description: notice (Terraform-managed). + destination: pubsub.googleapis.com/projects/myproject/topics/mytopic + disabled: false + exclusions: [] + filter: severity=NOTICE + include_children: false + name: notice + org_id: '1234567890' + google_logging_organization_sink.sink["warning"]: + description: warning (Terraform-managed). + destination: storage.googleapis.com/mybucket + disabled: false + exclusions: [] + filter: severity=WARNING + include_children: true + name: warning + org_id: '1234567890' + google_project_iam_member.bucket-sinks-binding["debug"]: + condition: + - expression: resource.name.endsWith('projects/myproject/locations/global/buckets/mybucket') + title: debug bucket writer + project: myproject + role: roles/logging.bucketWriter + google_pubsub_topic_iam_member.pubsub-sinks-binding["notice"]: + condition: [] + project: myproject + role: roles/pubsub.publisher + topic: mytopic + google_storage_bucket_iam_member.storage-sinks-binding["warning"]: + bucket: mybucket + condition: [] + role: roles/storage.objectCreator + +counts: + google_bigquery_dataset_iam_member: 1 + google_logging_organization_sink: 4 + google_project_iam_member: 1 + google_pubsub_topic_iam_member: 1 + google_storage_bucket_iam_member: 1 diff --git a/tests/modules/organization/logging_exclusions.tfvars b/tests/modules/organization/logging_exclusions.tfvars new file mode 100644 index 000000000..75c35604e --- /dev/null +++ b/tests/modules/organization/logging_exclusions.tfvars @@ -0,0 +1,4 @@ +logging_exclusions = { + exclusion1 = "resource.type=gce_instance" + exclusion2 = "severity=NOTICE" +} diff --git a/tests/modules/organization/logging_exclusions.yaml b/tests/modules/organization/logging_exclusions.yaml new file mode 100644 index 000000000..ded59fa54 --- /dev/null +++ b/tests/modules/organization/logging_exclusions.yaml @@ -0,0 +1,16 @@ +values: + google_logging_organization_exclusion.logging-exclusion["exclusion1"]: + description: exclusion1 (Terraform-managed). + disabled: null + filter: resource.type=gce_instance + name: exclusion1 + org_id: '1234567890' + google_logging_organization_exclusion.logging-exclusion["exclusion2"]: + description: exclusion2 (Terraform-managed). + disabled: null + filter: severity=NOTICE + name: exclusion2 + org_id: '1234567890' + +counts: + google_logging_organization_exclusion: 2 diff --git a/tests/modules/organization/fixture/test.network_tags.tfvars b/tests/modules/organization/network_tags.tfvars similarity index 100% rename from tests/modules/organization/fixture/test.network_tags.tfvars rename to tests/modules/organization/network_tags.tfvars diff --git a/tests/modules/organization/fixture/test.orgpolicies-boolean.tfvars b/tests/modules/organization/org_policies_boolean.tfvars similarity index 100% rename from tests/modules/organization/fixture/test.orgpolicies-boolean.tfvars rename to tests/modules/organization/org_policies_boolean.tfvars diff --git a/tests/modules/organization/org_policies_boolean.yaml b/tests/modules/organization/org_policies_boolean.yaml new file mode 100644 index 000000000..4038b0265 --- /dev/null +++ b/tests/modules/organization/org_policies_boolean.yaml @@ -0,0 +1,39 @@ +values: + google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]: + name: organizations/1234567890/policies/iam.disableServiceAccountKeyCreation + parent: organizations/1234567890 + spec: + - inherit_from_parent: null + reset: null + rules: + - allow_all: null + condition: [] + deny_all: null + enforce: 'TRUE' + values: [] + timeouts: null + google_org_policy_policy.default["iam.disableServiceAccountKeyUpload"]: + name: organizations/1234567890/policies/iam.disableServiceAccountKeyUpload + parent: organizations/1234567890 + spec: + - inherit_from_parent: null + reset: null + rules: + - allow_all: null + condition: [] + deny_all: null + enforce: 'FALSE' + values: [] + - allow_all: null + condition: + - description: test condition + expression: resource.matchTagId(aa, bb) + location: xxx + title: condition + deny_all: null + enforce: 'TRUE' + values: [] + timeouts: null + +counts: + google_org_policy_policy: 2 diff --git a/tests/modules/organization/fixture/test.orgpolicy-custom-constraints.tfvars b/tests/modules/organization/org_policies_custom_constraints.tfvars similarity index 100% rename from tests/modules/organization/fixture/test.orgpolicy-custom-constraints.tfvars rename to tests/modules/organization/org_policies_custom_constraints.tfvars diff --git a/tests/modules/organization/org_policies_custom_constraints.yaml b/tests/modules/organization/org_policies_custom_constraints.yaml new file mode 100644 index 000000000..62c92e5b5 --- /dev/null +++ b/tests/modules/organization/org_policies_custom_constraints.yaml @@ -0,0 +1,23 @@ +values: + google_org_policy_custom_constraint.constraint["custom.dataprocNoMoreThan10Workers"]: + action_type: DENY + condition: resource.config.workerConfig.numInstances + resource.config.secondaryWorkerConfig.numInstances > 10 + method_types: + - CREATE + - UPDATE + name: custom.dataprocNoMoreThan10Workers + parent: organizations/1234567890 + resource_types: + - dataproc.googleapis.com/Cluster + google_org_policy_custom_constraint.constraint["custom.gkeEnableAutoUpgrade"]: + action_type: ALLOW + condition: resource.management.autoUpgrade == true + method_types: + - CREATE + name: custom.gkeEnableAutoUpgrade + parent: organizations/1234567890 + resource_types: + - container.googleapis.com/NodePool + +counts: + google_org_policy_custom_constraint: 2 diff --git a/tests/modules/organization/fixture/test.orgpolicies-list.tfvars b/tests/modules/organization/org_policies_list.tfvars similarity index 96% rename from tests/modules/organization/fixture/test.orgpolicies-list.tfvars rename to tests/modules/organization/org_policies_list.tfvars index 738071733..f9de8dbab 100644 --- a/tests/modules/organization/fixture/test.orgpolicies-list.tfvars +++ b/tests/modules/organization/org_policies_list.tfvars @@ -3,6 +3,7 @@ org_policies = { deny = { all = true } } "iam.allowedPolicyMemberDomains" = { + inherit_from_parent = true allow = { values = ["C0xxxxxxx", "C0yyyyyyy"] } diff --git a/tests/modules/organization/org_policies_list.yaml b/tests/modules/organization/org_policies_list.yaml new file mode 100644 index 000000000..fc261a862 --- /dev/null +++ b/tests/modules/organization/org_policies_list.yaml @@ -0,0 +1,71 @@ +values: + google_org_policy_policy.default["compute.restrictLoadBalancerCreationForTypes"]: + name: organizations/1234567890/policies/compute.restrictLoadBalancerCreationForTypes + parent: organizations/1234567890 + spec: + - inherit_from_parent: null + reset: null + rules: + - allow_all: null + condition: [] + deny_all: null + enforce: null + values: + - allowed_values: null + denied_values: + - in:EXTERNAL + - allow_all: null + condition: + - description: test condition + expression: resource.matchTagId(aa, bb) + location: xxx + title: condition + deny_all: null + enforce: null + values: + - allowed_values: + - EXTERNAL_1 + denied_values: null + - allow_all: 'TRUE' + condition: + - description: test condition2 + expression: resource.matchTagId(cc, dd) + location: xxx + title: condition2 + deny_all: null + enforce: null + values: [] + timeouts: null + google_org_policy_policy.default["compute.vmExternalIpAccess"]: + name: organizations/1234567890/policies/compute.vmExternalIpAccess + parent: organizations/1234567890 + spec: + - inherit_from_parent: null + reset: null + rules: + - allow_all: null + condition: [] + deny_all: 'TRUE' + enforce: null + values: [] + timeouts: null + google_org_policy_policy.default["iam.allowedPolicyMemberDomains"]: + name: organizations/1234567890/policies/iam.allowedPolicyMemberDomains + parent: organizations/1234567890 + spec: + - inherit_from_parent: true + reset: null + rules: + - allow_all: null + condition: [] + deny_all: null + enforce: null + values: + - allowed_values: + - C0xxxxxxx + - C0yyyyyyy + denied_values: null + timeouts: null + +counts: + google_org_policy_policy: 3 diff --git a/tests/modules/organization/fixture/test.resource_tags.tfvars b/tests/modules/organization/resource_tags.tfvars similarity index 100% rename from tests/modules/organization/fixture/test.resource_tags.tfvars rename to tests/modules/organization/resource_tags.tfvars diff --git a/tests/modules/organization/tags.yaml b/tests/modules/organization/tags.yaml new file mode 100644 index 000000000..3821eba8d --- /dev/null +++ b/tests/modules/organization/tags.yaml @@ -0,0 +1,62 @@ +values: + google_tags_tag_key.default["bar"]: + description: Managed by the Terraform organization module. + parent: organizations/1234567890 + purpose: null + purpose_data: null + short_name: bar + google_tags_tag_key.default["foo"]: + description: Managed by the Terraform organization module. + parent: organizations/1234567890 + purpose: null + purpose_data: null + short_name: foo + google_tags_tag_key.default["foobar"]: + description: Foobar tag. + parent: organizations/1234567890 + purpose: null + purpose_data: null + short_name: foobar + google_tags_tag_key.default["net_environment"]: + description: Managed by the Terraform organization module. + parent: organizations/1234567890 + purpose: GCE_FIREWALL + purpose_data: + network: foobar + short_name: net_environment + google_tags_tag_key_iam_binding.default["foobar:roles/resourcemanager.tagAdmin"]: + condition: [] + members: + - user:user1@example.com + - user:user2@example.com + role: roles/resourcemanager.tagAdmin + google_tags_tag_value.default["foobar/one"]: + description: Managed by the Terraform organization module. + short_name: one + google_tags_tag_value.default["foobar/three"]: + description: Foobar 3. + short_name: three + google_tags_tag_value.default["foobar/two"]: + description: Foobar 2. + short_name: two + google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagAdmin"]: + condition: [] + members: + - user:user4@example.com + role: roles/resourcemanager.tagAdmin + google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagViewer"]: + condition: [] + members: + - user:user3@example.com + role: roles/resourcemanager.tagViewer + google_tags_tag_value_iam_binding.default["foobar/two:roles/resourcemanager.tagViewer"]: + condition: [] + members: + - user:user3@example.com + role: roles/resourcemanager.tagViewer + +counts: + google_tags_tag_key: 4 + google_tags_tag_key_iam_binding: 1 + google_tags_tag_value: 3 + google_tags_tag_value_iam_binding: 3 diff --git a/tests/modules/organization/test_plan.py b/tests/modules/organization/test_plan.py deleted file mode 100644 index 37860ab6d..000000000 --- a/tests/modules/organization/test_plan.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2022 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. - - -def test_audit_config(plan_runner): - "Test audit config." - iam_audit_config = '{allServices={DATA_READ=[], DATA_WRITE=["user:me@example.org"]}}' - _, resources = plan_runner(iam_audit_config=iam_audit_config) - assert len(resources) == 1 - log_types = set( - r['log_type'] for r in resources[0]['values']['audit_log_config']) - assert log_types == set(['DATA_READ', 'DATA_WRITE']) - - -def test_iam(plan_runner): - "Test IAM." - group_iam = ( - '{' - '"owners@example.org" = ["roles/owner", "roles/resourcemanager.folderAdmin"],' - '"viewers@example.org" = ["roles/viewer"]' - '}') - iam = ('{' - '"roles/owner" = ["user:one@example.org", "user:two@example.org"],' - '"roles/browser" = ["domain:example.org"]' - '}') - _, resources = plan_runner(group_iam=group_iam, iam=iam) - roles = sorted([(r['values']['role'], sorted(r['values']['members'])) - for r in resources - if r['type'] == 'google_organization_iam_binding']) - assert roles == [ - ('roles/browser', ['domain:example.org']), - ('roles/owner', [ - 'group:owners@example.org', 'user:one@example.org', - 'user:two@example.org' - ]), - ('roles/resourcemanager.folderAdmin', ['group:owners@example.org']), - ('roles/viewer', ['group:viewers@example.org']), - ] - - -def test_iam_additive_members(plan_runner): - "Test IAM additive members." - iam = ('{"user:one@example.org" = ["roles/owner"],' - '"user:two@example.org" = ["roles/owner", "roles/editor"]}') - _, resources = plan_runner(iam_additive_members=iam) - roles = set((r['values']['role'], r['values']['member']) - for r in resources - if r['type'] == 'google_organization_iam_member') - assert roles == set([('roles/owner', 'user:one@example.org'), - ('roles/owner', 'user:two@example.org'), - ('roles/editor', 'user:two@example.org')]) diff --git a/tests/modules/organization/test_plan_firewall.py b/tests/modules/organization/test_plan_firewall.py deleted file mode 100644 index 5925e98b6..000000000 --- a/tests/modules/organization/test_plan_firewall.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright 2022 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. - -_FACTORY = ''' -{ - cidr_file = "data/firewall-cidrs.yaml" - policy_name = "factory-1" - rules_file = "data/firewall-rules.yaml" -} -''' -_POLICIES = ''' -{ - policy1 = { - allow-ingress = { - description = "" - direction = "INGRESS" - action = "allow" - priority = 100 - ranges = ["10.0.0.0/8"] - ports = { - tcp = ["22"] - } - target_service_accounts = null - target_resources = null - logging = false - } - deny-egress = { - description = "" - direction = "EGRESS" - action = "deny" - priority = 200 - ranges = ["192.168.0.0/24"] - ports = { - tcp = ["443"] - } - target_service_accounts = null - target_resources = null - logging = false - } - } - policy2 = { - allow-ingress = { - description = "" - direction = "INGRESS" - action = "allow" - priority = 100 - ranges = ["10.0.0.0/8"] - ports = { - tcp = ["22"] - } - target_service_accounts = null - target_resources = null - logging = false - } - } - } -''' - - -def test_custom(plan_runner): - 'Test custom firewall policies.' - _, resources = plan_runner(firewall_policies=_POLICIES) - assert len(resources) == 5 - policies = [r for r in resources - if r['type'] == 'google_compute_firewall_policy'] - rules = [r for r in resources - if r['type'] == 'google_compute_firewall_policy_rule'] - assert set(r['index'] for r in policies) == set([ - 'policy1', 'policy2' - ]) - assert set(r['index'] for r in rules) == set([ - 'policy1-deny-egress', 'policy2-allow-ingress', 'policy1-allow-ingress' - ]) - - -def test_factory(plan_runner): - 'Test firewall policy factory.' - _, resources = plan_runner(firewall_policy_factory=_FACTORY) - assert len(resources) == 3 - policies = [r for r in resources - if r['type'] == 'google_compute_firewall_policy'] - rules = [r for r in resources - if r['type'] == 'google_compute_firewall_policy_rule'] - assert set(r['index'] for r in policies) == set([ - 'factory-1' - ]) - assert set(r['index'] for r in rules) == set([ - 'factory-1-allow-admins', 'factory-1-allow-ssh-from-iap' - ]) - - -def test_factory_name(plan_runner): - 'Test firewall policy factory default name.' - factory = _FACTORY.replace('"factory-1"', 'null') - _, resources = plan_runner(firewall_policy_factory=factory) - assert len(resources) == 3 - policies = [r for r in resources - if r['type'] == 'google_compute_firewall_policy'] - assert set(r['index'] for r in policies) == set([ - 'factory' - ]) - - -def test_combined(plan_runner): - 'Test combined rules.' - _, resources = plan_runner(firewall_policies=_POLICIES, - firewall_policy_factory=_FACTORY) - assert len(resources) == 8 - policies = [r for r in resources - if r['type'] == 'google_compute_firewall_policy'] - rules = [r for r in resources - if r['type'] == 'google_compute_firewall_policy_rule'] - assert set(r['index'] for r in policies) == set([ - 'factory-1', 'policy1', 'policy2' - ]) - assert set(r['index'] for r in rules) == set([ - 'factory-1-allow-admins', 'factory-1-allow-ssh-from-iap', - 'policy1-deny-egress', 'policy2-allow-ingress', 'policy1-allow-ingress' - ]) diff --git a/tests/modules/organization/test_plan_logging.py b/tests/modules/organization/test_plan_logging.py deleted file mode 100644 index 287a5a489..000000000 --- a/tests/modules/organization/test_plan_logging.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright 2022 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. - -from collections import Counter - - -def test_sinks(plan_runner): - "Test folder-level sinks." - tfvars = 'test.logging-sinks.tfvars' - _, resources = plan_runner(tf_var_file=tfvars) - assert len(resources) == 8 - - resource_types = Counter([r["type"] for r in resources]) - assert resource_types == { - "google_logging_organization_sink": 4, - "google_bigquery_dataset_iam_member": 1, - "google_project_iam_member": 1, - "google_pubsub_topic_iam_member": 1, - "google_storage_bucket_iam_member": 1, - } - - sinks = [ - r for r in resources if r["type"] == "google_logging_organization_sink" - ] - assert sorted([r["index"] for r in sinks]) == [ - "debug", - "info", - "notice", - "warning", - ] - values = [( - r["index"], - r["values"]["filter"], - r["values"]["destination"], - r["values"]["include_children"], - ) for r in sinks] - assert sorted(values) == [ - ( - "debug", - "severity=DEBUG", - "logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket", - False, - ), - ( - "info", - "severity=INFO", - "bigquery.googleapis.com/projects/myproject/datasets/mydataset", - True, - ), - ( - "notice", - "severity=NOTICE", - "pubsub.googleapis.com/projects/myproject/topics/mytopic", - False, - ), - ("warning", "severity=WARNING", "storage.googleapis.com/mybucket", True), - ] - - bindings = [r for r in resources if "member" in r["type"]] - values = [(r["index"], r["type"], r["values"]["role"]) for r in bindings] - assert sorted(values) == [ - ("debug", "google_project_iam_member", "roles/logging.bucketWriter"), - ("info", "google_bigquery_dataset_iam_member", - "roles/bigquery.dataEditor"), - ("notice", "google_pubsub_topic_iam_member", "roles/pubsub.publisher"), - ("warning", "google_storage_bucket_iam_member", - "roles/storage.objectCreator"), - ] - - exclusions = [(r["index"], r["values"]["exclusions"]) for r in sinks] - assert sorted(exclusions) == [ - ( - "debug", - [ - { - "description": None, - "disabled": False, - "filter": "logName:compute", - "name": "no-compute", - }, - { - "description": None, - "disabled": False, - "filter": "logName:container", - "name": "no-container", - }, - ], - ), - ("info", []), - ("notice", []), - ("warning", []), - ] - - -def test_exclusions(plan_runner): - "Test folder-level logging exclusions." - logging_exclusions = ("{" - 'exclusion1 = "resource.type=gce_instance", ' - 'exclusion2 = "severity=NOTICE", ' - "}") - _, resources = plan_runner(logging_exclusions=logging_exclusions) - assert len(resources) == 2 - exclusions = [ - r for r in resources - if r["type"] == "google_logging_organization_exclusion" - ] - assert sorted([r["index"] for r in exclusions]) == [ - "exclusion1", - "exclusion2", - ] - values = [(r["index"], r["values"]["filter"]) for r in exclusions] - assert sorted(values) == [ - ("exclusion1", "resource.type=gce_instance"), - ("exclusion2", "severity=NOTICE"), - ] diff --git a/tests/modules/organization/test_plan_org_policies.py b/tests/modules/organization/test_plan_org_policies.py index 05550832b..0497509bc 100644 --- a/tests/modules/organization/test_plan_org_policies.py +++ b/tests/modules/organization/test_plan_org_policies.py @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# 2022 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,46 +14,34 @@ import pathlib -from .validate_policies import validate_policy_boolean, validate_policy_list, validate_policy_custom_constraints +import pytest + +_params = ['boolean', 'list'] -def test_policy_boolean(plan_runner): - "Test boolean org policy." - tfvars = 'test.orgpolicies-boolean.tfvars' - _, resources = plan_runner(tf_var_file=tfvars) - validate_policy_boolean(resources) - - -def test_policy_list(plan_runner): - "Test list org policy." - tfvars = 'test.orgpolicies-list.tfvars' - _, resources = plan_runner(tf_var_file=tfvars) - validate_policy_list(resources) - - -def test_policy_custom_constraints(plan_runner): - "Test org policy custom constraints." - tfvars = 'test.orgpolicy-custom-constraints.tfvars' - _, resources = plan_runner(tf_var_file=tfvars) - validate_policy_custom_constraints(resources) - - -def test_factory_policy_boolean(plan_runner, tfvars_to_yaml, tmp_path): +@pytest.mark.parametrize('policy_type', _params) +def test_policy_factory(generic_plan_summary, tfvars_to_yaml, tmp_path, + policy_type): dest = tmp_path / 'policies.yaml' - tfvars_to_yaml('test.orgpolicies-boolean.tfvars', dest, 'org_policies') - _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"') - validate_policy_boolean(resources) + tfvars_to_yaml(f'org_policies_{policy_type}.tfvars', dest, 'org_policies') + tfvars_plan = generic_plan_summary( + 'modules/organization', + tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars']) + yaml_plan = generic_plan_summary('modules/organization', + tf_var_files=['common.tfvars'], + org_policies_data_path=f'{tmp_path}') + assert tfvars_plan.values == yaml_plan.values -def test_factory_policy_list(plan_runner, tfvars_to_yaml, tmp_path): - dest = tmp_path / 'policies.yaml' - tfvars_to_yaml('test.orgpolicies-list.tfvars', dest, 'org_policies') - _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"') - validate_policy_list(resources) - - -def test_factory_policy_custom_constraints(plan_runner, tfvars_to_yaml, tmp_path): +def test_custom_constraint_factory(generic_plan_summary, tfvars_to_yaml, + tmp_path): dest = tmp_path / 'constraints.yaml' - tfvars_to_yaml('test.orgpolicy-custom-constraints.tfvars', dest, 'org_policy_custom_constraints') - _, resources = plan_runner(org_policy_custom_constraints_data_path=f'"{tmp_path}"') - validate_policy_custom_constraints(resources) + tfvars_to_yaml(f'org_policies_custom_constraints.tfvars', dest, + 'org_policy_custom_constraints') + tfvars_plan = generic_plan_summary( + 'modules/organization', + tf_var_files=['common.tfvars', f'org_policies_custom_constraints.tfvars']) + yaml_plan = generic_plan_summary( + 'modules/organization', tf_var_files=['common.tfvars'], + org_policy_custom_constraints_data_path=f'{tmp_path}') + assert tfvars_plan.values == yaml_plan.values diff --git a/tests/modules/organization/test_plan_tags.py b/tests/modules/organization/test_plan_tags.py deleted file mode 100644 index bce2208af..000000000 --- a/tests/modules/organization/test_plan_tags.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2022 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. - - -def test_resource_tags(plan_runner): - 'Test resource tags.' - _, resources = plan_runner(tf_var_file='test.resource_tags.tfvars') - assert len(resources) == 10 - resource_values = {} - for r in resources: - resource_values.setdefault(r['type'], []).append(r['values']) - assert len(resource_values['google_tags_tag_key']) == 3 - assert len(resource_values['google_tags_tag_value']) == 3 - result = [ - r['role'] for r in resource_values['google_tags_tag_value_iam_binding'] - ] - expected = [ - 'roles/resourcemanager.tagAdmin', - 'roles/resourcemanager.tagViewer', - 'roles/resourcemanager.tagViewer' - ] - assert result == expected - - -def test_network_tags(plan_runner): - 'Test network tags.' - _, resources = plan_runner(tf_var_file='test.network_tags.tfvars') - assert len(resources) == 1 - resource_values = {} - for r in resources: - resource_values.setdefault(r['type'], []).append(r['values']) - google_tags_tag_key = resource_values['google_tags_tag_key'][0] - assert google_tags_tag_key['purpose'] == "GCE_FIREWALL" - assert google_tags_tag_key['purpose_data']['network'] == "foobar" - - -def test_bindings(plan_runner): - 'Test tag bindings.' - tag_bindings = '{foo = "tagValues/123456789012"}' - _, resources = plan_runner(tag_bindings=tag_bindings) - assert len(resources) == 1 diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml new file mode 100644 index 000000000..5bdfecf49 --- /dev/null +++ b/tests/modules/organization/tftest.yaml @@ -0,0 +1,66 @@ +module: modules/organization + +tests: + audit_config: + tfvars: + - common.tfvars + - audit_config.tfvars + + iam: + tfvars: + - common.tfvars + - iam.tfvars + + iam_additive: + tfvars: + - common.tfvars + - iam_additive.tfvars + + logging: + tfvars: + - common.tfvars + - logging.tfvars + + logging_exclusions: + tfvars: + - common.tfvars + - logging_exclusions.tfvars + + org_policies_list: + tfvars: + - common.tfvars + - org_policies_list.tfvars + + org_policies_boolean: + tfvars: + - common.tfvars + - org_policies_boolean.tfvars + + org_policies_custom_constraints: + tfvars: + - common.tfvars + - org_policies_custom_constraints.tfvars + + tags: + tfvars: + - common.tfvars + - network_tags.tfvars + - resource_tags.tfvars + inventory: + - tags.yaml + + firewall_policies: + tfvars: + - common.tfvars + - firewall_policies.tfvars + + firewall_policies_factory: + tfvars: + - common.tfvars + - firewall_policies_factory.tfvars + + firewall_policies_factory_combined: + tfvars: + - common.tfvars + - firewall_policies.tfvars + - firewall_policies_factory.tfvars diff --git a/tests/modules/project/test_plan_org_policies.py b/tests/modules/project/test_plan_org_policies.py index d4e4559d0..8463761e8 100644 --- a/tests/modules/project/test_plan_org_policies.py +++ b/tests/modules/project/test_plan_org_policies.py @@ -31,13 +31,14 @@ def test_policy_list(plan_runner): def test_factory_policy_boolean(plan_runner, tfvars_to_yaml, tmp_path): dest = tmp_path / 'policies.yaml' - tfvars_to_yaml('test.orgpolicies-boolean.tfvars', dest, 'org_policies') + tfvars_to_yaml('fixture/test.orgpolicies-boolean.tfvars', dest, + 'org_policies') _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"') validate_policy_boolean(resources) def test_factory_policy_list(plan_runner, tfvars_to_yaml, tmp_path): dest = tmp_path / 'policies.yaml' - tfvars_to_yaml('test.orgpolicies-list.tfvars', dest, 'org_policies') + tfvars_to_yaml('fixture/test.orgpolicies-list.tfvars', dest, 'org_policies') _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"') validate_policy_list(resources) From f5461056cf029485a12b12a0fca1dbd45563d6a9 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sun, 4 Dec 2022 20:58:32 +0100 Subject: [PATCH 15/28] Fix boilerplate --- tests/modules/net_vpc/factory.yaml | 14 ++++++++++++++ tests/modules/net_vpc/peering.yaml | 14 ++++++++++++++ tests/modules/net_vpc/psa_routes_export.yaml | 14 ++++++++++++++ tests/modules/net_vpc/psa_routes_import.yaml | 14 ++++++++++++++ .../modules/net_vpc/psa_routes_import_export.yaml | 14 ++++++++++++++ tests/modules/net_vpc/psa_simple.yaml | 14 ++++++++++++++ tests/modules/net_vpc/shared_vpc.yaml | 14 ++++++++++++++ tests/modules/net_vpc/simple.yaml | 14 ++++++++++++++ tests/modules/net_vpc/subnets.yaml | 14 ++++++++++++++ tests/modules/net_vpc/tftest.yaml | 14 ++++++++++++++ tests/modules/organization/audit_config.yaml | 14 ++++++++++++++ tests/modules/organization/firewall_policies.yaml | 14 ++++++++++++++ .../organization/firewall_policies_factory.yaml | 14 ++++++++++++++ .../firewall_policies_factory_combined.yaml | 14 ++++++++++++++ tests/modules/organization/iam.yaml | 14 ++++++++++++++ tests/modules/organization/iam_additive.yaml | 14 ++++++++++++++ tests/modules/organization/logging.yaml | 14 ++++++++++++++ tests/modules/organization/logging_exclusions.yaml | 14 ++++++++++++++ .../modules/organization/org_policies_boolean.yaml | 14 ++++++++++++++ .../org_policies_custom_constraints.yaml | 14 ++++++++++++++ tests/modules/organization/org_policies_list.yaml | 14 ++++++++++++++ tests/modules/organization/tags.yaml | 14 ++++++++++++++ .../modules/organization/test_plan_org_policies.py | 2 +- tests/modules/organization/tftest.yaml | 14 ++++++++++++++ 24 files changed, 323 insertions(+), 1 deletion(-) diff --git a/tests/modules/net_vpc/factory.yaml b/tests/modules/net_vpc/factory.yaml index e38adfd11..9cf628d09 100644 --- a/tests/modules/net_vpc/factory.yaml +++ b/tests/modules/net_vpc/factory.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_subnetwork.subnetwork["europe-west1/factory-subnet"]: description: 'Sample description' diff --git a/tests/modules/net_vpc/peering.yaml b/tests/modules/net_vpc/peering.yaml index 659eccff3..8d0bbed71 100644 --- a/tests/modules/net_vpc/peering.yaml +++ b/tests/modules/net_vpc/peering.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_network.network[0]: auto_create_subnetworks: false diff --git a/tests/modules/net_vpc/psa_routes_export.yaml b/tests/modules/net_vpc/psa_routes_export.yaml index d49d31380..c8cf631b2 100644 --- a/tests/modules/net_vpc/psa_routes_export.yaml +++ b/tests/modules/net_vpc/psa_routes_export.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_global_address.psa_ranges["bar"]: address: 172.16.100.0 diff --git a/tests/modules/net_vpc/psa_routes_import.yaml b/tests/modules/net_vpc/psa_routes_import.yaml index 06255280f..771d881f9 100644 --- a/tests/modules/net_vpc/psa_routes_import.yaml +++ b/tests/modules/net_vpc/psa_routes_import.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_global_address.psa_ranges["bar"]: address: 172.16.100.0 diff --git a/tests/modules/net_vpc/psa_routes_import_export.yaml b/tests/modules/net_vpc/psa_routes_import_export.yaml index 87b795f1f..561fa50ac 100644 --- a/tests/modules/net_vpc/psa_routes_import_export.yaml +++ b/tests/modules/net_vpc/psa_routes_import_export.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_global_address.psa_ranges["bar"]: address: 172.16.100.0 diff --git a/tests/modules/net_vpc/psa_simple.yaml b/tests/modules/net_vpc/psa_simple.yaml index 8b66711cc..019b443fa 100644 --- a/tests/modules/net_vpc/psa_simple.yaml +++ b/tests/modules/net_vpc/psa_simple.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_global_address.psa_ranges["bar"]: address: 172.16.100.0 diff --git a/tests/modules/net_vpc/shared_vpc.yaml b/tests/modules/net_vpc/shared_vpc.yaml index 78e6d5043..0837dbc4b 100644 --- a/tests/modules/net_vpc/shared_vpc.yaml +++ b/tests/modules/net_vpc/shared_vpc.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_network.network[0]: auto_create_subnetworks: false diff --git a/tests/modules/net_vpc/simple.yaml b/tests/modules/net_vpc/simple.yaml index 1c2d57eec..004be7ecf 100644 --- a/tests/modules/net_vpc/simple.yaml +++ b/tests/modules/net_vpc/simple.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_network.network[0]: auto_create_subnetworks: false diff --git a/tests/modules/net_vpc/subnets.yaml b/tests/modules/net_vpc/subnets.yaml index 1e44fe80c..9ccf31e60 100644 --- a/tests/modules/net_vpc/subnets.yaml +++ b/tests/modules/net_vpc/subnets.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_network.network[0]: auto_create_subnetworks: false diff --git a/tests/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml index 07caeb40a..3a8060dd1 100644 --- a/tests/modules/net_vpc/tftest.yaml +++ b/tests/modules/net_vpc/tftest.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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/net-vpc tests: diff --git a/tests/modules/organization/audit_config.yaml b/tests/modules/organization/audit_config.yaml index 712478f7a..56e90b622 100644 --- a/tests/modules/organization/audit_config.yaml +++ b/tests/modules/organization/audit_config.yaml @@ -1,2 +1,16 @@ +# Copyright 2022 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. + counts: google_organization_iam_audit_config: 1 diff --git a/tests/modules/organization/firewall_policies.yaml b/tests/modules/organization/firewall_policies.yaml index 1c64ceaa3..4ecc5c72c 100644 --- a/tests/modules/organization/firewall_policies.yaml +++ b/tests/modules/organization/firewall_policies.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_firewall_policy.policy["policy1"]: parent: organizations/1234567890 diff --git a/tests/modules/organization/firewall_policies_factory.yaml b/tests/modules/organization/firewall_policies_factory.yaml index f5e8bd986..85e565fdd 100644 --- a/tests/modules/organization/firewall_policies_factory.yaml +++ b/tests/modules/organization/firewall_policies_factory.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_firewall_policy.policy["factory-1"]: description: null diff --git a/tests/modules/organization/firewall_policies_factory_combined.yaml b/tests/modules/organization/firewall_policies_factory_combined.yaml index 6d3f8976b..3b5cf6cc2 100644 --- a/tests/modules/organization/firewall_policies_factory_combined.yaml +++ b/tests/modules/organization/firewall_policies_factory_combined.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_compute_firewall_policy.policy["factory-1"]: {} google_compute_firewall_policy.policy["policy1"]: {} diff --git a/tests/modules/organization/iam.yaml b/tests/modules/organization/iam.yaml index b735131fa..7b1a8cb95 100644 --- a/tests/modules/organization/iam.yaml +++ b/tests/modules/organization/iam.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_organization_iam_binding.authoritative["roles/browser"]: condition: [] diff --git a/tests/modules/organization/iam_additive.yaml b/tests/modules/organization/iam_additive.yaml index 6e2a7875c..68eda8c27 100644 --- a/tests/modules/organization/iam_additive.yaml +++ b/tests/modules/organization/iam_additive.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_organization_iam_binding.authoritative["user:one@example.org"]: condition: [] diff --git a/tests/modules/organization/logging.yaml b/tests/modules/organization/logging.yaml index dea783ec3..8038c9ab5 100644 --- a/tests/modules/organization/logging.yaml +++ b/tests/modules/organization/logging.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_bigquery_dataset_iam_member.bq-sinks-binding["info"]: condition: [] diff --git a/tests/modules/organization/logging_exclusions.yaml b/tests/modules/organization/logging_exclusions.yaml index ded59fa54..4d51dd7cd 100644 --- a/tests/modules/organization/logging_exclusions.yaml +++ b/tests/modules/organization/logging_exclusions.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_logging_organization_exclusion.logging-exclusion["exclusion1"]: description: exclusion1 (Terraform-managed). diff --git a/tests/modules/organization/org_policies_boolean.yaml b/tests/modules/organization/org_policies_boolean.yaml index 4038b0265..310997a4c 100644 --- a/tests/modules/organization/org_policies_boolean.yaml +++ b/tests/modules/organization/org_policies_boolean.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]: name: organizations/1234567890/policies/iam.disableServiceAccountKeyCreation diff --git a/tests/modules/organization/org_policies_custom_constraints.yaml b/tests/modules/organization/org_policies_custom_constraints.yaml index 62c92e5b5..c558c066f 100644 --- a/tests/modules/organization/org_policies_custom_constraints.yaml +++ b/tests/modules/organization/org_policies_custom_constraints.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_org_policy_custom_constraint.constraint["custom.dataprocNoMoreThan10Workers"]: action_type: DENY diff --git a/tests/modules/organization/org_policies_list.yaml b/tests/modules/organization/org_policies_list.yaml index fc261a862..39c3a3896 100644 --- a/tests/modules/organization/org_policies_list.yaml +++ b/tests/modules/organization/org_policies_list.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_org_policy_policy.default["compute.restrictLoadBalancerCreationForTypes"]: name: organizations/1234567890/policies/compute.restrictLoadBalancerCreationForTypes diff --git a/tests/modules/organization/tags.yaml b/tests/modules/organization/tags.yaml index 3821eba8d..7da6f77bb 100644 --- a/tests/modules/organization/tags.yaml +++ b/tests/modules/organization/tags.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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: google_tags_tag_key.default["bar"]: description: Managed by the Terraform organization module. diff --git a/tests/modules/organization/test_plan_org_policies.py b/tests/modules/organization/test_plan_org_policies.py index 0497509bc..0a5d9a35a 100644 --- a/tests/modules/organization/test_plan_org_policies.py +++ b/tests/modules/organization/test_plan_org_policies.py @@ -1,4 +1,4 @@ -# 2022 Google LLC +# Copyright 2022 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/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml index 5bdfecf49..662b6b1a5 100644 --- a/tests/modules/organization/tftest.yaml +++ b/tests/modules/organization/tftest.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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/organization tests: From 0a6285fd4e97188f1489a280088b0a1a1296b620 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sun, 4 Dec 2022 21:42:14 +0100 Subject: [PATCH 16/28] Reorder code --- tests/collectors.py | 51 ++++++++++ tests/conftest.py | 229 +------------------------------------------- tests/fixtures.py | 223 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 226 deletions(-) create mode 100644 tests/collectors.py create mode 100644 tests/fixtures.py diff --git a/tests/collectors.py b/tests/collectors.py new file mode 100644 index 000000000..4ba3c7bc5 --- /dev/null +++ b/tests/collectors.py @@ -0,0 +1,51 @@ +# Copyright 2022 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 pytest +import yaml + +from .fixtures import generic_plan_summary, generic_plan_validator + + +class FabricTestFile(pytest.File): + + def collect(self): + raw = yaml.safe_load(self.path.open()) + module = raw['module'] + for test_name, spec in raw['tests'].items(): + inventory = spec.get('inventory', f'{test_name}.yaml') + tfvars = spec['tfvars'] + yield FabricTestItem.from_parent(self, name=test_name, module=module, + inventory=inventory, tfvars=tfvars) + + +class FabricTestItem(pytest.Item): + + def __init__(self, name, parent, module, inventory, tfvars): + super().__init__(name, parent) + self.module = module + self.inventory = inventory + self.tfvars = tfvars + + def runtest(self): + s = generic_plan_validator(self.module, self.inventory, + self.parent.path.parent, self.tfvars) + + def reportinfo(self): + return self.path, None, self.name + + +def pytest_collect_file(parent, file_path): + if file_path.suffix == '.yaml' and file_path.name.startswith('tftest'): + return FabricTestFile.from_parent(parent, path=file_path) diff --git a/tests/conftest.py b/tests/conftest.py index abd05dbd1..778989fd1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,19 +13,18 @@ # limitations under the License. 'Shared fixtures.' -import collections import inspect -import itertools import os import shutil import tempfile -from pathlib import Path import pytest import tftest import yaml -PlanSummary = collections.namedtuple('PlanSummary', 'values counts outputs') +from .collectors import pytest_collect_file +from .fixtures import generic_plan_summary_fixture, generic_plan_validator_fixture +from .fixtures import generic_plan_summary, generic_plan_validator BASEDIR = os.path.dirname(os.path.dirname(__file__)) @@ -158,225 +157,3 @@ def apply_runner(): @pytest.fixture def basedir(): return BASEDIR - - -def _generic_plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): - """ - Run a Terraform plan on the module located at `module_path`. - - - module_path: terraform root module to run. Can be an absolute - path or relative to the root of the repository - - - basedir: directory root to use for relative paths in - tf_var_files. - - - tf_var_files: set of terraform variable files (tfvars) to pass - in to terraform - - Returns a PlanSummary object containing 3 attributes: - - values: dictionary where the keys are terraform plan addresses - and values are the JSON representation (converted to python - types) of the attribute values of the resource. - - - counts: dictionary where the keys are the terraform resource - types and the values are the number of times that type appears - in the plan - - - outputs: dictionary of the modules outputs that can be - determined at plan type. - - Consult [1] for mode details on the structure of values and outputs - - [1] https://developer.hashicorp.com/terraform/internals/json-format - """ - module_path = Path(BASEDIR) / module_path - - # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not - # in the environment - with tempfile.TemporaryDirectory(dir=module_path.parent) as tmp_path: - # if TFTEST_COPY is set, copy the fixture to a temporary - # directory before running the plan. This is needed if you want - # to run multiple tests for the same module in parallel - if os.environ.get('TFTEST_COPY'): - test_path = Path(tmp_path) - shutil.copytree(module_path, test_path, dirs_exist_ok=True) - - # if we're copying the module, we might as well remove any files - # and directories from the test directory that are automatically - # read by terraform. Useful to avoid surprises if, for example, - # you have an active fast deployment with links to configs) - autopaths = itertools.chain( - test_path.glob('*.auto.tfvars'), - test_path.glob('*.auto.tfvars.json'), - test_path.glob('terraform.tfstate*'), - test_path.glob('terraform.tfvars'), - test_path.glob('.terraform'), - # any symlinks? - ) - for p in autopaths: - if p.is_dir(): - shutil.rmtree(p) - else: - p.unlink() - else: - test_path = module_path - - # prepare tftest and run plan - binary = os.environ.get('TERRAFORM', 'terraform') - tf = tftest.TerraformTest(test_path, binary=binary) - tf.setup(upgrade=True) - tf_var_files = [(basedir / x).resolve() for x in tf_var_files or []] - plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, - tf_vars=tf_vars) - - # compute resource type counts and address->values map - values = {} - counts = collections.defaultdict(int) - q = collections.deque([plan.root_module]) - while q: - e = q.popleft() - - if 'type' in e: - counts[e['type']] += 1 - if 'values' in e: - values[e['address']] = e['values'] - - for x in e.get('resources', []): - q.append(x) - for x in e.get('child_modules', []): - q.append(x) - - # extract planned outputs - outputs = plan.get('planned_values', {}).get('outputs', {}) - - return PlanSummary(values, dict(counts), outputs) - - -@pytest.fixture -def generic_plan_summary(request): - 'Return a function to generate a PlanSummary.' - - def inner(module_path, basedir=None, tf_var_files=None, **tf_vars): - if basedir is None: - basedir = Path(request.fspath).parent - return _generic_plan_summary(module_path=module_path, basedir=basedir, - tf_var_files=tf_var_files, **tf_vars) - - return inner - - -def _generic_plan_validator(module_path, inventory_paths, basedir, - tf_var_files=None, **tf_vars): - summary = _generic_plan_summary(module_path=module_path, - tf_var_files=tf_var_files, basedir=basedir, - **tf_vars) - - # allow single single string for inventory_paths - if not isinstance(inventory_paths, list): - inventory_paths = [inventory_paths] - - for path in inventory_paths: - # allow tfvars and inventory to be relative to the caller - path = basedir / path - inventory = yaml.safe_load(path.read_text()) - assert inventory is not None, f'Inventory {path} is empty' - - # If you add additional asserts to this function: - # - put the values coming from the plan on the left side of - # any comparison operators - # - put the values coming from user's inventory the right - # side of any comparison operators. - # - include a descriptive error message to the assert - - # for values: - # - verify each address in the user's inventory exists in the plan - # - for those address that exist on both the user's inventory and - # the plan output, ensure the set of keys on the inventory are a - # subset of the keys in the plan, and compare their values by - # equality - if 'values' in inventory: - expected_values = inventory['values'] - for address, expected_value in expected_values.items(): - assert address in summary.values, \ - f'{address} is not a valid address in the plan' - for k, v in expected_value.items(): - assert k in summary.values[address], \ - f'{k} not found at {address}' - plan_value = summary.values[address][k] - assert plan_value == v, \ - f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' - - if 'counts' in inventory: - expected_counts = inventory['counts'] - for type_, expected_count in expected_counts.items(): - assert type_ in summary.counts, \ - f'module does not create any resources of type `{type_}`' - plan_count = summary.counts[type_] - assert plan_count == expected_count, \ - f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' - - if 'outputs' in inventory: - expected_outputs = inventory['outputs'] - for output_name, expected_output in expected_outputs.items(): - assert output_name in summary.outputs, \ - f'module does not output `{output_name}`' - output = summary.outputs[output_name] - # assert 'value' in output, \ - # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' - plan_output = output.get('value', '__missing__') - assert plan_output == expected_output, \ - f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' - - return summary - - -@pytest.fixture -def generic_plan_validator(request): - 'Return a function that builds a PlanSummary and compares it to an yaml inventory.' - - def inner(module_path, inventory_paths, basedir=None, tf_var_files=None, - **tf_vars): - if basedir is None: - basedir = Path(request.fspath).parent - return _generic_plan_validator(module_path=module_path, - inventory_paths=inventory_paths, - basedir=basedir, tf_var_files=tf_var_paths, - **tf_vars) - - return inner - - -def pytest_collect_file(parent, file_path): - if file_path.suffix == '.yaml' and file_path.name.startswith('tftest'): - return FabricTestFile.from_parent(parent, path=file_path) - - -class FabricTestFile(pytest.File): - - def collect(self): - raw = yaml.safe_load(self.path.open()) - module = raw['module'] - for test_name, spec in raw['tests'].items(): - inventory = spec.get('inventory', f'{test_name}.yaml') - tfvars = spec['tfvars'] - yield FabricTestItem.from_parent(self, name=test_name, module=module, - inventory=inventory, tfvars=tfvars) - - -from icecream import ic - - -class FabricTestItem(pytest.Item): - - def __init__(self, name, parent, module, inventory, tfvars): - super().__init__(name, parent) - self.module = module - self.inventory = inventory - self.tfvars = tfvars - - def runtest(self): - s = _generic_plan_validator(self.module, self.inventory, - self.parent.path.parent, self.tfvars) - - def reportinfo(self): - return self.path, None, self.name diff --git a/tests/fixtures.py b/tests/fixtures.py new file mode 100644 index 000000000..43b2128cd --- /dev/null +++ b/tests/fixtures.py @@ -0,0 +1,223 @@ +# Copyright 2022 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 collections +import itertools +import os +import shutil +import tempfile +from pathlib import Path + +import pytest +import tftest +import yaml + +PlanSummary = collections.namedtuple('PlanSummary', 'values counts outputs') + + +def generic_plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): + """ + Run a Terraform plan on the module located at `module_path`. + + - module_path: terraform root module to run. Can be an absolute + path or relative to the root of the repository + + - basedir: directory root to use for relative paths in + tf_var_files. + + - tf_var_files: set of terraform variable files (tfvars) to pass + in to terraform + + Returns a PlanSummary object containing 3 attributes: + - values: dictionary where the keys are terraform plan addresses + and values are the JSON representation (converted to python + types) of the attribute values of the resource. + + - counts: dictionary where the keys are the terraform resource + types and the values are the number of times that type appears + in the plan + + - outputs: dictionary of the modules outputs that can be + determined at plan type. + + Consult [1] for mode details on the structure of values and outputs + + [1] https://developer.hashicorp.com/terraform/internals/json-format + """ + module_path = Path(__file__).parents[1] / module_path + + # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not + # in the environment + with tempfile.TemporaryDirectory(dir=module_path.parent) as tmp_path: + # if TFTEST_COPY is set, copy the fixture to a temporary + # directory before running the plan. This is needed if you want + # to run multiple tests for the same module in parallel + if os.environ.get('TFTEST_COPY'): + test_path = Path(tmp_path) + shutil.copytree(module_path, test_path, dirs_exist_ok=True) + + # if we're copying the module, we might as well remove any files + # and directories from the test directory that are automatically + # read by terraform. Useful to avoid surprises if, for example, + # you have an active fast deployment with links to configs) + autopaths = itertools.chain( + test_path.glob('*.auto.tfvars'), + test_path.glob('*.auto.tfvars.json'), + test_path.glob('terraform.tfstate*'), + test_path.glob('terraform.tfvars'), + test_path.glob('.terraform'), + # any symlinks? + ) + for p in autopaths: + if p.is_dir(): + shutil.rmtree(p) + else: + p.unlink() + else: + test_path = module_path + + # prepare tftest and run plan + binary = os.environ.get('TERRAFORM', 'terraform') + tf = tftest.TerraformTest(test_path, binary=binary) + tf.setup(upgrade=True) + tf_var_files = [(basedir / x).resolve() for x in tf_var_files or []] + plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, + tf_vars=tf_vars) + + # compute resource type counts and address->values map + values = {} + counts = collections.defaultdict(int) + q = collections.deque([plan.root_module]) + while q: + e = q.popleft() + + if 'type' in e: + counts[e['type']] += 1 + if 'values' in e: + values[e['address']] = e['values'] + + for x in e.get('resources', []): + q.append(x) + for x in e.get('child_modules', []): + q.append(x) + + # extract planned outputs + outputs = plan.get('planned_values', {}).get('outputs', {}) + + return PlanSummary(values, dict(counts), outputs) + + +@pytest.fixture(name='generic_plan_summary') +def generic_plan_summary_fixture(request): + """Return a function to generate a PlanSummary. + + In the returned function `basedir` becomes optional and it defaults + to the directory of the calling test + """ + + def inner(module_path, basedir=None, tf_var_files=None, **tf_vars): + if basedir is None: + basedir = Path(request.fspath).parent + return generic_plan_summary(module_path=module_path, basedir=basedir, + tf_var_files=tf_var_files, **tf_vars) + + return inner + + +def generic_plan_validator(module_path, inventory_paths, basedir, + tf_var_files=None, **tf_vars): + summary = generic_plan_summary(module_path=module_path, + tf_var_files=tf_var_files, basedir=basedir, + **tf_vars) + + # allow single single string for inventory_paths + if not isinstance(inventory_paths, list): + inventory_paths = [inventory_paths] + + for path in inventory_paths: + # allow tfvars and inventory to be relative to the caller + path = basedir / path + inventory = yaml.safe_load(path.read_text()) + + # don't fail if the inventory is empty + inventory = inventory or {} + + # If you add additional asserts to this function: + # - put the values coming from the plan on the left side of + # any comparison operators + # - put the values coming from user's inventory the right + # side of any comparison operators. + # - include a descriptive error message to the assert + + # for values: + # - verify each address in the user's inventory exists in the plan + # - for those address that exist on both the user's inventory and + # the plan output, ensure the set of keys on the inventory are a + # subset of the keys in the plan, and compare their values by + # equality + if 'values' in inventory: + expected_values = inventory['values'] + for address, expected_value in expected_values.items(): + assert address in summary.values, \ + f'{address} is not a valid address in the plan' + for k, v in expected_value.items(): + assert k in summary.values[address], \ + f'{k} not found at {address}' + plan_value = summary.values[address][k] + assert plan_value == v, \ + f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' + + if 'counts' in inventory: + expected_counts = inventory['counts'] + for type_, expected_count in expected_counts.items(): + assert type_ in summary.counts, \ + f'module does not create any resources of type `{type_}`' + plan_count = summary.counts[type_] + assert plan_count == expected_count, \ + f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' + + if 'outputs' in inventory: + expected_outputs = inventory['outputs'] + for output_name, expected_output in expected_outputs.items(): + assert output_name in summary.outputs, \ + f'module does not output `{output_name}`' + output = summary.outputs[output_name] + # assert 'value' in output, \ + # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' + plan_output = output.get('value', '__missing__') + assert plan_output == expected_output, \ + f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' + + return summary + + +@pytest.fixture(name='generic_plan_validator') +def generic_plan_validator_fixture(request): + """Return a function to builds a PlanSummary and compare it to YAML + inventory. + + In the returned function `basedir` becomes optional and it defaults + to the directory of the calling test' + + """ + + def inner(module_path, inventory_paths, basedir=None, tf_var_files=None, + **tf_vars): + if basedir is None: + basedir = Path(request.fspath).parent + return generic_plan_validator(module_path=module_path, + inventory_paths=inventory_paths, + basedir=basedir, tf_var_files=tf_var_paths, + **tf_vars) + + return inner From 2af4a826fa6bb8bc86db056fd1976e26acf1a733 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sun, 4 Dec 2022 21:42:26 +0100 Subject: [PATCH 17/28] Initial FAST bootstrap fixture --- tests/fast/stages/s00_bootstrap/simple.yaml | 705 ++++++++++++++++++++ tests/fast/stages/s00_bootstrap/tftest.yaml | 8 + 2 files changed, 713 insertions(+) create mode 100644 tests/fast/stages/s00_bootstrap/simple.yaml create mode 100644 tests/fast/stages/s00_bootstrap/tftest.yaml diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml new file mode 100644 index 000000000..d8f6e36a0 --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/simple.yaml @@ -0,0 +1,705 @@ +# # TODO: missing all local_file and gcs objects +# values: +# google_organization_iam_binding.org_admin_delegated: +# condition: +# - description: Automation service account delegated grants. +# expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/billing.admin','roles/billing.costsManager','roles/billing.user']) +# title: automation_sa_delegated_grants +# members: +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: organizations/123456789012/roles/organizationIamAdmin +# module.automation-project.data.google_bigquery_default_service_account.bq_sa[0]: +# project: fast-prod-iac-core-0 +# module.automation-project.data.google_storage_project_service_account.gcs_sa[0]: +# project: fast-prod-iac-core-0 +# user_project: null +# module.automation-project.google_project.project[0]: +# auto_create_network: false +# billing_account: 000000-111111-222222 +# folder_id: null +# labels: null +# name: fast-prod-iac-core-0 +# org_id: '123456789012' +# project_id: fast-prod-iac-core-0 +# skip_delete: false +# module.automation-project.google_project_iam_binding.authoritative["roles/cloudbuild.builds.editor"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# project: fast-prod-iac-core-0 +# role: roles/cloudbuild.builds.editor +# module.automation-project.google_project_iam_binding.authoritative["roles/iam.serviceAccountAdmin"]: +# condition: [] +# members: +# - group:gcp-devops@fast.example.com +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# project: fast-prod-iac-core-0 +# role: roles/iam.serviceAccountAdmin +# module.automation-project.google_project_iam_binding.authoritative["roles/iam.serviceAccountTokenCreator"]: +# condition: [] +# members: +# - group:gcp-devops@fast.example.com +# - group:gcp-organization-admins@fast.example.com +# project: fast-prod-iac-core-0 +# role: roles/iam.serviceAccountTokenCreator +# module.automation-project.google_project_iam_binding.authoritative["roles/iam.workloadIdentityPoolAdmin"]: +# condition: [] +# members: +# - group:gcp-organization-admins@fast.example.com +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# project: fast-prod-iac-core-0 +# role: roles/iam.workloadIdentityPoolAdmin +# module.automation-project.google_project_iam_binding.authoritative["roles/owner"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# project: fast-prod-iac-core-0 +# role: roles/owner +# module.automation-project.google_project_iam_binding.authoritative["roles/source.admin"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# project: fast-prod-iac-core-0 +# role: roles/source.admin +# module.automation-project.google_project_iam_binding.authoritative["roles/storage.admin"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# project: fast-prod-iac-core-0 +# role: roles/storage.admin +# module.automation-project.google_project_iam_member.servicenetworking[0]: +# condition: [] +# project: fast-prod-iac-core-0 +# role: roles/servicenetworking.serviceAgent +# module.automation-project.google_project_service.project_services["accesscontextmanager.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: accesscontextmanager.googleapis.com +# module.automation-project.google_project_service.project_services["bigquery.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: bigquery.googleapis.com +# module.automation-project.google_project_service.project_services["bigqueryreservation.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: bigqueryreservation.googleapis.com +# module.automation-project.google_project_service.project_services["bigquerystorage.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: bigquerystorage.googleapis.com +# module.automation-project.google_project_service.project_services["billingbudgets.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: billingbudgets.googleapis.com +# module.automation-project.google_project_service.project_services["cloudbilling.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: cloudbilling.googleapis.com +# module.automation-project.google_project_service.project_services["cloudbuild.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: cloudbuild.googleapis.com +# module.automation-project.google_project_service.project_services["cloudkms.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: cloudkms.googleapis.com +# module.automation-project.google_project_service.project_services["cloudresourcemanager.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: cloudresourcemanager.googleapis.com +# module.automation-project.google_project_service.project_services["compute.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: compute.googleapis.com +# module.automation-project.google_project_service.project_services["container.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: container.googleapis.com +# module.automation-project.google_project_service.project_services["essentialcontacts.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: essentialcontacts.googleapis.com +# module.automation-project.google_project_service.project_services["iam.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: iam.googleapis.com +# module.automation-project.google_project_service.project_services["iamcredentials.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: iamcredentials.googleapis.com +# module.automation-project.google_project_service.project_services["orgpolicy.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: orgpolicy.googleapis.com +# module.automation-project.google_project_service.project_services["pubsub.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: pubsub.googleapis.com +# module.automation-project.google_project_service.project_services["servicenetworking.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: servicenetworking.googleapis.com +# module.automation-project.google_project_service.project_services["serviceusage.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: serviceusage.googleapis.com +# module.automation-project.google_project_service.project_services["sourcerepo.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: sourcerepo.googleapis.com +# module.automation-project.google_project_service.project_services["stackdriver.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: stackdriver.googleapis.com +# module.automation-project.google_project_service.project_services["storage-component.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: storage-component.googleapis.com +# module.automation-project.google_project_service.project_services["storage.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: storage.googleapis.com +# module.automation-project.google_project_service.project_services["sts.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-iac-core-0 +# service: sts.googleapis.com +# module.automation-project.google_project_service_identity.jit_si["pubsub.googleapis.com"]: +# project: fast-prod-iac-core-0 +# service: pubsub.googleapis.com +# module.automation-project.google_project_service_identity.servicenetworking[0]: +# project: fast-prod-iac-core-0 +# service: servicenetworking.googleapis.com +# module.automation-tf-bootstrap-gcs.google_storage_bucket.bucket: +# cors: [] +# custom_placement_config: [] +# default_event_based_hold: null +# encryption: [] +# force_destroy: false +# labels: null +# lifecycle_rule: [] +# location: EU +# logging: [] +# name: fast-prod-iac-core-bootstrap-0 +# project: fast-prod-iac-core-0 +# requester_pays: null +# retention_policy: [] +# storage_class: MULTI_REGIONAL +# uniform_bucket_level_access: true +# versioning: +# - enabled: true +# website: [] +# module.automation-tf-bootstrap-sa.google_service_account.service_account[0]: +# account_id: fast-prod-bootstrap-0 +# description: null +# disabled: false +# display_name: Terraform organization bootstrap service account. +# project: fast-prod-iac-core-0 +# module.automation-tf-bootstrap-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: +# condition: [] +# members: null +# role: roles/iam.serviceAccountTokenCreator +# module.automation-tf-bootstrap-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: +# bucket: fast-prod-iac-core-outputs-0 +# condition: [] +# role: roles/storage.admin +# module.automation-tf-cicd-gcs.google_storage_bucket.bucket: +# cors: [] +# custom_placement_config: [] +# default_event_based_hold: null +# encryption: [] +# force_destroy: false +# labels: null +# lifecycle_rule: [] +# location: EU +# logging: [] +# name: fast-prod-iac-core-cicd-0 +# project: fast-prod-iac-core-0 +# requester_pays: null +# retention_policy: [] +# storage_class: MULTI_REGIONAL +# uniform_bucket_level_access: true +# versioning: +# - enabled: true +# website: [] +# module.automation-tf-cicd-gcs.google_storage_bucket_iam_binding.bindings["roles/storage.objectAdmin"]: +# bucket: fast-prod-iac-core-cicd-0 +# condition: [] +# members: +# - serviceAccount:fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# role: roles/storage.objectAdmin +# module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]: +# account_id: fast-prod-cicd-0 +# description: null +# disabled: false +# display_name: Terraform stage 1 CICD service account. +# project: fast-prod-iac-core-0 +# module.automation-tf-cicd-provisioning-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: +# condition: [] +# members: null +# role: roles/iam.serviceAccountTokenCreator +# module.automation-tf-cicd-provisioning-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: +# bucket: fast-prod-iac-core-outputs-0 +# condition: [] +# role: roles/storage.admin +# module.automation-tf-output-gcs.google_storage_bucket.bucket: +# cors: [] +# custom_placement_config: [] +# default_event_based_hold: null +# encryption: [] +# force_destroy: false +# labels: null +# lifecycle_rule: [] +# location: EU +# logging: [] +# name: fast-prod-iac-core-outputs-0 +# project: fast-prod-iac-core-0 +# requester_pays: null +# retention_policy: [] +# storage_class: MULTI_REGIONAL +# uniform_bucket_level_access: true +# versioning: +# - enabled: true +# website: [] +# module.automation-tf-resman-gcs.google_storage_bucket.bucket: +# cors: [] +# custom_placement_config: [] +# default_event_based_hold: null +# encryption: [] +# force_destroy: false +# labels: null +# lifecycle_rule: [] +# location: EU +# logging: [] +# name: fast-prod-iac-core-resman-0 +# project: fast-prod-iac-core-0 +# requester_pays: null +# retention_policy: [] +# storage_class: MULTI_REGIONAL +# uniform_bucket_level_access: true +# versioning: +# - enabled: true +# website: [] +# module.automation-tf-resman-gcs.google_storage_bucket_iam_binding.bindings["roles/storage.objectAdmin"]: +# bucket: fast-prod-iac-core-resman-0 +# condition: [] +# members: +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# role: roles/storage.objectAdmin +# module.automation-tf-resman-sa.google_service_account.service_account[0]: +# account_id: fast-prod-resman-0 +# description: null +# disabled: false +# display_name: Terraform stage 1 resman service account. +# project: fast-prod-iac-core-0 +# module.automation-tf-resman-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: +# condition: [] +# members: null +# role: roles/iam.serviceAccountTokenCreator +# module.automation-tf-resman-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: +# bucket: fast-prod-iac-core-outputs-0 +# condition: [] +# role: roles/storage.admin +# module.billing-export-dataset[0].google_bigquery_dataset.default: +# dataset_id: billing_export +# default_encryption_configuration: [] +# default_partition_expiration_ms: null +# default_table_expiration_ms: null +# delete_contents_on_destroy: false +# description: Terraform managed. +# friendly_name: Billing export. +# labels: null +# location: EU +# project: fast-prod-billing-exp-0 +# module.billing-export-project[0].data.google_bigquery_default_service_account.bq_sa[0]: +# project: fast-prod-billing-exp-0 +# module.billing-export-project[0].data.google_storage_project_service_account.gcs_sa[0]: +# project: fast-prod-billing-exp-0 +# user_project: null +# module.billing-export-project[0].google_project.project[0]: +# auto_create_network: false +# billing_account: 000000-111111-222222 +# folder_id: null +# labels: null +# name: fast-prod-billing-exp-0 +# org_id: '123456789012' +# project_id: fast-prod-billing-exp-0 +# skip_delete: false +# module.billing-export-project[0].google_project_iam_binding.authoritative["roles/owner"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# project: fast-prod-billing-exp-0 +# role: roles/owner +# module.billing-export-project[0].google_project_service.project_services["bigquery.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-billing-exp-0 +# service: bigquery.googleapis.com +# module.billing-export-project[0].google_project_service.project_services["bigquerydatatransfer.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-billing-exp-0 +# service: bigquerydatatransfer.googleapis.com +# module.billing-export-project[0].google_project_service.project_services["storage.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-billing-exp-0 +# service: storage.googleapis.com +# module.log-export-dataset[0].google_bigquery_dataset.default: +# dataset_id: audit_export +# default_encryption_configuration: [] +# default_partition_expiration_ms: null +# default_table_expiration_ms: null +# delete_contents_on_destroy: false +# description: Terraform managed. +# friendly_name: Audit logs export. +# labels: null +# location: EU +# project: fast-prod-audit-logs-0 +# module.log-export-project.data.google_bigquery_default_service_account.bq_sa[0]: +# project: fast-prod-audit-logs-0 +# module.log-export-project.data.google_storage_project_service_account.gcs_sa[0]: +# project: fast-prod-audit-logs-0 +# user_project: null +# module.log-export-project.google_project.project[0]: +# auto_create_network: false +# billing_account: 000000-111111-222222 +# folder_id: null +# labels: null +# name: fast-prod-audit-logs-0 +# org_id: '123456789012' +# project_id: fast-prod-audit-logs-0 +# skip_delete: false +# module.log-export-project.google_project_iam_binding.authoritative["roles/owner"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# project: fast-prod-audit-logs-0 +# role: roles/owner +# module.log-export-project.google_project_service.project_services["bigquery.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-audit-logs-0 +# service: bigquery.googleapis.com +# module.log-export-project.google_project_service.project_services["stackdriver.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-audit-logs-0 +# service: stackdriver.googleapis.com +# module.log-export-project.google_project_service.project_services["storage.googleapis.com"]: +# disable_dependent_services: false +# disable_on_destroy: false +# project: fast-prod-audit-logs-0 +# service: storage.googleapis.com +# module.organization.google_bigquery_dataset_iam_member.bq-sinks-binding["audit-logs"]: +# condition: [] +# role: roles/bigquery.dataEditor +# module.organization.google_bigquery_dataset_iam_member.bq-sinks-binding["vpc-sc"]: +# condition: [] +# role: roles/bigquery.dataEditor +# module.organization.google_logging_organization_sink.sink["audit-logs"]: +# description: audit-logs (Terraform-managed). +# disabled: false +# exclusions: [] +# filter: logName:"/logs/cloudaudit.googleapis.com%2Factivity" OR logName:"/logs/cloudaudit.googleapis.com%2Fsystem_event" +# include_children: true +# name: audit-logs +# org_id: '123456789012' +# module.organization.google_logging_organization_sink.sink["vpc-sc"]: +# description: vpc-sc (Terraform-managed). +# disabled: false +# exclusions: [] +# filter: protoPayload.metadata.@type="type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata" +# include_children: true +# name: vpc-sc +# org_id: '123456789012' +# module.organization.google_organization_iam_binding.authoritative["roles/browser"]: +# condition: [] +# members: +# - domain:fast.example.com +# org_id: '123456789012' +# role: roles/browser +# module.organization.google_organization_iam_binding.authoritative["roles/cloudasset.owner"]: +# condition: [] +# members: +# - group:gcp-network-admins@fast.example.com +# - group:gcp-organization-admins@fast.example.com +# - group:gcp-security-admins@fast.example.com +# org_id: '123456789012' +# role: roles/cloudasset.owner +# module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.admin"]: +# condition: [] +# members: +# - group:gcp-organization-admins@fast.example.com +# org_id: '123456789012' +# role: roles/cloudsupport.admin +# module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.techSupportEditor"]: +# condition: [] +# members: +# - group:gcp-devops@fast.example.com +# - group:gcp-network-admins@fast.example.com +# - group:gcp-security-admins@fast.example.com +# org_id: '123456789012' +# role: roles/cloudsupport.techSupportEditor +# module.organization.google_organization_iam_binding.authoritative["roles/compute.osAdminLogin"]: +# condition: [] +# members: +# - group:gcp-organization-admins@fast.example.com +# org_id: '123456789012' +# role: roles/compute.osAdminLogin +# module.organization.google_organization_iam_binding.authoritative["roles/compute.osLoginExternalUser"]: +# condition: [] +# members: +# - group:gcp-organization-admins@fast.example.com +# org_id: '123456789012' +# role: roles/compute.osLoginExternalUser +# module.organization.google_organization_iam_binding.authoritative["roles/iam.securityReviewer"]: +# condition: [] +# members: +# - group:gcp-security-admins@fast.example.com +# org_id: '123456789012' +# role: roles/iam.securityReviewer +# module.organization.google_organization_iam_binding.authoritative["roles/logging.admin"]: +# condition: [] +# members: +# - group:gcp-security-admins@fast.example.com +# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/logging.admin +# module.organization.google_organization_iam_binding.authoritative["roles/logging.viewer"]: +# condition: [] +# members: +# - group:gcp-devops@fast.example.com +# org_id: '123456789012' +# role: roles/logging.viewer +# module.organization.google_organization_iam_binding.authoritative["roles/monitoring.viewer"]: +# condition: [] +# members: +# - group:gcp-devops@fast.example.com +# org_id: '123456789012' +# role: roles/monitoring.viewer +# module.organization.google_organization_iam_binding.authoritative["roles/owner"]: +# condition: [] +# members: +# - group:gcp-organization-admins@fast.example.com +# org_id: '123456789012' +# role: roles/owner +# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.folderAdmin"]: +# condition: [] +# members: +# - group:gcp-organization-admins@fast.example.com +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/resourcemanager.folderAdmin +# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.organizationAdmin"]: +# condition: [] +# members: +# - group:gcp-organization-admins@fast.example.com +# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/resourcemanager.organizationAdmin +# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.projectCreator"]: +# condition: [] +# members: +# - group:gcp-organization-admins@fast.example.com +# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/resourcemanager.projectCreator +# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.projectMover"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/resourcemanager.projectMover +# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.tagAdmin"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/resourcemanager.tagAdmin +# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.tagUser"]: +# condition: [] +# members: +# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/resourcemanager.tagUser +# module.organization.google_organization_iam_binding.authoritative["roles/securitycenter.admin"]: +# condition: [] +# members: +# - group:gcp-security-admins@fast.example.com +# org_id: '123456789012' +# role: roles/securitycenter.admin +# module.organization.google_organization_iam_custom_role.roles["organizationIamAdmin"]: +# description: Terraform-managed. +# org_id: '123456789012' +# permissions: +# - resourcemanager.organizations.get +# - resourcemanager.organizations.getIamPolicy +# - resourcemanager.organizations.setIamPolicy +# role_id: organizationIamAdmin +# stage: GA +# title: Custom role organizationIamAdmin +# module.organization.google_organization_iam_custom_role.roles["serviceProjectNetworkAdmin"]: +# description: Terraform-managed. +# org_id: '123456789012' +# permissions: +# - compute.globalOperations.get +# - compute.networks.get +# - compute.networks.updatePeering +# - compute.organizations.disableXpnResource +# - compute.organizations.enableXpnResource +# - compute.projects.get +# - compute.subnetworks.getIamPolicy +# - compute.subnetworks.setIamPolicy +# - dns.networks.bindPrivateDNSZone +# - resourcemanager.projects.get +# role_id: serviceProjectNetworkAdmin +# stage: GA +# title: Custom role serviceProjectNetworkAdmin +# module.organization.google_organization_iam_member.additive["roles/accesscontextmanager.policyAdmin-group:gcp-security-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-security-admins@fast.example.com +# org_id: '123456789012' +# role: roles/accesscontextmanager.policyAdmin +# module.organization.google_organization_iam_member.additive["roles/billing.admin-group:gcp-billing-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-billing-admins@fast.example.com +# org_id: '123456789012' +# role: roles/billing.admin +# module.organization.google_organization_iam_member.additive["roles/billing.admin-group:gcp-organization-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-organization-admins@fast.example.com +# org_id: '123456789012' +# role: roles/billing.admin +# module.organization.google_organization_iam_member.additive["roles/billing.admin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: +# condition: [] +# member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/billing.admin +# module.organization.google_organization_iam_member.additive["roles/billing.admin-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: +# condition: [] +# member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/billing.admin +# module.organization.google_organization_iam_member.additive["roles/billing.costsManager-group:gcp-billing-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-billing-admins@fast.example.com +# org_id: '123456789012' +# role: roles/billing.costsManager +# module.organization.google_organization_iam_member.additive["roles/billing.costsManager-group:gcp-organization-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-organization-admins@fast.example.com +# org_id: '123456789012' +# role: roles/billing.costsManager +# module.organization.google_organization_iam_member.additive["roles/billing.costsManager-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: +# condition: [] +# member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/billing.costsManager +# module.organization.google_organization_iam_member.additive["roles/billing.costsManager-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: +# condition: [] +# member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/billing.costsManager +# module.organization.google_organization_iam_member.additive["roles/compute.orgFirewallPolicyAdmin-group:gcp-network-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-network-admins@fast.example.com +# org_id: '123456789012' +# role: roles/compute.orgFirewallPolicyAdmin +# module.organization.google_organization_iam_member.additive["roles/compute.xpnAdmin-group:gcp-network-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-network-admins@fast.example.com +# org_id: '123456789012' +# role: roles/compute.xpnAdmin +# module.organization.google_organization_iam_member.additive["roles/iam.organizationRoleAdmin-group:gcp-security-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-security-admins@fast.example.com +# org_id: '123456789012' +# role: roles/iam.organizationRoleAdmin +# module.organization.google_organization_iam_member.additive["roles/iam.organizationRoleAdmin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: +# condition: [] +# member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/iam.organizationRoleAdmin +# module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-group:gcp-organization-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-organization-admins@fast.example.com +# org_id: '123456789012' +# role: roles/orgpolicy.policyAdmin +# module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-group:gcp-security-admins@fast.example.com"]: +# condition: [] +# member: group:gcp-security-admins@fast.example.com +# org_id: '123456789012' +# role: roles/orgpolicy.policyAdmin +# module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: +# condition: [] +# member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# org_id: '123456789012' +# role: roles/orgpolicy.policyAdmin + +# counts: +# google_bigquery_dataset: 2 +# google_bigquery_dataset_iam_member: 2 +# google_bigquery_default_service_account: 3 +# google_logging_organization_sink: 2 +# google_organization_iam_binding: 19 +# google_organization_iam_custom_role: 2 +# google_organization_iam_member: 16 +# google_project: 3 +# google_project_iam_binding: 9 +# google_project_iam_member: 1 +# google_project_service: 29 +# google_project_service_identity: 2 +# google_service_account: 3 +# google_service_account_iam_binding: 3 +# google_storage_bucket: 4 +# google_storage_bucket_iam_binding: 2 +# google_storage_bucket_iam_member: 3 +# google_storage_bucket_object: 5 +# google_storage_project_service_account: 3 +# local_file: 5 + +# outputs: +# automation: __missing__ +# billing_dataset: __missing__ +# cicd_repositories: {} +# custom_roles: +# organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin +# service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin +# federated_identity: +# pool: null +# providers: {} +# outputs_bucket: fast-prod-iac-core-outputs-0 +# project_ids: +# automation: fast-prod-iac-core-0 +# billing-export: fast-prod-billing-exp-0 +# log-export: fast-prod-audit-logs-0 +# service_accounts: +# bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com +# tfvars: __missing__ diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s00_bootstrap/tftest.yaml new file mode 100644 index 000000000..3c57e3229 --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/tftest.yaml @@ -0,0 +1,8 @@ +# skip boilerplate check + +module: fast/stages/00-bootstrap + +tests: + simple: + tfvars: + - simple.tfvars From 34f01762c3676b26ccb9b4ac9f70edc00df81e3d Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Sun, 4 Dec 2022 22:26:06 +0100 Subject: [PATCH 18/28] Simplify fast bootstrap test --- tests/fast/stages/s00_bootstrap/simple.yaml | 750 ++---------------- .../stages/s00_bootstrap/simple_projects.yaml | 33 + .../fast/stages/s00_bootstrap/simple_sas.yaml | 27 + tests/fast/stages/s00_bootstrap/tftest.yaml | 4 + 4 files changed, 111 insertions(+), 703 deletions(-) create mode 100644 tests/fast/stages/s00_bootstrap/simple_projects.yaml create mode 100644 tests/fast/stages/s00_bootstrap/simple_sas.yaml diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml index d8f6e36a0..703b84b45 100644 --- a/tests/fast/stages/s00_bootstrap/simple.yaml +++ b/tests/fast/stages/s00_bootstrap/simple.yaml @@ -1,705 +1,49 @@ -# # TODO: missing all local_file and gcs objects -# values: -# google_organization_iam_binding.org_admin_delegated: -# condition: -# - description: Automation service account delegated grants. -# expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/billing.admin','roles/billing.costsManager','roles/billing.user']) -# title: automation_sa_delegated_grants -# members: -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: organizations/123456789012/roles/organizationIamAdmin -# module.automation-project.data.google_bigquery_default_service_account.bq_sa[0]: -# project: fast-prod-iac-core-0 -# module.automation-project.data.google_storage_project_service_account.gcs_sa[0]: -# project: fast-prod-iac-core-0 -# user_project: null -# module.automation-project.google_project.project[0]: -# auto_create_network: false -# billing_account: 000000-111111-222222 -# folder_id: null -# labels: null -# name: fast-prod-iac-core-0 -# org_id: '123456789012' -# project_id: fast-prod-iac-core-0 -# skip_delete: false -# module.automation-project.google_project_iam_binding.authoritative["roles/cloudbuild.builds.editor"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# project: fast-prod-iac-core-0 -# role: roles/cloudbuild.builds.editor -# module.automation-project.google_project_iam_binding.authoritative["roles/iam.serviceAccountAdmin"]: -# condition: [] -# members: -# - group:gcp-devops@fast.example.com -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# project: fast-prod-iac-core-0 -# role: roles/iam.serviceAccountAdmin -# module.automation-project.google_project_iam_binding.authoritative["roles/iam.serviceAccountTokenCreator"]: -# condition: [] -# members: -# - group:gcp-devops@fast.example.com -# - group:gcp-organization-admins@fast.example.com -# project: fast-prod-iac-core-0 -# role: roles/iam.serviceAccountTokenCreator -# module.automation-project.google_project_iam_binding.authoritative["roles/iam.workloadIdentityPoolAdmin"]: -# condition: [] -# members: -# - group:gcp-organization-admins@fast.example.com -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# project: fast-prod-iac-core-0 -# role: roles/iam.workloadIdentityPoolAdmin -# module.automation-project.google_project_iam_binding.authoritative["roles/owner"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# project: fast-prod-iac-core-0 -# role: roles/owner -# module.automation-project.google_project_iam_binding.authoritative["roles/source.admin"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# project: fast-prod-iac-core-0 -# role: roles/source.admin -# module.automation-project.google_project_iam_binding.authoritative["roles/storage.admin"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# project: fast-prod-iac-core-0 -# role: roles/storage.admin -# module.automation-project.google_project_iam_member.servicenetworking[0]: -# condition: [] -# project: fast-prod-iac-core-0 -# role: roles/servicenetworking.serviceAgent -# module.automation-project.google_project_service.project_services["accesscontextmanager.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: accesscontextmanager.googleapis.com -# module.automation-project.google_project_service.project_services["bigquery.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: bigquery.googleapis.com -# module.automation-project.google_project_service.project_services["bigqueryreservation.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: bigqueryreservation.googleapis.com -# module.automation-project.google_project_service.project_services["bigquerystorage.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: bigquerystorage.googleapis.com -# module.automation-project.google_project_service.project_services["billingbudgets.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: billingbudgets.googleapis.com -# module.automation-project.google_project_service.project_services["cloudbilling.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: cloudbilling.googleapis.com -# module.automation-project.google_project_service.project_services["cloudbuild.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: cloudbuild.googleapis.com -# module.automation-project.google_project_service.project_services["cloudkms.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: cloudkms.googleapis.com -# module.automation-project.google_project_service.project_services["cloudresourcemanager.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: cloudresourcemanager.googleapis.com -# module.automation-project.google_project_service.project_services["compute.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: compute.googleapis.com -# module.automation-project.google_project_service.project_services["container.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: container.googleapis.com -# module.automation-project.google_project_service.project_services["essentialcontacts.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: essentialcontacts.googleapis.com -# module.automation-project.google_project_service.project_services["iam.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: iam.googleapis.com -# module.automation-project.google_project_service.project_services["iamcredentials.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: iamcredentials.googleapis.com -# module.automation-project.google_project_service.project_services["orgpolicy.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: orgpolicy.googleapis.com -# module.automation-project.google_project_service.project_services["pubsub.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: pubsub.googleapis.com -# module.automation-project.google_project_service.project_services["servicenetworking.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: servicenetworking.googleapis.com -# module.automation-project.google_project_service.project_services["serviceusage.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: serviceusage.googleapis.com -# module.automation-project.google_project_service.project_services["sourcerepo.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: sourcerepo.googleapis.com -# module.automation-project.google_project_service.project_services["stackdriver.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: stackdriver.googleapis.com -# module.automation-project.google_project_service.project_services["storage-component.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: storage-component.googleapis.com -# module.automation-project.google_project_service.project_services["storage.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: storage.googleapis.com -# module.automation-project.google_project_service.project_services["sts.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-iac-core-0 -# service: sts.googleapis.com -# module.automation-project.google_project_service_identity.jit_si["pubsub.googleapis.com"]: -# project: fast-prod-iac-core-0 -# service: pubsub.googleapis.com -# module.automation-project.google_project_service_identity.servicenetworking[0]: -# project: fast-prod-iac-core-0 -# service: servicenetworking.googleapis.com -# module.automation-tf-bootstrap-gcs.google_storage_bucket.bucket: -# cors: [] -# custom_placement_config: [] -# default_event_based_hold: null -# encryption: [] -# force_destroy: false -# labels: null -# lifecycle_rule: [] -# location: EU -# logging: [] -# name: fast-prod-iac-core-bootstrap-0 -# project: fast-prod-iac-core-0 -# requester_pays: null -# retention_policy: [] -# storage_class: MULTI_REGIONAL -# uniform_bucket_level_access: true -# versioning: -# - enabled: true -# website: [] -# module.automation-tf-bootstrap-sa.google_service_account.service_account[0]: -# account_id: fast-prod-bootstrap-0 -# description: null -# disabled: false -# display_name: Terraform organization bootstrap service account. -# project: fast-prod-iac-core-0 -# module.automation-tf-bootstrap-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: -# condition: [] -# members: null -# role: roles/iam.serviceAccountTokenCreator -# module.automation-tf-bootstrap-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: -# bucket: fast-prod-iac-core-outputs-0 -# condition: [] -# role: roles/storage.admin -# module.automation-tf-cicd-gcs.google_storage_bucket.bucket: -# cors: [] -# custom_placement_config: [] -# default_event_based_hold: null -# encryption: [] -# force_destroy: false -# labels: null -# lifecycle_rule: [] -# location: EU -# logging: [] -# name: fast-prod-iac-core-cicd-0 -# project: fast-prod-iac-core-0 -# requester_pays: null -# retention_policy: [] -# storage_class: MULTI_REGIONAL -# uniform_bucket_level_access: true -# versioning: -# - enabled: true -# website: [] -# module.automation-tf-cicd-gcs.google_storage_bucket_iam_binding.bindings["roles/storage.objectAdmin"]: -# bucket: fast-prod-iac-core-cicd-0 -# condition: [] -# members: -# - serviceAccount:fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# role: roles/storage.objectAdmin -# module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]: -# account_id: fast-prod-cicd-0 -# description: null -# disabled: false -# display_name: Terraform stage 1 CICD service account. -# project: fast-prod-iac-core-0 -# module.automation-tf-cicd-provisioning-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: -# condition: [] -# members: null -# role: roles/iam.serviceAccountTokenCreator -# module.automation-tf-cicd-provisioning-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: -# bucket: fast-prod-iac-core-outputs-0 -# condition: [] -# role: roles/storage.admin -# module.automation-tf-output-gcs.google_storage_bucket.bucket: -# cors: [] -# custom_placement_config: [] -# default_event_based_hold: null -# encryption: [] -# force_destroy: false -# labels: null -# lifecycle_rule: [] -# location: EU -# logging: [] -# name: fast-prod-iac-core-outputs-0 -# project: fast-prod-iac-core-0 -# requester_pays: null -# retention_policy: [] -# storage_class: MULTI_REGIONAL -# uniform_bucket_level_access: true -# versioning: -# - enabled: true -# website: [] -# module.automation-tf-resman-gcs.google_storage_bucket.bucket: -# cors: [] -# custom_placement_config: [] -# default_event_based_hold: null -# encryption: [] -# force_destroy: false -# labels: null -# lifecycle_rule: [] -# location: EU -# logging: [] -# name: fast-prod-iac-core-resman-0 -# project: fast-prod-iac-core-0 -# requester_pays: null -# retention_policy: [] -# storage_class: MULTI_REGIONAL -# uniform_bucket_level_access: true -# versioning: -# - enabled: true -# website: [] -# module.automation-tf-resman-gcs.google_storage_bucket_iam_binding.bindings["roles/storage.objectAdmin"]: -# bucket: fast-prod-iac-core-resman-0 -# condition: [] -# members: -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# role: roles/storage.objectAdmin -# module.automation-tf-resman-sa.google_service_account.service_account[0]: -# account_id: fast-prod-resman-0 -# description: null -# disabled: false -# display_name: Terraform stage 1 resman service account. -# project: fast-prod-iac-core-0 -# module.automation-tf-resman-sa.google_service_account_iam_binding.roles["roles/iam.serviceAccountTokenCreator"]: -# condition: [] -# members: null -# role: roles/iam.serviceAccountTokenCreator -# module.automation-tf-resman-sa.google_storage_bucket_iam_member.bucket-roles["fast-prod-iac-core-outputs-0-roles/storage.admin"]: -# bucket: fast-prod-iac-core-outputs-0 -# condition: [] -# role: roles/storage.admin -# module.billing-export-dataset[0].google_bigquery_dataset.default: -# dataset_id: billing_export -# default_encryption_configuration: [] -# default_partition_expiration_ms: null -# default_table_expiration_ms: null -# delete_contents_on_destroy: false -# description: Terraform managed. -# friendly_name: Billing export. -# labels: null -# location: EU -# project: fast-prod-billing-exp-0 -# module.billing-export-project[0].data.google_bigquery_default_service_account.bq_sa[0]: -# project: fast-prod-billing-exp-0 -# module.billing-export-project[0].data.google_storage_project_service_account.gcs_sa[0]: -# project: fast-prod-billing-exp-0 -# user_project: null -# module.billing-export-project[0].google_project.project[0]: -# auto_create_network: false -# billing_account: 000000-111111-222222 -# folder_id: null -# labels: null -# name: fast-prod-billing-exp-0 -# org_id: '123456789012' -# project_id: fast-prod-billing-exp-0 -# skip_delete: false -# module.billing-export-project[0].google_project_iam_binding.authoritative["roles/owner"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# project: fast-prod-billing-exp-0 -# role: roles/owner -# module.billing-export-project[0].google_project_service.project_services["bigquery.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-billing-exp-0 -# service: bigquery.googleapis.com -# module.billing-export-project[0].google_project_service.project_services["bigquerydatatransfer.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-billing-exp-0 -# service: bigquerydatatransfer.googleapis.com -# module.billing-export-project[0].google_project_service.project_services["storage.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-billing-exp-0 -# service: storage.googleapis.com -# module.log-export-dataset[0].google_bigquery_dataset.default: -# dataset_id: audit_export -# default_encryption_configuration: [] -# default_partition_expiration_ms: null -# default_table_expiration_ms: null -# delete_contents_on_destroy: false -# description: Terraform managed. -# friendly_name: Audit logs export. -# labels: null -# location: EU -# project: fast-prod-audit-logs-0 -# module.log-export-project.data.google_bigquery_default_service_account.bq_sa[0]: -# project: fast-prod-audit-logs-0 -# module.log-export-project.data.google_storage_project_service_account.gcs_sa[0]: -# project: fast-prod-audit-logs-0 -# user_project: null -# module.log-export-project.google_project.project[0]: -# auto_create_network: false -# billing_account: 000000-111111-222222 -# folder_id: null -# labels: null -# name: fast-prod-audit-logs-0 -# org_id: '123456789012' -# project_id: fast-prod-audit-logs-0 -# skip_delete: false -# module.log-export-project.google_project_iam_binding.authoritative["roles/owner"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# project: fast-prod-audit-logs-0 -# role: roles/owner -# module.log-export-project.google_project_service.project_services["bigquery.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-audit-logs-0 -# service: bigquery.googleapis.com -# module.log-export-project.google_project_service.project_services["stackdriver.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-audit-logs-0 -# service: stackdriver.googleapis.com -# module.log-export-project.google_project_service.project_services["storage.googleapis.com"]: -# disable_dependent_services: false -# disable_on_destroy: false -# project: fast-prod-audit-logs-0 -# service: storage.googleapis.com -# module.organization.google_bigquery_dataset_iam_member.bq-sinks-binding["audit-logs"]: -# condition: [] -# role: roles/bigquery.dataEditor -# module.organization.google_bigquery_dataset_iam_member.bq-sinks-binding["vpc-sc"]: -# condition: [] -# role: roles/bigquery.dataEditor -# module.organization.google_logging_organization_sink.sink["audit-logs"]: -# description: audit-logs (Terraform-managed). -# disabled: false -# exclusions: [] -# filter: logName:"/logs/cloudaudit.googleapis.com%2Factivity" OR logName:"/logs/cloudaudit.googleapis.com%2Fsystem_event" -# include_children: true -# name: audit-logs -# org_id: '123456789012' -# module.organization.google_logging_organization_sink.sink["vpc-sc"]: -# description: vpc-sc (Terraform-managed). -# disabled: false -# exclusions: [] -# filter: protoPayload.metadata.@type="type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata" -# include_children: true -# name: vpc-sc -# org_id: '123456789012' -# module.organization.google_organization_iam_binding.authoritative["roles/browser"]: -# condition: [] -# members: -# - domain:fast.example.com -# org_id: '123456789012' -# role: roles/browser -# module.organization.google_organization_iam_binding.authoritative["roles/cloudasset.owner"]: -# condition: [] -# members: -# - group:gcp-network-admins@fast.example.com -# - group:gcp-organization-admins@fast.example.com -# - group:gcp-security-admins@fast.example.com -# org_id: '123456789012' -# role: roles/cloudasset.owner -# module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.admin"]: -# condition: [] -# members: -# - group:gcp-organization-admins@fast.example.com -# org_id: '123456789012' -# role: roles/cloudsupport.admin -# module.organization.google_organization_iam_binding.authoritative["roles/cloudsupport.techSupportEditor"]: -# condition: [] -# members: -# - group:gcp-devops@fast.example.com -# - group:gcp-network-admins@fast.example.com -# - group:gcp-security-admins@fast.example.com -# org_id: '123456789012' -# role: roles/cloudsupport.techSupportEditor -# module.organization.google_organization_iam_binding.authoritative["roles/compute.osAdminLogin"]: -# condition: [] -# members: -# - group:gcp-organization-admins@fast.example.com -# org_id: '123456789012' -# role: roles/compute.osAdminLogin -# module.organization.google_organization_iam_binding.authoritative["roles/compute.osLoginExternalUser"]: -# condition: [] -# members: -# - group:gcp-organization-admins@fast.example.com -# org_id: '123456789012' -# role: roles/compute.osLoginExternalUser -# module.organization.google_organization_iam_binding.authoritative["roles/iam.securityReviewer"]: -# condition: [] -# members: -# - group:gcp-security-admins@fast.example.com -# org_id: '123456789012' -# role: roles/iam.securityReviewer -# module.organization.google_organization_iam_binding.authoritative["roles/logging.admin"]: -# condition: [] -# members: -# - group:gcp-security-admins@fast.example.com -# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/logging.admin -# module.organization.google_organization_iam_binding.authoritative["roles/logging.viewer"]: -# condition: [] -# members: -# - group:gcp-devops@fast.example.com -# org_id: '123456789012' -# role: roles/logging.viewer -# module.organization.google_organization_iam_binding.authoritative["roles/monitoring.viewer"]: -# condition: [] -# members: -# - group:gcp-devops@fast.example.com -# org_id: '123456789012' -# role: roles/monitoring.viewer -# module.organization.google_organization_iam_binding.authoritative["roles/owner"]: -# condition: [] -# members: -# - group:gcp-organization-admins@fast.example.com -# org_id: '123456789012' -# role: roles/owner -# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.folderAdmin"]: -# condition: [] -# members: -# - group:gcp-organization-admins@fast.example.com -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/resourcemanager.folderAdmin -# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.organizationAdmin"]: -# condition: [] -# members: -# - group:gcp-organization-admins@fast.example.com -# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/resourcemanager.organizationAdmin -# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.projectCreator"]: -# condition: [] -# members: -# - group:gcp-organization-admins@fast.example.com -# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/resourcemanager.projectCreator -# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.projectMover"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/resourcemanager.projectMover -# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.tagAdmin"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/resourcemanager.tagAdmin -# module.organization.google_organization_iam_binding.authoritative["roles/resourcemanager.tagUser"]: -# condition: [] -# members: -# - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/resourcemanager.tagUser -# module.organization.google_organization_iam_binding.authoritative["roles/securitycenter.admin"]: -# condition: [] -# members: -# - group:gcp-security-admins@fast.example.com -# org_id: '123456789012' -# role: roles/securitycenter.admin -# module.organization.google_organization_iam_custom_role.roles["organizationIamAdmin"]: -# description: Terraform-managed. -# org_id: '123456789012' -# permissions: -# - resourcemanager.organizations.get -# - resourcemanager.organizations.getIamPolicy -# - resourcemanager.organizations.setIamPolicy -# role_id: organizationIamAdmin -# stage: GA -# title: Custom role organizationIamAdmin -# module.organization.google_organization_iam_custom_role.roles["serviceProjectNetworkAdmin"]: -# description: Terraform-managed. -# org_id: '123456789012' -# permissions: -# - compute.globalOperations.get -# - compute.networks.get -# - compute.networks.updatePeering -# - compute.organizations.disableXpnResource -# - compute.organizations.enableXpnResource -# - compute.projects.get -# - compute.subnetworks.getIamPolicy -# - compute.subnetworks.setIamPolicy -# - dns.networks.bindPrivateDNSZone -# - resourcemanager.projects.get -# role_id: serviceProjectNetworkAdmin -# stage: GA -# title: Custom role serviceProjectNetworkAdmin -# module.organization.google_organization_iam_member.additive["roles/accesscontextmanager.policyAdmin-group:gcp-security-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-security-admins@fast.example.com -# org_id: '123456789012' -# role: roles/accesscontextmanager.policyAdmin -# module.organization.google_organization_iam_member.additive["roles/billing.admin-group:gcp-billing-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-billing-admins@fast.example.com -# org_id: '123456789012' -# role: roles/billing.admin -# module.organization.google_organization_iam_member.additive["roles/billing.admin-group:gcp-organization-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-organization-admins@fast.example.com -# org_id: '123456789012' -# role: roles/billing.admin -# module.organization.google_organization_iam_member.additive["roles/billing.admin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: -# condition: [] -# member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/billing.admin -# module.organization.google_organization_iam_member.additive["roles/billing.admin-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: -# condition: [] -# member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/billing.admin -# module.organization.google_organization_iam_member.additive["roles/billing.costsManager-group:gcp-billing-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-billing-admins@fast.example.com -# org_id: '123456789012' -# role: roles/billing.costsManager -# module.organization.google_organization_iam_member.additive["roles/billing.costsManager-group:gcp-organization-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-organization-admins@fast.example.com -# org_id: '123456789012' -# role: roles/billing.costsManager -# module.organization.google_organization_iam_member.additive["roles/billing.costsManager-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: -# condition: [] -# member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/billing.costsManager -# module.organization.google_organization_iam_member.additive["roles/billing.costsManager-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: -# condition: [] -# member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/billing.costsManager -# module.organization.google_organization_iam_member.additive["roles/compute.orgFirewallPolicyAdmin-group:gcp-network-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-network-admins@fast.example.com -# org_id: '123456789012' -# role: roles/compute.orgFirewallPolicyAdmin -# module.organization.google_organization_iam_member.additive["roles/compute.xpnAdmin-group:gcp-network-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-network-admins@fast.example.com -# org_id: '123456789012' -# role: roles/compute.xpnAdmin -# module.organization.google_organization_iam_member.additive["roles/iam.organizationRoleAdmin-group:gcp-security-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-security-admins@fast.example.com -# org_id: '123456789012' -# role: roles/iam.organizationRoleAdmin -# module.organization.google_organization_iam_member.additive["roles/iam.organizationRoleAdmin-serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: -# condition: [] -# member: serviceAccount:fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/iam.organizationRoleAdmin -# module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-group:gcp-organization-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-organization-admins@fast.example.com -# org_id: '123456789012' -# role: roles/orgpolicy.policyAdmin -# module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-group:gcp-security-admins@fast.example.com"]: -# condition: [] -# member: group:gcp-security-admins@fast.example.com -# org_id: '123456789012' -# role: roles/orgpolicy.policyAdmin -# module.organization.google_organization_iam_member.additive["roles/orgpolicy.policyAdmin-serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com"]: -# condition: [] -# member: serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# org_id: '123456789012' -# role: roles/orgpolicy.policyAdmin +# Copyright 2022 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. -# counts: -# google_bigquery_dataset: 2 -# google_bigquery_dataset_iam_member: 2 -# google_bigquery_default_service_account: 3 -# google_logging_organization_sink: 2 -# google_organization_iam_binding: 19 -# google_organization_iam_custom_role: 2 -# google_organization_iam_member: 16 -# google_project: 3 -# google_project_iam_binding: 9 -# google_project_iam_member: 1 -# google_project_service: 29 -# google_project_service_identity: 2 -# google_service_account: 3 -# google_service_account_iam_binding: 3 -# google_storage_bucket: 4 -# google_storage_bucket_iam_binding: 2 -# google_storage_bucket_iam_member: 3 -# google_storage_bucket_object: 5 -# google_storage_project_service_account: 3 -# local_file: 5 +counts: + google_bigquery_dataset: 2 + google_bigquery_dataset_iam_member: 2 + google_bigquery_default_service_account: 3 + google_logging_organization_sink: 2 + google_organization_iam_binding: 19 + google_organization_iam_custom_role: 2 + google_organization_iam_member: 16 + google_project: 3 + google_project_iam_binding: 9 + google_project_iam_member: 1 + google_project_service: 29 + google_project_service_identity: 2 + google_service_account: 3 + google_service_account_iam_binding: 3 + google_storage_bucket: 4 + google_storage_bucket_iam_binding: 2 + google_storage_bucket_iam_member: 3 + google_storage_bucket_object: 5 + google_storage_project_service_account: 3 + local_file: 5 -# outputs: -# automation: __missing__ -# billing_dataset: __missing__ -# cicd_repositories: {} -# custom_roles: -# organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin -# service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin -# federated_identity: -# pool: null -# providers: {} -# outputs_bucket: fast-prod-iac-core-outputs-0 -# project_ids: -# automation: fast-prod-iac-core-0 -# billing-export: fast-prod-billing-exp-0 -# log-export: fast-prod-audit-logs-0 -# service_accounts: -# bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com -# tfvars: __missing__ +outputs: + custom_roles: + organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin + service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin + outputs_bucket: fast-prod-iac-core-outputs-0 + project_ids: + automation: fast-prod-iac-core-0 + billing-export: fast-prod-billing-exp-0 + log-export: fast-prod-audit-logs-0 + service_accounts: + bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com + cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com + resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com diff --git a/tests/fast/stages/s00_bootstrap/simple_projects.yaml b/tests/fast/stages/s00_bootstrap/simple_projects.yaml new file mode 100644 index 000000000..c4d359f33 --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/simple_projects.yaml @@ -0,0 +1,33 @@ +# Copyright 2022 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-project.google_project.project[0]: + auto_create_network: false + billing_account: 000000-111111-222222 + name: fast-prod-iac-core-0 + org_id: '123456789012' + project_id: fast-prod-iac-core-0 + module.billing-export-project[0].google_project.project[0]: + auto_create_network: false + billing_account: 000000-111111-222222 + name: fast-prod-billing-exp-0 + org_id: '123456789012' + project_id: fast-prod-billing-exp-0 + module.log-export-project.google_project.project[0]: + auto_create_network: false + billing_account: 000000-111111-222222 + name: fast-prod-audit-logs-0 + org_id: '123456789012' + project_id: fast-prod-audit-logs-0 diff --git a/tests/fast/stages/s00_bootstrap/simple_sas.yaml b/tests/fast/stages/s00_bootstrap/simple_sas.yaml new file mode 100644 index 000000000..ba84948d8 --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/simple_sas.yaml @@ -0,0 +1,27 @@ +# Copyright 2022 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-tf-bootstrap-sa.google_service_account.service_account[0]: + account_id: fast-prod-bootstrap-0 + display_name: Terraform organization bootstrap service account. + project: fast-prod-iac-core-0 + module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]: + account_id: fast-prod-cicd-0 + display_name: Terraform stage 1 CICD service account. + project: fast-prod-iac-core-0 + module.automation-tf-resman-sa.google_service_account.service_account[0]: + account_id: fast-prod-resman-0 + display_name: Terraform stage 1 resman service account. + project: fast-prod-iac-core-0 diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s00_bootstrap/tftest.yaml index 3c57e3229..4656859bc 100644 --- a/tests/fast/stages/s00_bootstrap/tftest.yaml +++ b/tests/fast/stages/s00_bootstrap/tftest.yaml @@ -6,3 +6,7 @@ tests: simple: tfvars: - simple.tfvars + inventory: + - simple.yaml + - simple_projects.yaml + - simple_sas.yaml From 589f7a5c2f94c732bfa568d32f37a03ac8050e4c Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Mon, 5 Dec 2022 11:20:20 +0100 Subject: [PATCH 19/28] Simplify yaml test spec --- tests/collectors.py | 42 +++++-- tests/conftest.py | 3 +- tests/fast/stages/s00_bootstrap/tftest.yaml | 15 ++- tests/fixtures.py | 30 +++-- tests/modules/net_vpc/test_routes.py | 7 +- tests/modules/net_vpc/tftest.yaml | 73 ++++++------- .../organization/test_plan_org_policies.py | 18 ++- tests/modules/organization/tftest.yaml | 103 +++++++++--------- 8 files changed, 152 insertions(+), 139 deletions(-) diff --git a/tests/collectors.py b/tests/collectors.py index 4ba3c7bc5..2ab591362 100644 --- a/tests/collectors.py +++ b/tests/collectors.py @@ -15,19 +15,44 @@ import pytest import yaml -from .fixtures import generic_plan_summary, generic_plan_validator +from .fixtures import plan_summary, plan_validator class FabricTestFile(pytest.File): def collect(self): + """Read yaml test spec and yield test items for each test definition. + + Test spec should contain a `module` key with the path of the + terraform module to test, relative to the root of the repository + + All other top-level keys in the yaml are taken as test names, and + should have the following structure: + + test-name: + tfvars: + - tfvars1.tfvars + - tfvars2.tfvars + inventory: + - inventory1.yaml + - inventory2.yaml + + All paths specifications are relative to the location of the test + spec. The inventory key is optional, if omitted, the inventory + will be taken from the file test-name.yaml + + """ raw = yaml.safe_load(self.path.open()) - module = raw['module'] - for test_name, spec in raw['tests'].items(): - inventory = spec.get('inventory', f'{test_name}.yaml') + module = raw.pop('module') + for test_name, spec in raw.items(): + inventories = spec.get('inventory', [f'{test_name}.yaml']) tfvars = spec['tfvars'] - yield FabricTestItem.from_parent(self, name=test_name, module=module, - inventory=inventory, tfvars=tfvars) + for i in inventories: + name = test_name + if isinstance(inventories, list) and len(inventories) > 1: + name = f'{test_name}[{i}]' + yield FabricTestItem.from_parent(self, name=name, module=module, + inventory=[i], tfvars=tfvars) class FabricTestItem(pytest.Item): @@ -39,13 +64,14 @@ class FabricTestItem(pytest.Item): self.tfvars = tfvars def runtest(self): - s = generic_plan_validator(self.module, self.inventory, - self.parent.path.parent, self.tfvars) + s = plan_validator(self.module, self.inventory, self.parent.path.parent, + self.tfvars) def reportinfo(self): return self.path, None, self.name def pytest_collect_file(parent, file_path): + 'Collect tftest*.yaml files and run plan_validator from them.' if file_path.suffix == '.yaml' and file_path.name.startswith('tftest'): return FabricTestFile.from_parent(parent, path=file_path) diff --git a/tests/conftest.py b/tests/conftest.py index 778989fd1..0d9287988 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,8 +23,7 @@ import tftest import yaml from .collectors import pytest_collect_file -from .fixtures import generic_plan_summary_fixture, generic_plan_validator_fixture -from .fixtures import generic_plan_summary, generic_plan_validator +from .fixtures import plan_summary_fixture, plan_validator_fixture BASEDIR = os.path.dirname(os.path.dirname(__file__)) diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s00_bootstrap/tftest.yaml index 4656859bc..9f9ce1078 100644 --- a/tests/fast/stages/s00_bootstrap/tftest.yaml +++ b/tests/fast/stages/s00_bootstrap/tftest.yaml @@ -2,11 +2,10 @@ module: fast/stages/00-bootstrap -tests: - simple: - tfvars: - - simple.tfvars - inventory: - - simple.yaml - - simple_projects.yaml - - simple_sas.yaml +simple: + tfvars: + - simple.tfvars + inventory: + - simple.yaml + - simple_projects.yaml + - simple_sas.yaml diff --git a/tests/fixtures.py b/tests/fixtures.py index 43b2128cd..a225d871d 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -25,7 +25,7 @@ import yaml PlanSummary = collections.namedtuple('PlanSummary', 'values counts outputs') -def generic_plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): +def plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): """ Run a Terraform plan on the module located at `module_path`. @@ -117,8 +117,8 @@ def generic_plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): return PlanSummary(values, dict(counts), outputs) -@pytest.fixture(name='generic_plan_summary') -def generic_plan_summary_fixture(request): +@pytest.fixture(name='plan_summary') +def plan_summary_fixture(request): """Return a function to generate a PlanSummary. In the returned function `basedir` becomes optional and it defaults @@ -128,17 +128,16 @@ def generic_plan_summary_fixture(request): def inner(module_path, basedir=None, tf_var_files=None, **tf_vars): if basedir is None: basedir = Path(request.fspath).parent - return generic_plan_summary(module_path=module_path, basedir=basedir, - tf_var_files=tf_var_files, **tf_vars) + return plan_summary(module_path=module_path, basedir=basedir, + tf_var_files=tf_var_files, **tf_vars) return inner -def generic_plan_validator(module_path, inventory_paths, basedir, - tf_var_files=None, **tf_vars): - summary = generic_plan_summary(module_path=module_path, - tf_var_files=tf_var_files, basedir=basedir, - **tf_vars) +def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, + **tf_vars): + summary = plan_summary(module_path=module_path, tf_var_files=tf_var_files, + basedir=basedir, **tf_vars) # allow single single string for inventory_paths if not isinstance(inventory_paths, list): @@ -201,8 +200,8 @@ def generic_plan_validator(module_path, inventory_paths, basedir, return summary -@pytest.fixture(name='generic_plan_validator') -def generic_plan_validator_fixture(request): +@pytest.fixture(name='plan_validator') +def plan_validator_fixture(request): """Return a function to builds a PlanSummary and compare it to YAML inventory. @@ -215,9 +214,8 @@ def generic_plan_validator_fixture(request): **tf_vars): if basedir is None: basedir = Path(request.fspath).parent - return generic_plan_validator(module_path=module_path, - inventory_paths=inventory_paths, - basedir=basedir, tf_var_files=tf_var_paths, - **tf_vars) + return plan_validator(module_path=module_path, + inventory_paths=inventory_paths, basedir=basedir, + tf_var_files=tf_var_paths, **tf_vars) return inner diff --git a/tests/modules/net_vpc/test_routes.py b/tests/modules/net_vpc/test_routes.py index e4df8d6bd..01d9673dd 100644 --- a/tests/modules/net_vpc/test_routes.py +++ b/tests/modules/net_vpc/test_routes.py @@ -22,7 +22,7 @@ _route_parameters = [('gateway', 'global/gateways/default-internet-gateway'), @pytest.mark.parametrize('next_hop_type,next_hop', _route_parameters) -def test_vpc_routes(generic_plan_summary, next_hop_type, next_hop): +def test_vpc_routes(plan_summary, next_hop_type, next_hop): 'Test vpc routes.' var_routes = '''{ @@ -40,9 +40,8 @@ def test_vpc_routes(generic_plan_summary, next_hop_type, next_hop): next_hop = "global/gateways/default-internet-gateway" } }''' % (next_hop_type, next_hop) - summary = generic_plan_summary('modules/net-vpc', - tf_var_files=['common.tfvars'], - routes=var_routes) + summary = plan_summary('modules/net-vpc', tf_var_files=['common.tfvars'], + routes=var_routes) assert len(summary.values) == 3 route = summary.values[f'google_compute_route.{next_hop_type}["next-hop"]'] assert route[f'next_hop_{next_hop_type}'] == next_hop diff --git a/tests/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml index 3a8060dd1..4463a1847 100644 --- a/tests/modules/net_vpc/tftest.yaml +++ b/tests/modules/net_vpc/tftest.yaml @@ -14,49 +14,46 @@ module: modules/net-vpc -tests: - simple: - tfvars: - - common.tfvars +simple: + tfvars: + - common.tfvars - subnets: - tfvars: - - common.tfvars - - subnets.tfvars +subnets: + tfvars: + - common.tfvars + - subnets.tfvars - peering: - tfvars: - - common.tfvars - - peering.tfvars +peering: + tfvars: + - common.tfvars + - peering.tfvars - shared_vpc: - tfvars: - - common.tfvars - - shared_vpc.tfvars +shared_vpc: + tfvars: + - common.tfvars + - shared_vpc.tfvars - factory: - tfvars: - - common.tfvars - - factory.tfvars - inventory: - - factory.yaml +factory: + tfvars: + - common.tfvars + - factory.tfvars - psa_simple: - tfvars: - - common.tfvars - - psa_simple.tfvars +psa_simple: + tfvars: + - common.tfvars + - psa_simple.tfvars - psa_routes_export: - tfvars: - - common.tfvars - - psa_routes_export.tfvars +psa_routes_export: + tfvars: + - common.tfvars + - psa_routes_export.tfvars - psa_routes_import: - tfvars: - - common.tfvars - - psa_routes_import.tfvars +psa_routes_import: + tfvars: + - common.tfvars + - psa_routes_import.tfvars - psa_routes_import_export: - tfvars: - - common.tfvars - - psa_routes_import_export.tfvars +psa_routes_import_export: + tfvars: + - common.tfvars + - psa_routes_import_export.tfvars diff --git a/tests/modules/organization/test_plan_org_policies.py b/tests/modules/organization/test_plan_org_policies.py index 0a5d9a35a..1e041dbc4 100644 --- a/tests/modules/organization/test_plan_org_policies.py +++ b/tests/modules/organization/test_plan_org_policies.py @@ -20,28 +20,26 @@ _params = ['boolean', 'list'] @pytest.mark.parametrize('policy_type', _params) -def test_policy_factory(generic_plan_summary, tfvars_to_yaml, tmp_path, - policy_type): +def test_policy_factory(plan_summary, tfvars_to_yaml, tmp_path, policy_type): dest = tmp_path / 'policies.yaml' tfvars_to_yaml(f'org_policies_{policy_type}.tfvars', dest, 'org_policies') - tfvars_plan = generic_plan_summary( + tfvars_plan = plan_summary( 'modules/organization', tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars']) - yaml_plan = generic_plan_summary('modules/organization', - tf_var_files=['common.tfvars'], - org_policies_data_path=f'{tmp_path}') + yaml_plan = plan_summary('modules/organization', + tf_var_files=['common.tfvars'], + org_policies_data_path=f'{tmp_path}') assert tfvars_plan.values == yaml_plan.values -def test_custom_constraint_factory(generic_plan_summary, tfvars_to_yaml, - tmp_path): +def test_custom_constraint_factory(plan_summary, tfvars_to_yaml, tmp_path): dest = tmp_path / 'constraints.yaml' tfvars_to_yaml(f'org_policies_custom_constraints.tfvars', dest, 'org_policy_custom_constraints') - tfvars_plan = generic_plan_summary( + tfvars_plan = plan_summary( 'modules/organization', tf_var_files=['common.tfvars', f'org_policies_custom_constraints.tfvars']) - yaml_plan = generic_plan_summary( + yaml_plan = plan_summary( 'modules/organization', tf_var_files=['common.tfvars'], org_policy_custom_constraints_data_path=f'{tmp_path}') assert tfvars_plan.values == yaml_plan.values diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml index 662b6b1a5..6389aba0b 100644 --- a/tests/modules/organization/tftest.yaml +++ b/tests/modules/organization/tftest.yaml @@ -14,67 +14,64 @@ module: modules/organization -tests: - audit_config: - tfvars: - - common.tfvars - - audit_config.tfvars +audit_config: + tfvars: + - common.tfvars + - audit_config.tfvars - iam: - tfvars: - - common.tfvars - - iam.tfvars +iam: + tfvars: + - common.tfvars + - iam.tfvars - iam_additive: - tfvars: - - common.tfvars - - iam_additive.tfvars +iam_additive: + tfvars: + - common.tfvars + - iam_additive.tfvars - logging: - tfvars: - - common.tfvars - - logging.tfvars +logging: + tfvars: + - common.tfvars + - logging.tfvars - logging_exclusions: - tfvars: - - common.tfvars - - logging_exclusions.tfvars +logging_exclusions: + tfvars: + - common.tfvars + - logging_exclusions.tfvars - org_policies_list: - tfvars: - - common.tfvars - - org_policies_list.tfvars +org_policies_list: + tfvars: + - common.tfvars + - org_policies_list.tfvars - org_policies_boolean: - tfvars: - - common.tfvars - - org_policies_boolean.tfvars +org_policies_boolean: + tfvars: + - common.tfvars + - org_policies_boolean.tfvars - org_policies_custom_constraints: - tfvars: - - common.tfvars - - org_policies_custom_constraints.tfvars +org_policies_custom_constraints: + tfvars: + - common.tfvars + - org_policies_custom_constraints.tfvars - tags: - tfvars: - - common.tfvars - - network_tags.tfvars - - resource_tags.tfvars - inventory: - - tags.yaml +tags: + tfvars: + - common.tfvars + - network_tags.tfvars + - resource_tags.tfvars - firewall_policies: - tfvars: - - common.tfvars - - firewall_policies.tfvars +firewall_policies: + tfvars: + - common.tfvars + - firewall_policies.tfvars - firewall_policies_factory: - tfvars: - - common.tfvars - - firewall_policies_factory.tfvars +firewall_policies_factory: + tfvars: + - common.tfvars + - firewall_policies_factory.tfvars - firewall_policies_factory_combined: - tfvars: - - common.tfvars - - firewall_policies.tfvars - - firewall_policies_factory.tfvars +firewall_policies_factory_combined: + tfvars: + - common.tfvars + - firewall_policies.tfvars + - firewall_policies_factory.tfvars From 284f8ff106caf59a1410df4d1db6b61487286167 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Mon, 5 Dec 2022 11:28:58 +0100 Subject: [PATCH 20/28] Basic error handling --- tests/collectors.py | 16 +++++++++++++--- tests/fixtures.py | 5 ++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/collectors.py b/tests/collectors.py index 2ab591362..c9d584d9c 100644 --- a/tests/collectors.py +++ b/tests/collectors.py @@ -42,11 +42,21 @@ class FabricTestFile(pytest.File): will be taken from the file test-name.yaml """ - raw = yaml.safe_load(self.path.open()) - module = raw.pop('module') + try: + raw = yaml.safe_load(self.path.open()) + module = raw.pop('module') + except (IOError, OSError, yaml.YAMLError) as e: + raise Exception(f'cannot read test spec {self.path}: {e}') + except KeyError as e: + raise Exception(f'`module` key not found in {self.path}: {e}') for test_name, spec in raw.items(): inventories = spec.get('inventory', [f'{test_name}.yaml']) - tfvars = spec['tfvars'] + try: + tfvars = spec['tfvars'] + except KeyError: + raise Exception( + f'test definition `{test_name}` in {self.path} does not contain a `tfvars` key' + ) for i in inventories: name = test_name if isinstance(inventories, list) and len(inventories) > 1: diff --git a/tests/fixtures.py b/tests/fixtures.py index a225d871d..4545766a1 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -146,7 +146,10 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, for path in inventory_paths: # allow tfvars and inventory to be relative to the caller path = basedir / path - inventory = yaml.safe_load(path.read_text()) + try: + inventory = yaml.safe_load(path.read_text()) + except (IOError, OSError, yaml.YAMLError) as e: + raise Exception(f'cannot read test inventory {path}: {e}') # don't fail if the inventory is empty inventory = inventory or {} From fded49cc67da86666e6ba3b5c95c7892f1606121 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Mon, 5 Dec 2022 23:17:16 +0100 Subject: [PATCH 21/28] Remove unneeded imports from tests/conftest.py and use pytest_plugins --- tests/conftest.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0d9287988..efb6ad876 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,11 +22,10 @@ import pytest import tftest import yaml -from .collectors import pytest_collect_file -from .fixtures import plan_summary_fixture, plan_validator_fixture - BASEDIR = os.path.dirname(os.path.dirname(__file__)) +pytest_plugins = ('tests.fixtures', 'tests.collectors') + @pytest.fixture(scope='session') def _plan_runner(): From be0e807435c41800565c7449232934d304cd7dc1 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 6 Dec 2022 00:02:16 +0100 Subject: [PATCH 22/28] Bring back `tests` key in test yaml spec --- tests/collectors.py | 18 ++- tests/conftest.py | 147 +------------------ tests/fast/stages/s00_bootstrap/tftest.yaml | 15 +- tests/fixtures.py | 11 +- tests/legacy_fixtures.py | 153 ++++++++++++++++++++ tests/modules/net_vpc/tftest.yaml | 71 ++++----- tests/modules/organization/tftest.yaml | 101 ++++++------- 7 files changed, 276 insertions(+), 240 deletions(-) create mode 100644 tests/legacy_fixtures.py diff --git a/tests/collectors.py b/tests/collectors.py index c9d584d9c..78f3febff 100644 --- a/tests/collectors.py +++ b/tests/collectors.py @@ -11,6 +11,13 @@ # 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. +"""Pytest plugin to discover tests specified in YAML files. + +This plugin uses the pytest_collect_file hook to collect all files +matching tftest*.yaml and runs plan_validate for each test found. +See FabricTestFile for details on the file structure. + +""" import pytest import yaml @@ -23,11 +30,11 @@ class FabricTestFile(pytest.File): def collect(self): """Read yaml test spec and yield test items for each test definition. - Test spec should contain a `module` key with the path of the + The test spec should contain a `module` key with the path of the terraform module to test, relative to the root of the repository - All other top-level keys in the yaml are taken as test names, and - should have the following structure: + Tests are defined within the top-level `tests` key, and should + have the following structure: test-name: tfvars: @@ -42,6 +49,7 @@ class FabricTestFile(pytest.File): will be taken from the file test-name.yaml """ + try: raw = yaml.safe_load(self.path.open()) module = raw.pop('module') @@ -49,13 +57,13 @@ class FabricTestFile(pytest.File): raise Exception(f'cannot read test spec {self.path}: {e}') except KeyError as e: raise Exception(f'`module` key not found in {self.path}: {e}') - for test_name, spec in raw.items(): + for test_name, spec in raw.get('tests', {}).items(): inventories = spec.get('inventory', [f'{test_name}.yaml']) try: tfvars = spec['tfvars'] except KeyError: raise Exception( - f'test definition `{test_name}` in {self.path} does not contain a `tfvars` key' + f'test `{test_name}` in {self.path} does not contain a `tfvars` key' ) for i in inventories: name = test_name diff --git a/tests/conftest.py b/tests/conftest.py index efb6ad876..167c74f73 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,147 +11,12 @@ # 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. -'Shared fixtures.' - -import inspect -import os -import shutil -import tempfile +'Pytest configuration.' import pytest -import tftest -import yaml -BASEDIR = os.path.dirname(os.path.dirname(__file__)) - -pytest_plugins = ('tests.fixtures', 'tests.collectors') - - -@pytest.fixture(scope='session') -def _plan_runner(): - 'Return a function to run Terraform plan on a fixture.' - - def run_plan(fixture_path=None, extra_files=None, tf_var_file=None, - targets=None, refresh=True, tmpdir=True, **tf_vars): - 'Run Terraform plan and returns parsed output.' - if fixture_path is None: - # find out the fixture directory from the caller's directory - caller = inspect.stack()[2] - fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture') - - fixture_parent = os.path.dirname(fixture_path) - fixture_prefix = os.path.basename(fixture_path) + '_' - with tempfile.TemporaryDirectory(prefix=fixture_prefix, - dir=fixture_parent) as tmp_path: - # copy fixture to a temporary directory so we can execute - # multiple tests in parallel - if tmpdir: - shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True) - tf = tftest.TerraformTest(tmp_path if tmpdir else fixture_path, BASEDIR, - os.environ.get('TERRAFORM', 'terraform')) - tf.setup(extra_files=extra_files, upgrade=True) - plan = tf.plan(output=True, refresh=refresh, tf_var_file=tf_var_file, - tf_vars=tf_vars, targets=targets) - return plan - - return run_plan - - -@pytest.fixture(scope='session') -def plan_runner(_plan_runner): - 'Return a function to run Terraform plan on a module fixture.' - - def run_plan(fixture_path=None, extra_files=None, tf_var_file=None, - targets=None, **tf_vars): - 'Run Terraform plan and returns plan and module resources.' - plan = _plan_runner(fixture_path, extra_files=extra_files, - tf_var_file=tf_var_file, targets=targets, **tf_vars) - # skip the fixture - root_module = plan.root_module['child_modules'][0] - return plan, root_module['resources'] - - return run_plan - - -@pytest.fixture(scope='session') -def e2e_plan_runner(_plan_runner): - 'Return a function to run Terraform plan on an end-to-end fixture.' - - def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True, - include_bare_resources=False, **tf_vars): - 'Run Terraform plan on an end-to-end module using defaults, returns data.' - plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, - refresh=refresh, **tf_vars) - # skip the fixture - root_module = plan.root_module['child_modules'][0] - modules = dict((mod['address'], mod['resources']) - for mod in root_module['child_modules']) - resources = [r for m in modules.values() for r in m] - if include_bare_resources: - bare_resources = root_module['resources'] - resources.extend(bare_resources) - return modules, resources - - return run_plan - - -@pytest.fixture(scope='session') -def recursive_e2e_plan_runner(_plan_runner): - """ - Plan runner for end-to-end root module, returns total number of - (nested) modules and resources - """ - - def walk_plan(node, modules, resources): - new_modules = node.get('child_modules', []) - resources += node.get('resources', []) - modules += new_modules - for module in new_modules: - walk_plan(module, modules, resources) - - def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True, - include_bare_resources=False, compute_sums=True, tmpdir=True, - **tf_vars): - 'Run Terraform plan on a root module using defaults, returns data.' - plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, - refresh=refresh, tmpdir=tmpdir, **tf_vars) - modules = [] - resources = [] - walk_plan(plan.root_module, modules, resources) - return len(modules), len(resources) - - return run_plan - - -@pytest.fixture(scope='session') -def apply_runner(): - 'Return a function to run Terraform apply on a fixture.' - - def run_apply(fixture_path=None, **tf_vars): - 'Run Terraform plan and returns parsed output.' - if fixture_path is None: - # find out the fixture directory from the caller's directory - caller = inspect.stack()[1] - fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture') - - fixture_parent = os.path.dirname(fixture_path) - fixture_prefix = os.path.basename(fixture_path) + '_' - - with tempfile.TemporaryDirectory(prefix=fixture_prefix, - dir=fixture_parent) as tmp_path: - # copy fixture to a temporary directory so we can execute - # multiple tests in parallel - shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True) - tf = tftest.TerraformTest(tmp_path, BASEDIR, - os.environ.get('TERRAFORM', 'terraform')) - tf.setup(upgrade=True) - apply = tf.apply(tf_vars=tf_vars) - output = tf.output(json_format=True) - return apply, output - - return run_apply - - -@pytest.fixture -def basedir(): - return BASEDIR +pytest_plugins = ( + 'tests.fixtures', + 'tests.legacy_fixtures', + 'tests.collectors', +) diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s00_bootstrap/tftest.yaml index 9f9ce1078..4656859bc 100644 --- a/tests/fast/stages/s00_bootstrap/tftest.yaml +++ b/tests/fast/stages/s00_bootstrap/tftest.yaml @@ -2,10 +2,11 @@ module: fast/stages/00-bootstrap -simple: - tfvars: - - simple.tfvars - inventory: - - simple.yaml - - simple_projects.yaml - - simple_sas.yaml +tests: + simple: + tfvars: + - simple.tfvars + inventory: + - simple.yaml + - simple_projects.yaml + - simple_sas.yaml diff --git a/tests/fixtures.py b/tests/fixtures.py index 4545766a1..ad2077425 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -11,6 +11,8 @@ # 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. +"""Common fixtures.""" + import collections import itertools import os @@ -205,8 +207,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, @pytest.fixture(name='plan_validator') def plan_validator_fixture(request): - """Return a function to builds a PlanSummary and compare it to YAML - inventory. + """Return a function to build a PlanSummary and compare it to a YAML inventory. In the returned function `basedir` becomes optional and it defaults to the directory of the calling test' @@ -222,3 +223,9 @@ def plan_validator_fixture(request): tf_var_files=tf_var_paths, **tf_vars) return inner + + +# @pytest.fixture +# def repo_root(): +# 'Return a pathlib.Path to the root of the repository' +# return Path(__file__).parents[1] diff --git a/tests/legacy_fixtures.py b/tests/legacy_fixtures.py new file mode 100644 index 000000000..5891d704b --- /dev/null +++ b/tests/legacy_fixtures.py @@ -0,0 +1,153 @@ +# Copyright 2022 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. +"""Legacy pytest fixtures. + +The fixtures contained in this file will eventually go away. Consider +using one of the fixtures in fixtures.py +""" + +import inspect +import os +import shutil +import tempfile + +import pytest +import tftest + +BASEDIR = os.path.dirname(os.path.dirname(__file__)) + + +@pytest.fixture(scope='session') +def _plan_runner(): + 'Return a function to run Terraform plan on a fixture.' + + def run_plan(fixture_path=None, extra_files=None, tf_var_file=None, + targets=None, refresh=True, tmpdir=True, **tf_vars): + 'Run Terraform plan and returns parsed output.' + if fixture_path is None: + # find out the fixture directory from the caller's directory + caller = inspect.stack()[2] + fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture') + + fixture_parent = os.path.dirname(fixture_path) + fixture_prefix = os.path.basename(fixture_path) + '_' + with tempfile.TemporaryDirectory(prefix=fixture_prefix, + dir=fixture_parent) as tmp_path: + # copy fixture to a temporary directory so we can execute + # multiple tests in parallel + if tmpdir: + shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True) + tf = tftest.TerraformTest(tmp_path if tmpdir else fixture_path, BASEDIR, + os.environ.get('TERRAFORM', 'terraform')) + tf.setup(extra_files=extra_files, upgrade=True) + plan = tf.plan(output=True, refresh=refresh, tf_var_file=tf_var_file, + tf_vars=tf_vars, targets=targets) + return plan + + return run_plan + + +@pytest.fixture(scope='session') +def plan_runner(_plan_runner): + 'Return a function to run Terraform plan on a module fixture.' + + def run_plan(fixture_path=None, extra_files=None, tf_var_file=None, + targets=None, **tf_vars): + 'Run Terraform plan and returns plan and module resources.' + plan = _plan_runner(fixture_path, extra_files=extra_files, + tf_var_file=tf_var_file, targets=targets, **tf_vars) + # skip the fixture + root_module = plan.root_module['child_modules'][0] + return plan, root_module['resources'] + + return run_plan + + +@pytest.fixture(scope='session') +def e2e_plan_runner(_plan_runner): + 'Return a function to run Terraform plan on an end-to-end fixture.' + + def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True, + include_bare_resources=False, **tf_vars): + 'Run Terraform plan on an end-to-end module using defaults, returns data.' + plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, + refresh=refresh, **tf_vars) + # skip the fixture + root_module = plan.root_module['child_modules'][0] + modules = dict((mod['address'], mod['resources']) + for mod in root_module['child_modules']) + resources = [r for m in modules.values() for r in m] + if include_bare_resources: + bare_resources = root_module['resources'] + resources.extend(bare_resources) + return modules, resources + + return run_plan + + +@pytest.fixture(scope='session') +def recursive_e2e_plan_runner(_plan_runner): + """ + Plan runner for end-to-end root module, returns total number of + (nested) modules and resources + """ + + def walk_plan(node, modules, resources): + new_modules = node.get('child_modules', []) + resources += node.get('resources', []) + modules += new_modules + for module in new_modules: + walk_plan(module, modules, resources) + + def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True, + include_bare_resources=False, compute_sums=True, tmpdir=True, + **tf_vars): + 'Run Terraform plan on a root module using defaults, returns data.' + plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets, + refresh=refresh, tmpdir=tmpdir, **tf_vars) + modules = [] + resources = [] + walk_plan(plan.root_module, modules, resources) + return len(modules), len(resources) + + return run_plan + + +@pytest.fixture(scope='session') +def apply_runner(): + 'Return a function to run Terraform apply on a fixture.' + + def run_apply(fixture_path=None, **tf_vars): + 'Run Terraform plan and returns parsed output.' + if fixture_path is None: + # find out the fixture directory from the caller's directory + caller = inspect.stack()[1] + fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture') + + fixture_parent = os.path.dirname(fixture_path) + fixture_prefix = os.path.basename(fixture_path) + '_' + + with tempfile.TemporaryDirectory(prefix=fixture_prefix, + dir=fixture_parent) as tmp_path: + # copy fixture to a temporary directory so we can execute + # multiple tests in parallel + shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True) + tf = tftest.TerraformTest(tmp_path, BASEDIR, + os.environ.get('TERRAFORM', 'terraform')) + tf.setup(upgrade=True) + apply = tf.apply(tf_vars=tf_vars) + output = tf.output(json_format=True) + return apply, output + + return run_apply diff --git a/tests/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml index 4463a1847..955206c98 100644 --- a/tests/modules/net_vpc/tftest.yaml +++ b/tests/modules/net_vpc/tftest.yaml @@ -14,46 +14,47 @@ module: modules/net-vpc -simple: - tfvars: - - common.tfvars +tests: + simple: + tfvars: + - common.tfvars -subnets: - tfvars: - - common.tfvars - - subnets.tfvars + subnets: + tfvars: + - common.tfvars + - subnets.tfvars -peering: - tfvars: - - common.tfvars - - peering.tfvars + peering: + tfvars: + - common.tfvars + - peering.tfvars -shared_vpc: - tfvars: - - common.tfvars - - shared_vpc.tfvars + shared_vpc: + tfvars: + - common.tfvars + - shared_vpc.tfvars -factory: - tfvars: - - common.tfvars - - factory.tfvars + factory: + tfvars: + - common.tfvars + - factory.tfvars -psa_simple: - tfvars: - - common.tfvars - - psa_simple.tfvars + psa_simple: + tfvars: + - common.tfvars + - psa_simple.tfvars -psa_routes_export: - tfvars: - - common.tfvars - - psa_routes_export.tfvars + psa_routes_export: + tfvars: + - common.tfvars + - psa_routes_export.tfvars -psa_routes_import: - tfvars: - - common.tfvars - - psa_routes_import.tfvars + psa_routes_import: + tfvars: + - common.tfvars + - psa_routes_import.tfvars -psa_routes_import_export: - tfvars: - - common.tfvars - - psa_routes_import_export.tfvars + psa_routes_import_export: + tfvars: + - common.tfvars + - psa_routes_import_export.tfvars diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml index 6389aba0b..264abf176 100644 --- a/tests/modules/organization/tftest.yaml +++ b/tests/modules/organization/tftest.yaml @@ -14,64 +14,65 @@ module: modules/organization -audit_config: - tfvars: - - common.tfvars - - audit_config.tfvars +tests: + audit_config: + tfvars: + - common.tfvars + - audit_config.tfvars -iam: - tfvars: - - common.tfvars - - iam.tfvars + iam: + tfvars: + - common.tfvars + - iam.tfvars -iam_additive: - tfvars: - - common.tfvars - - iam_additive.tfvars + iam_additive: + tfvars: + - common.tfvars + - iam_additive.tfvars -logging: - tfvars: - - common.tfvars - - logging.tfvars + logging: + tfvars: + - common.tfvars + - logging.tfvars -logging_exclusions: - tfvars: - - common.tfvars - - logging_exclusions.tfvars + logging_exclusions: + tfvars: + - common.tfvars + - logging_exclusions.tfvars -org_policies_list: - tfvars: - - common.tfvars - - org_policies_list.tfvars + org_policies_list: + tfvars: + - common.tfvars + - org_policies_list.tfvars -org_policies_boolean: - tfvars: - - common.tfvars - - org_policies_boolean.tfvars + org_policies_boolean: + tfvars: + - common.tfvars + - org_policies_boolean.tfvars -org_policies_custom_constraints: - tfvars: - - common.tfvars - - org_policies_custom_constraints.tfvars + org_policies_custom_constraints: + tfvars: + - common.tfvars + - org_policies_custom_constraints.tfvars -tags: - tfvars: - - common.tfvars - - network_tags.tfvars - - resource_tags.tfvars + tags: + tfvars: + - common.tfvars + - network_tags.tfvars + - resource_tags.tfvars -firewall_policies: - tfvars: - - common.tfvars - - firewall_policies.tfvars + firewall_policies: + tfvars: + - common.tfvars + - firewall_policies.tfvars -firewall_policies_factory: - tfvars: - - common.tfvars - - firewall_policies_factory.tfvars + firewall_policies_factory: + tfvars: + - common.tfvars + - firewall_policies_factory.tfvars -firewall_policies_factory_combined: - tfvars: - - common.tfvars - - firewall_policies.tfvars - - firewall_policies_factory.tfvars + firewall_policies_factory_combined: + tfvars: + - common.tfvars + - firewall_policies.tfvars + - firewall_policies_factory.tfvars From f8d5f43c05181948374f81ecc1c9ba260a47368f Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 6 Dec 2022 10:05:30 +0100 Subject: [PATCH 23/28] Use ignore instead of copy+delete in plan_summary() --- tests/fixtures.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index ad2077425..6d0a53506 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -66,25 +66,19 @@ def plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): # to run multiple tests for the same module in parallel if os.environ.get('TFTEST_COPY'): test_path = Path(tmp_path) - shutil.copytree(module_path, test_path, dirs_exist_ok=True) - # if we're copying the module, we might as well remove any files - # and directories from the test directory that are automatically - # read by terraform. Useful to avoid surprises if, for example, - # you have an active fast deployment with links to configs) - autopaths = itertools.chain( - test_path.glob('*.auto.tfvars'), - test_path.glob('*.auto.tfvars.json'), - test_path.glob('terraform.tfstate*'), - test_path.glob('terraform.tfvars'), - test_path.glob('.terraform'), - # any symlinks? - ) - for p in autopaths: - if p.is_dir(): - shutil.rmtree(p) - else: - p.unlink() + # if we're copying the module, we might as well ignore files and + # directories that are automatically read by terraform. Useful + # to avoid surprises if, for example, you have an active fast + # deployment with links to configs) + ignore_patterns = shutil.ignore_patterns('*.auto.tfvars', + '*.auto.tfvars.json', + 'terraform.tfstate*', + 'terraform.tfvars', '.terraform') + + shutil.copytree(module_path, test_path, dirs_exist_ok=True, + ignore=ignore_patterns) + else: test_path = module_path From a017fef121c69801bc196ca871e51630a6adb760 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 6 Dec 2022 14:44:18 +0100 Subject: [PATCH 24/28] Removed refresh=True from plan() call --- tests/fixtures.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 6d0a53506..2f92023a1 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -87,8 +87,7 @@ def plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): tf = tftest.TerraformTest(test_path, binary=binary) tf.setup(upgrade=True) tf_var_files = [(basedir / x).resolve() for x in tf_var_files or []] - plan = tf.plan(output=True, refresh=True, tf_var_file=tf_var_files, - tf_vars=tf_vars) + plan = tf.plan(output=True, tf_var_file=tf_var_files, tf_vars=tf_vars) # compute resource type counts and address->values map values = {} From 19816475951caacd8f7fa7c3f6da11e1cd150813 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 6 Dec 2022 15:08:06 +0100 Subject: [PATCH 25/28] Custom context manager to create temp directory --- tests/fixtures.py | 63 ++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 2f92023a1..c483063f4 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -14,6 +14,7 @@ """Common fixtures.""" import collections +import contextlib import itertools import os import shutil @@ -27,6 +28,39 @@ import yaml PlanSummary = collections.namedtuple('PlanSummary', 'values counts outputs') +@contextlib.contextmanager +def _prepare_root_module(path): + """Context manager to prepare a terraform module to be tested. + + If the TFTEST_COPY environment variable is set, `path` is copied to + a temporary directory and a few terraform files (e.g. + terraform.tfvars) are delete to ensure a clean test environment. + Otherwise, `path` is simply returned untouched. + """ + if os.environ.get('TFTEST_COPY'): + # if the TFTEST_COPY is set, create temp dir and copy the root + # module there + with tempfile.TemporaryDirectory(dir=path.parent) as tmp_path: + tmp_path = Path(tmp_path) + + # if we're copying the module, we might as well ignore files and + # directories that are automatically read by terraform. Useful + # to avoid surprises if, for example, you have an active fast + # deployment with links to configs) + ignore_patterns = shutil.ignore_patterns('*.auto.tfvars', + '*.auto.tfvars.json', + 'terraform.tfstate*', + 'terraform.tfvars', '.terraform') + + shutil.copytree(path, tmp_path, dirs_exist_ok=True, + ignore=ignore_patterns) + + yield tmp_path + else: + # if TFTEST_COPY is not set, just return the same path + yield path + + def plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): """ Run a Terraform plan on the module located at `module_path`. @@ -56,33 +90,10 @@ def plan_summary(module_path, basedir, tf_var_files=None, **tf_vars): [1] https://developer.hashicorp.com/terraform/internals/json-format """ + # make the module_path relative to the root of the repo while still + # supporting absolute paths module_path = Path(__file__).parents[1] / module_path - - # FIXME: find a way to prevent the temp dir if TFTEST_COPY is not - # in the environment - with tempfile.TemporaryDirectory(dir=module_path.parent) as tmp_path: - # if TFTEST_COPY is set, copy the fixture to a temporary - # directory before running the plan. This is needed if you want - # to run multiple tests for the same module in parallel - if os.environ.get('TFTEST_COPY'): - test_path = Path(tmp_path) - - # if we're copying the module, we might as well ignore files and - # directories that are automatically read by terraform. Useful - # to avoid surprises if, for example, you have an active fast - # deployment with links to configs) - ignore_patterns = shutil.ignore_patterns('*.auto.tfvars', - '*.auto.tfvars.json', - 'terraform.tfstate*', - 'terraform.tfvars', '.terraform') - - shutil.copytree(module_path, test_path, dirs_exist_ok=True, - ignore=ignore_patterns) - - else: - test_path = module_path - - # prepare tftest and run plan + with _prepare_root_module(module_path) as test_path: binary = os.environ.get('TERRAFORM', 'terraform') tf = tftest.TerraformTest(test_path, binary=binary) tf.setup(upgrade=True) From 8b664fc5e34923eef67202df75fff8b0fa622f64 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 6 Dec 2022 15:50:39 +0100 Subject: [PATCH 26/28] Simplify test spec structure --- tests/collectors.py | 9 ++--- tests/modules/net_vpc/tftest.yaml | 36 +----------------- tests/modules/organization/tftest.yaml | 52 ++------------------------ 3 files changed, 8 insertions(+), 89 deletions(-) diff --git a/tests/collectors.py b/tests/collectors.py index 78f3febff..e69782f09 100644 --- a/tests/collectors.py +++ b/tests/collectors.py @@ -57,14 +57,11 @@ class FabricTestFile(pytest.File): raise Exception(f'cannot read test spec {self.path}: {e}') except KeyError as e: raise Exception(f'`module` key not found in {self.path}: {e}') + common = raw.pop('common_tfvars', []) for test_name, spec in raw.get('tests', {}).items(): + spec = {} if spec is None else spec inventories = spec.get('inventory', [f'{test_name}.yaml']) - try: - tfvars = spec['tfvars'] - except KeyError: - raise Exception( - f'test `{test_name}` in {self.path} does not contain a `tfvars` key' - ) + tfvars = common + [f'{test_name}.tfvars'] + spec.get('tfvars', []) for i in inventories: name = test_name if isinstance(inventories, list) and len(inventories) > 1: diff --git a/tests/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml index 955206c98..b2b09798b 100644 --- a/tests/modules/net_vpc/tftest.yaml +++ b/tests/modules/net_vpc/tftest.yaml @@ -13,48 +13,16 @@ # limitations under the License. module: modules/net-vpc +common_tfvars: + - common.tfvars tests: simple: - tfvars: - - common.tfvars - subnets: - tfvars: - - common.tfvars - - subnets.tfvars - peering: - tfvars: - - common.tfvars - - peering.tfvars - shared_vpc: - tfvars: - - common.tfvars - - shared_vpc.tfvars - factory: - tfvars: - - common.tfvars - - factory.tfvars - psa_simple: - tfvars: - - common.tfvars - - psa_simple.tfvars - psa_routes_export: - tfvars: - - common.tfvars - - psa_routes_export.tfvars - psa_routes_import: - tfvars: - - common.tfvars - - psa_routes_import.tfvars - psa_routes_import_export: - tfvars: - - common.tfvars - - psa_routes_import_export.tfvars diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml index 264abf176..644410eda 100644 --- a/tests/modules/organization/tftest.yaml +++ b/tests/modules/organization/tftest.yaml @@ -14,65 +14,19 @@ module: modules/organization +common_tfvars: + - common.tfvars + tests: audit_config: - tfvars: - - common.tfvars - - audit_config.tfvars - iam: - tfvars: - - common.tfvars - - iam.tfvars - iam_additive: - tfvars: - - common.tfvars - - iam_additive.tfvars - logging: - tfvars: - - common.tfvars - - logging.tfvars - logging_exclusions: - tfvars: - - common.tfvars - - logging_exclusions.tfvars - org_policies_list: - tfvars: - - common.tfvars - - org_policies_list.tfvars - org_policies_boolean: - tfvars: - - common.tfvars - - org_policies_boolean.tfvars - org_policies_custom_constraints: - tfvars: - - common.tfvars - - org_policies_custom_constraints.tfvars - tags: - tfvars: - - common.tfvars - - network_tags.tfvars - - resource_tags.tfvars - firewall_policies: - tfvars: - - common.tfvars - - firewall_policies.tfvars - firewall_policies_factory: - tfvars: - - common.tfvars - - firewall_policies_factory.tfvars - firewall_policies_factory_combined: - tfvars: - - common.tfvars - - firewall_policies.tfvars - - firewall_policies_factory.tfvars From 51594c141372514bb72e2e37338ed610232defe1 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 6 Dec 2022 16:12:38 +0100 Subject: [PATCH 27/28] Fix tests --- tests/modules/net_vpc/simple.tfvars | 1 + .../organization/firewall_policies_factory_combined.tfvars | 1 + tests/modules/organization/network_tags.tfvars | 5 ----- .../organization/{resource_tags.tfvars => tags.tfvars} | 5 +++++ tests/modules/organization/tftest.yaml | 3 +++ 5 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 tests/modules/net_vpc/simple.tfvars create mode 100644 tests/modules/organization/firewall_policies_factory_combined.tfvars delete mode 100644 tests/modules/organization/network_tags.tfvars rename tests/modules/organization/{resource_tags.tfvars => tags.tfvars} (91%) diff --git a/tests/modules/net_vpc/simple.tfvars b/tests/modules/net_vpc/simple.tfvars new file mode 100644 index 000000000..6f848aa99 --- /dev/null +++ b/tests/modules/net_vpc/simple.tfvars @@ -0,0 +1 @@ +# skip boilerplate check diff --git a/tests/modules/organization/firewall_policies_factory_combined.tfvars b/tests/modules/organization/firewall_policies_factory_combined.tfvars new file mode 100644 index 000000000..6f848aa99 --- /dev/null +++ b/tests/modules/organization/firewall_policies_factory_combined.tfvars @@ -0,0 +1 @@ +# skip boilerplate check diff --git a/tests/modules/organization/network_tags.tfvars b/tests/modules/organization/network_tags.tfvars deleted file mode 100644 index 6c5c3ccd9..000000000 --- a/tests/modules/organization/network_tags.tfvars +++ /dev/null @@ -1,5 +0,0 @@ -network_tags = { - net_environment = { - network = "foobar" - } -} diff --git a/tests/modules/organization/resource_tags.tfvars b/tests/modules/organization/tags.tfvars similarity index 91% rename from tests/modules/organization/resource_tags.tfvars rename to tests/modules/organization/tags.tfvars index 42cf7e0fe..31c6764e3 100644 --- a/tests/modules/organization/resource_tags.tfvars +++ b/tests/modules/organization/tags.tfvars @@ -1,3 +1,8 @@ +network_tags = { + net_environment = { + network = "foobar" + } +} tags = { foo = {} bar = { diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml index 644410eda..c88e05c12 100644 --- a/tests/modules/organization/tftest.yaml +++ b/tests/modules/organization/tftest.yaml @@ -30,3 +30,6 @@ tests: firewall_policies: firewall_policies_factory: firewall_policies_factory_combined: + tfvars: + - firewall_policies.tfvars + - firewall_policies_factory.tfvars From b840fcd74e3d7dbab8c5436287a9be990e477c53 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Tue, 6 Dec 2022 16:13:20 +0100 Subject: [PATCH 28/28] Plan summary tool --- tools/plan_summary.py | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100755 tools/plan_summary.py diff --git a/tools/plan_summary.py b/tools/plan_summary.py new file mode 100755 index 000000000..def79adb4 --- /dev/null +++ b/tools/plan_summary.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +# Copyright 2022 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 + +BASEDIR = Path(__file__).parents[1] +sys.path.append(str(BASEDIR / 'tests')) + +import fixtures + + +@click.command() +@click.argument('module', type=click.Path(), nargs=1) +@click.argument('tfvars', type=click.Path(exists=True), nargs=-1) +def main(module, tfvars): + module = BASEDIR / module + summary = fixtures.plan_summary(module, Path(), tfvars) + 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()