From f1a3cac8ca516677064c9d3cce8fa32078324e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Fri, 11 Apr 2025 08:34:48 +0000 Subject: [PATCH] Use factory-projects-object to normalize inputs for project module --- modules/net-vpc-factory/factory-project.tf | 23 +- .../factory-projects-object.tf | 257 ++++++++++++++++++ tools/duplicate-diff.py | 4 +- 3 files changed, 267 insertions(+), 17 deletions(-) create mode 100644 modules/net-vpc-factory/factory-projects-object.tf diff --git a/modules/net-vpc-factory/factory-project.tf b/modules/net-vpc-factory/factory-project.tf index 00f783ba3..4e7ae1992 100644 --- a/modules/net-vpc-factory/factory-project.tf +++ b/modules/net-vpc-factory/factory-project.tf @@ -17,22 +17,15 @@ # tfdoc:file:description Dedicated project factory. locals { - projects = { for k, v in local.network_projects : k => merge( - { - billing_account = try(v.project_config.billing_account, var.billing_account) - prefix = try(v.project_config.prefix, var.prefix) - parent = try(v.project_config.parent, var.parent_id) - shared_vpc_host_config = try(v.project_config.shared_vpc_host_config, null) - iam = try(v.project_config.iam, {}) - iam_bindings = try(v.project_config.iam_bindings, {}) - iam_bindings_additive = try(v.project_config.iam_bindings_additive, {}) - iam_by_principals = try(v.project_config.iam_by_principals, {}) - iam_by_principals_additive = try(v.project_config.iam_by_principals_additive, {}) - services = try(v.project_config.services, []) - org_policies = try(v.project_config.org_policies, {}) - }, - v.project_config) + _projects_input = { for k, v in local.network_projects : k => v.project_config } + _projects_config = { + data_defaults = { + billing_account = var.billing_account + prefix = var.prefix + parent = var.parent_id + } } + projects = local._projects_output } module "projects" { diff --git a/modules/net-vpc-factory/factory-projects-object.tf b/modules/net-vpc-factory/factory-projects-object.tf new file mode 100644 index 000000000..3e9c2d714 --- /dev/null +++ b/modules/net-vpc-factory/factory-projects-object.tf @@ -0,0 +1,257 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# inputs +# local._projects_input - parsed data from yaml as map +# local._projects_config = object({ +# data_overrides = ... +# data_defaults = ... +# }) +# outputs: +# local._projects_output - map +locals { + __projects_config = { + data_defaults = merge({ + billing_account = null + contacts = {} + factories_config = merge({ + custom_roles = null + observability = null + org_policies = null + quotas = null + }, try(local._projects_config.data_defaults.factories_config, { + custom_roles = null + observability = null + org_policies = null + quotas = null + }) + ) + labels = {} + metric_scopes = [] + parent = null + prefix = null + service_encryption_key_ids = {} + services = [] + shared_vpc_service_config = merge({ + host_project = null + network_users = [] + service_agent_iam = {} + service_agent_subnet_iam = {} + service_iam_grants = [] + network_subnet_users = {} + }, try(local._projects_config.data_defaults.shared_vpc_service_config, { + host_project = null + network_users = [] + service_agent_iam = {} + service_agent_subnet_iam = {} + service_iam_grants = [] + network_subnet_users = {} + }) + ) + storage_location = null + tag_bindings = {} + service_accounts = {} + vpc_sc = merge({ + perimeter_name = null + perimeter_bridges = [] + is_dry_run = false + }, try(local._projects_config.data_defaults.vpc_sc, { + perimeter_name = null + perimeter_bridges = [] + is_dry_run = false + }) + ) + logging_data_access = {} + }, + try(local._projects_config.data_defaults, {}) + ) + # data_overrides default to null's, to mark that they should not override + data_overrides = merge({ + billing_account = null + contacts = null + factories_config = merge({ + custom_roles = null + observability = null + org_policies = null + quotas = null + }, try(local._projects_config.data_overrides.factories_config, { + custom_roles = null + observability = null + org_policies = null + quotas = null + }) + ) + parent = null + prefix = null + service_encryption_key_ids = null + storage_location = null + tag_bindings = null + services = null + service_accounts = null + vpc_sc = try( + merge( + { + perimeter_name = null + perimeter_bridges = [] + is_dry_run = false + }, + local._projects_config.data_overrides.vpc_sc + ), + null + ) + logging_data_access = null + }, + try(local._projects_config.data_overrides, {}) + ) + } + _projects_output = { + # Semantics of the merges are: + # * if data_overrides. is not null, use this value + # * if _projects_inputs. is not null, use this value + # * use data_default value, which if not set, will provide "empty" type + # This logic is easily implemented using coalesce, even on maps and list and allows to + # set data_overrides. to "", [] or {} to ensure, that empty value is always passed, or do + # the same in _projects_input to prevent falling back to default value + for k, v in local._projects_input : k => merge(v, { + billing_account = try(coalesce( # type: string + local.__projects_config.data_overrides.billing_account, + try(v.billing_account, null), + local.__projects_config.data_defaults.billing_account + ), null) + contacts = coalesce( # type: map + local.__projects_config.data_overrides.contacts, + try(v.contacts, null), + local.__projects_config.data_defaults.contacts + ) + factories_config = { # type: object + custom_roles = try( # type: string + coalesce( + local.__projects_config.data_overrides.factories_config.custom_roles, + try(v.factories_config.custom_roles, null), + local.__projects_config.data_defaults.factories_config.custom_roles + ), + null + ) + observability = try( # type: string + coalesce( + local.__projects_config.data_overrides.factories_config.observability, + try(v.factories_config.observability, null), + local.__projects_config.data_defaults.factories_config.observability + ), + null) + org_policies = try( # type: string + coalesce( + local.__projects_config.data_overrides.factories_config.org_policies, + try(v.factories_config.org_policies, null), + local.__projects_config.data_defaults.factories_config.org_policies + ), + null) + quotas = try( # type: string + coalesce( + local.__projects_config.data_overrides.factories_config.quotas, + try(v.factories_config.quotas, null), + local.__projects_config.data_defaults.factories_config.quotas + ), + null) + } + iam = try(v.iam, {}) # type: map(list(string)) + iam_bindings = try(v.iam_bindings, {}) # type: map(object({...})) + iam_bindings_additive = try(v.iam_bindings_additive, {}) # type: map(object({...})) + iam_by_principals_additive = try(v.iam_by_principals_additive, {}) # type: map(list(string)) + iam_by_principals = try(v.iam_by_principals, {}) # map(list(string)) + labels = coalesce( # type: map(string) + try(v.labels, null), + local.__projects_config.data_defaults.labels + ) + metric_scopes = coalesce( # type: list(string) + try(v.metric_scopes, null), + local.__projects_config.data_defaults.metric_scopes + ) + name = lookup(v, "name", k) # type: string + org_policies = try(v.org_policies, {}) # type: map(object({...})) + parent = try( # type: string, nullable + coalesce( + local.__projects_config.data_overrides.parent, + try(v.parent, null), + local.__projects_config.data_defaults.parent + ), null + ) + prefix = try( # type: string, nullable + coalesce( + local.__projects_config.data_overrides.prefix, + try(v.prefix, null), + local.__projects_config.data_defaults.prefix + ), null + ) + service_encryption_key_ids = coalesce( # type: map(list(string)) + local.__projects_config.data_overrides.service_encryption_key_ids, + try(v.service_encryption_key_ids, null), + local.__projects_config.data_defaults.service_encryption_key_ids + ) + services = coalesce( # type: list(string) + local.__projects_config.data_overrides.services, + try(v.services, null), + local.__projects_config.data_defaults.services + ) + shared_vpc_host_config = ( # type: object({...}) + try(v.shared_vpc_host_config, null) != null + ? merge( + { service_projects = [] }, + v.shared_vpc_host_config + ) + : null + ) + shared_vpc_service_config = ( # type: object({...}) + try(v.shared_vpc_service_config, null) != null + ? merge( + { + host_project = null + network_users = [] + service_agent_iam = {} + service_agent_subnet_iam = {} + service_iam_grants = [] + network_subnet_users = {} + }, + v.shared_vpc_service_config + ) + : local.__projects_config.data_defaults.shared_vpc_service_config + ) + tag_bindings = coalesce( # type: map(string) + local.__projects_config.data_overrides.tag_bindings, + try(v.tag_bindings, null), + local.__projects_config.data_defaults.tag_bindings + ) + vpc_sc = ( # type: object + local.__projects_config.data_overrides.vpc_sc != null + ? local.__projects_config.data_overrides.vpc_sc + : ( + try(v.vpc_sc, null) != null + ? merge({ + perimeter_name = null + perimeter_bridges = [] + is_dry_run = false + }, v.vpc_sc) + : local.__projects_config.data_defaults.vpc_sc + ) + ) + logging_data_access = coalesce( # type: map(object({...})) + local.__projects_config.data_overrides.logging_data_access, + try(v.logging_data_access, null), + local.__projects_config.data_defaults.logging_data_access + ) + }) + } +} diff --git a/tools/duplicate-diff.py b/tools/duplicate-diff.py index 895dcc04a..a87febfc6 100644 --- a/tools/duplicate-diff.py +++ b/tools/duplicate-diff.py @@ -19,9 +19,9 @@ import sys duplicates = [ [ - # "modules/net-vpc-factory/factory-projects-object.tf", - # data factory + "modules/net-vpc-factory/factory-projects-object.tf", "modules/project-factory/factory-projects-object.tf", + # data factory ], ]