diff --git a/blueprints/factories/project-factory/README.md b/blueprints/factories/project-factory/README.md index fe65b6bf6..e496aa4d1 100644 --- a/blueprints/factories/project-factory/README.md +++ b/blueprints/factories/project-factory/README.md @@ -41,46 +41,40 @@ The Project Factory is meant to be executed by a Service Account (or a regular u ### Terraform code -```tfvars -# ./terraform.tfvars -data_dir = "data/projects/" -defaults_file = "data/defaults.yaml" -``` - ```hcl -# ./main.tf - locals { - defaults = yamldecode(file(var.defaults_file)) + defaults = yamldecode(file(local._defaults_file)) projects = { - for f in fileset("${var.data_dir}", "**/*.yaml") : - trimsuffix(f, ".yaml") => yamldecode(file("${var.data_dir}/${f}")) + for f in fileset("${local._data_dir}", "**/*.yaml") : + trimsuffix(f, ".yaml") => yamldecode(file("${local._data_dir}/${f}")) } + # these are usually set via variables + _base_dir = "./fabric/blueprints/factories/project-factory" + _data_dir = "${local._base_dir}/sample-data/projects/" + _defaults_file = "${local._base_dir}/sample-data/defaults.yaml" } module "projects" { - source = "./factories/project-factory" - for_each = local.projects - defaults = local.defaults - project_id = each.key - billing_account_id = try(each.value.billing_account_id, null) - billing_alert = try(each.value.billing_alert, null) - dns_zones = try(each.value.dns_zones, []) - essential_contacts = try(each.value.essential_contacts, []) - folder_id = each.value.folder_id - group_iam = try(each.value.group_iam, {}) - iam = try(each.value.iam, {}) - kms_service_agents = try(each.value.kms, {}) - labels = try(each.value.labels, {}) - org_policies = try(each.value.org_policies, null) - secrets = try(each.value.secrets, {}) - service_accounts = try(each.value.service_accounts, {}) - services = try(each.value.services, []) - services_iam = try(each.value.services_iam, {}) - vpc = try(each.value.vpc, null) + source = "./fabric/blueprints/factories/project-factory" + for_each = local.projects + defaults = local.defaults + project_id = each.key + billing_account_id = try(each.value.billing_account_id, null) + billing_alert = try(each.value.billing_alert, null) + dns_zones = try(each.value.dns_zones, []) + essential_contacts = try(each.value.essential_contacts, []) + folder_id = each.value.folder_id + group_iam = try(each.value.group_iam, {}) + iam = try(each.value.iam, {}) + kms_service_agents = try(each.value.kms, {}) + labels = try(each.value.labels, {}) + org_policies = try(each.value.org_policies, null) + service_accounts = try(each.value.service_accounts, {}) + services = try(each.value.services, []) + service_identities_iam = try(each.value.service_identities_iam, {}) + vpc = try(each.value.vpc, null) } - -# tftest skip +# tftest modules=7 resources=27 ``` ### Projects configuration diff --git a/blueprints/factories/project-factory/sample-data/defaults.yaml b/blueprints/factories/project-factory/sample-data/defaults.yaml new file mode 100644 index 000000000..af810c941 --- /dev/null +++ b/blueprints/factories/project-factory/sample-data/defaults.yaml @@ -0,0 +1,28 @@ +# skip boilerplate check + +billing_account_id: 012345-67890A-BCDEF0 + +# [opt] Setup for billing alerts +billing_alert: + amount: 1000 + thresholds: + current: [0.5, 0.8] + forecasted: [0.5, 0.8] + credit_treatment: INCLUDE_ALL_CREDITS + +environment_dns_zone: dev.example.org + +# [opt] Contacts for billing alerts and important notifications +essential_contacts: ["team-contacts@example.com"] + +# [opt] Labels set for all projects +labels: + environment: dev + department: accounting + application: example-app + foo: bar + +# [opt] Additional notification channels for billing +notification_channels: [] +shared_vpc_self_link: projects/foo/networks/bar +vpc_host_project: diff --git a/blueprints/factories/project-factory/sample-data/projects/project.yaml b/blueprints/factories/project-factory/sample-data/projects/project.yaml new file mode 100644 index 000000000..13a8f5f52 --- /dev/null +++ b/blueprints/factories/project-factory/sample-data/projects/project.yaml @@ -0,0 +1,100 @@ +# skip boilerplate check + +# [opt] Billing account id - overrides default if set +billing_account_id: 012345-67890A-BCDEF0 + +# [opt] Billing alerts config - overrides default if set +billing_alert: + amount: 10 + thresholds: + current: + - 0.5 + - 0.8 + forecasted: [] + credit_treatment: INCLUDE_ALL_CREDITS + +# [opt] DNS zones to be created as children of the environment_dns_zone defined in defaults +dns_zones: + - lorem + - ipsum + +# [opt] Contacts for billing alerts and important notifications +essential_contacts: + - team-a-contacts@example.com + +# Folder the project will be created as children of +folder_id: folders/012345678901 + +# [opt] Authoritative IAM bindings in group => [roles] format +group_iam: + test-team-foobar@fast-lab-0.gcp-pso-italy.net: + - roles/compute.admin + +# [opt] Authoritative IAM bindings in role => [principals] format +# Generally used to grant roles to service accounts external to the project +iam: + roles/compute.admin: + - serviceAccount:service-account + +# [opt] Service robots and keys they will be assigned as cryptoKeyEncrypterDecrypter +# in service => [keys] format +kms_service_agents: + compute: [key1, key2] + storage: [key1, key2] + +# [opt] Labels for the project - merged with the ones defined in defaults +labels: + environment: dev + +# [opt] Org policy overrides defined at project level +org_policies: + policy_boolean: + constraints/compute.disableGuestAttributesAccess: true + policy_list: + constraints/compute.trustedImageProjects: + inherit_from_parent: null + status: true + suggested_value: null + values: + - projects/fast-dev-iac-core-0 + +# [opt] Service account to create for the project and their roles on the project +# in name => [roles] format +service_accounts: + another-service-account: + - roles/compute.admin + my-service-account: + - roles/compute.admin + +# [opt] APIs to enable on the project. +services: + - storage.googleapis.com + - stackdriver.googleapis.com + - compute.googleapis.com + +# [opt] Roles to assign to the service identities in service => [roles] format +service_identities_iam: + compute: + - roles/storage.objectViewer + + # [opt] VPC setup. + # If set enables the `compute.googleapis.com` service and configures + # service project attachment +vpc: + # [opt] If set, enables the container API + gke_setup: + # Grants "roles/container.hostServiceAgentUser" to the container robot if set + enable_host_service_agent: false + + # Grants "roles/compute.securityAdmin" to the container robot if set + enable_security_admin: true + + # Host project the project will be service project of + host_project: fast-dev-net-spoke-0 + + # [opt] Subnets in the host project where principals will be granted networkUser + # in region/subnet-name => [principals] + subnets_iam: + europe-west1/dev-default-ew1: + - user:foobar@example.com + - serviceAccount:service-account1 diff --git a/tests/conftest.py b/tests/conftest.py index d32e2fad3..48498db55 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,8 @@ BASEDIR = os.path.dirname(os.path.dirname(__file__)) def _plan_runner(): "Returns a function to run Terraform plan on a fixture." - def run_plan(fixture_path=None, targets=None, refresh=True, **tf_vars): + def run_plan(fixture_path=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 @@ -41,12 +42,14 @@ def _plan_runner(): 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, + 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(upgrade=True) - return tf.plan(output=True, refresh=refresh, tf_vars=tf_vars, + plan = tf.plan(output=True, refresh=refresh, tf_vars=tf_vars, targets=targets) + return plan return run_plan @@ -102,10 +105,11 @@ def recursive_e2e_plan_runner(_plan_runner): walk_plan(module, modules, resources) def run_plan(fixture_path=None, targets=None, refresh=True, - include_bare_resources=False, compute_sums=True, **tf_vars): + 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, targets=targets, refresh=refresh, - **tf_vars) + tmpdir=tmpdir, **tf_vars) modules = [] resources = [] walk_plan(plan.root_module, modules, resources) diff --git a/tests/examples/test_plan.py b/tests/examples/test_plan.py index f82e7ccf8..be0686424 100644 --- a/tests/examples/test_plan.py +++ b/tests/examples/test_plan.py @@ -29,6 +29,7 @@ def test_example(recursive_e2e_plan_runner, tmp_path, example): expected_modules = int(match.group(1)) if match is not None else 1 expected_resources = int(match.group(2)) if match is not None else 1 - num_modules, num_resources = recursive_e2e_plan_runner(str(tmp_path)) + num_modules, num_resources = recursive_e2e_plan_runner( + str(tmp_path), tmpdir=False) assert expected_modules == num_modules assert expected_resources == num_resources