diff --git a/tests/collectors.py b/tests/collectors.py new file mode 100644 index 000000000..e69782f09 --- /dev/null +++ b/tests/collectors.py @@ -0,0 +1,92 @@ +# 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. +"""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 + +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. + + The test spec should contain a `module` key with the path of the + terraform module to test, relative to the root of the repository + + Tests are defined within the top-level `tests` key, 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 + + """ + + 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}') + 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']) + tfvars = common + [f'{test_name}.tfvars'] + spec.get('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): + + 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 = 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 d676bbc00..167c74f73 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,144 +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 -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." - - 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." - 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): - "Returns 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." - 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): - "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): - "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, - 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): - # 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 - 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): - "Runs 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(): - "Returns a function to run Terraform apply on a fixture." - - def run_apply(fixture_path=None, **tf_vars): - "Runs 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/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..f8ef5735b --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/simple.tfvars @@ -0,0 +1,11 @@ +organization = { + domain = "fast.example.com" + id = 123456789012 + customer_id = "C00000000" +} +billing_account = { + id = "000000-111111-222222" + organization_id = 123456789012 +} +prefix = "fast" +outputs_location = "/fast-config" diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml new file mode 100644 index 000000000..703b84b45 --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/simple.yaml @@ -0,0 +1,49 @@ +# 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 + +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/test_plan.py b/tests/fast/stages/s00_bootstrap/test_plan.py deleted file mode 100644 index 781469703..000000000 --- a/tests/fast/stages/s00_bootstrap/test_plan.py +++ /dev/null @@ -1,33 +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. - -# _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 diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s00_bootstrap/tftest.yaml new file mode 100644 index 000000000..4656859bc --- /dev/null +++ b/tests/fast/stages/s00_bootstrap/tftest.yaml @@ -0,0 +1,12 @@ +# skip boilerplate check + +module: fast/stages/00-bootstrap + +tests: + simple: + tfvars: + - simple.tfvars + inventory: + - simple.yaml + - simple_projects.yaml + - simple_sas.yaml diff --git a/tests/fixtures.py b/tests/fixtures.py new file mode 100644 index 000000000..c483063f4 --- /dev/null +++ b/tests/fixtures.py @@ -0,0 +1,235 @@ +# 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. +"""Common fixtures.""" + +import collections +import contextlib +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') + + +@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`. + + - 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 + """ + # make the module_path relative to the root of the repo while still + # supporting absolute paths + module_path = Path(__file__).parents[1] / module_path + 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) + tf_var_files = [(basedir / x).resolve() for x in tf_var_files or []] + plan = tf.plan(output=True, tf_var_file=tf_var_files, tf_vars=tf_vars) + + # 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='plan_summary') +def 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 plan_summary(module_path=module_path, basedir=basedir, + tf_var_files=tf_var_files, **tf_vars) + + return inner + + +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): + inventory_paths = [inventory_paths] + + for path in inventory_paths: + # allow tfvars and inventory to be relative to the caller + path = basedir / path + 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 {} + + # 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='plan_validator') +def plan_validator_fixture(request): + """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' + + """ + + def inner(module_path, inventory_paths, basedir=None, tf_var_files=None, + **tf_vars): + if basedir is None: + basedir = Path(request.fspath).parent + return plan_validator(module_path=module_path, + inventory_paths=inventory_paths, basedir=basedir, + 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/conftest.py b/tests/modules/conftest.py index a34cef656..c199cff7b 100644 --- a/tests/modules/conftest.py +++ b/tests/modules/conftest.py @@ -20,16 +20,11 @@ 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(): +@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/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/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..9cf628d09 --- /dev/null +++ b/tests/modules/net_vpc/factory.yaml @@ -0,0 +1,44 @@ +# 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' + 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/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..8d0bbed71 --- /dev/null +++ b/tests/modules/net_vpc/peering.yaml @@ -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. + +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..c8cf631b2 --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_export.yaml @@ -0,0 +1,60 @@ +# 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 + 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..771d881f9 --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_import.yaml @@ -0,0 +1,60 @@ +# 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 + 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..561fa50ac --- /dev/null +++ b/tests/modules/net_vpc/psa_routes_import_export.yaml @@ -0,0 +1,60 @@ +# 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 + 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..019b443fa --- /dev/null +++ b/tests/modules/net_vpc/psa_simple.yaml @@ -0,0 +1,70 @@ +# 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 + 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..0837dbc4b --- /dev/null +++ b/tests/modules/net_vpc/shared_vpc.yaml @@ -0,0 +1,46 @@ +# 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 + 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.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/net_vpc/simple.yaml b/tests/modules/net_vpc/simple.yaml new file mode 100644 index 000000000..004be7ecf --- /dev/null +++ b/tests/modules/net_vpc/simple.yaml @@ -0,0 +1,36 @@ +# 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 + 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..9ccf31e60 --- /dev/null +++ b/tests/modules/net_vpc/subnets.yaml @@ -0,0 +1,120 @@ +# 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 + 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 deleted file mode 100644 index 2f49f801b..000000000 --- a/tests/modules/net_vpc/test_plan.py +++ /dev/null @@ -1,81 +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_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, - 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_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'] - - -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_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_routes(plan_runner): - "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] 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 359977d65..000000000 --- a/tests/modules/net_vpc/test_plan_psa.py +++ /dev/null @@ -1,79 +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_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'] - - -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_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_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'] 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 affea44c2..000000000 --- a/tests/modules/net_vpc/test_plan_subnets.py +++ /dev/null @@ -1,71 +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" - - -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(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' - }, - } diff --git a/tests/modules/net_vpc/test_routes.py b/tests/modules/net_vpc/test_routes.py new file mode 100644 index 000000000..01d9673dd --- /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(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 = 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/blueprints/conftest.py b/tests/modules/net_vpc/tftest.yaml similarity index 73% rename from tests/blueprints/conftest.py rename to tests/modules/net_vpc/tftest.yaml index ed29d5bb6..b2b09798b 100644 --- a/tests/blueprints/conftest.py +++ b/tests/modules/net_vpc/tftest.yaml @@ -12,10 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest +module: modules/net-vpc +common_tfvars: + - common.tfvars - -def pytest_collection_modifyitems(config, items): - for item in items: - item.add_marker( - pytest.mark.xdist_group(name='/'.join(item.path.parent.parts[-2:]))) +tests: + simple: + subnets: + peering: + shared_vpc: + factory: + psa_simple: + psa_routes_export: + psa_routes_import: + psa_routes_import_export: 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/fast/conftest.py b/tests/modules/organization/audit_config.yaml similarity index 78% rename from tests/fast/conftest.py rename to tests/modules/organization/audit_config.yaml index 3d559e6f0..56e90b622 100644 --- a/tests/fast/conftest.py +++ b/tests/modules/organization/audit_config.yaml @@ -12,9 +12,5 @@ # 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)) +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..4ecc5c72c --- /dev/null +++ b/tests/modules/organization/firewall_policies.yaml @@ -0,0 +1,73 @@ +# 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 + 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..85e565fdd --- /dev/null +++ b/tests/modules/organization/firewall_policies_factory.yaml @@ -0,0 +1,61 @@ +# 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 + 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.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/firewall_policies_factory_combined.yaml b/tests/modules/organization/firewall_policies_factory_combined.yaml new file mode 100644 index 000000000..3b5cf6cc2 --- /dev/null +++ b/tests/modules/organization/firewall_policies_factory_combined.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: + 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/fixture/test.network_tags.tfvars b/tests/modules/organization/fixture/test.network_tags.tfvars deleted file mode 100644 index 6c5c3ccd9..000000000 --- a/tests/modules/organization/fixture/test.network_tags.tfvars +++ /dev/null @@ -1,5 +0,0 @@ -network_tags = { - net_environment = { - network = "foobar" - } -} 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..7b1a8cb95 --- /dev/null +++ b/tests/modules/organization/iam.yaml @@ -0,0 +1,44 @@ +# 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: [] + 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..68eda8c27 --- /dev/null +++ b/tests/modules/organization/iam_additive.yaml @@ -0,0 +1,31 @@ +# 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: [] + 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..8038c9ab5 --- /dev/null +++ b/tests/modules/organization/logging.yaml @@ -0,0 +1,86 @@ +# 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: [] + 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..4d51dd7cd --- /dev/null +++ b/tests/modules/organization/logging_exclusions.yaml @@ -0,0 +1,30 @@ +# 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). + 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.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..310997a4c --- /dev/null +++ b/tests/modules/organization/org_policies_boolean.yaml @@ -0,0 +1,53 @@ +# 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 + 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..c558c066f --- /dev/null +++ b/tests/modules/organization/org_policies_custom_constraints.yaml @@ -0,0 +1,37 @@ +# 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 + 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..39c3a3896 --- /dev/null +++ b/tests/modules/organization/org_policies_list.yaml @@ -0,0 +1,85 @@ +# 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 + 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/tags.tfvars similarity index 91% rename from tests/modules/organization/fixture/test.resource_tags.tfvars rename to tests/modules/organization/tags.tfvars index 42cf7e0fe..31c6764e3 100644 --- a/tests/modules/organization/fixture/test.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/tags.yaml b/tests/modules/organization/tags.yaml new file mode 100644 index 000000000..7da6f77bb --- /dev/null +++ b/tests/modules/organization/tags.yaml @@ -0,0 +1,76 @@ +# 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. + 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..1e041dbc4 100644 --- a/tests/modules/organization/test_plan_org_policies.py +++ b/tests/modules/organization/test_plan_org_policies.py @@ -14,46 +14,32 @@ 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(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 = plan_summary( + 'modules/organization', + tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars']) + 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_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(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 = plan_summary( + 'modules/organization', + tf_var_files=['common.tfvars', f'org_policies_custom_constraints.tfvars']) + 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/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..c88e05c12 --- /dev/null +++ b/tests/modules/organization/tftest.yaml @@ -0,0 +1,35 @@ +# 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 + +common_tfvars: + - common.tfvars + +tests: + audit_config: + iam: + iam_additive: + logging: + logging_exclusions: + org_policies_list: + org_policies_boolean: + org_policies_custom_constraints: + tags: + firewall_policies: + firewall_policies_factory: + firewall_policies_factory_combined: + 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) 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 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()