Added tag factory option for organization module (#3178)

* Added tag factory option for organization module

* added tags-factory tests

* added tag factory for project module

* missing header

* added lookup catch for org tag values and fixed incorrect link in doco

* fixed factory locals from copy/paste

* added full doco/tests for project tags factory. fixed failed test looking for ID in yamls

* added context option for factories_config to configure existing K/Vs

---------

Co-authored-by: Ludovico Magnocavallo <ludomagno@google.com>
This commit is contained in:
Liam Nesteroff
2025-06-23 16:24:43 +10:00
committed by GitHub
parent 1dde5258fa
commit 1fbb2cb330
8 changed files with 335 additions and 59 deletions

View File

@@ -26,6 +26,7 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
- [Custom Roles](#custom-roles)
- [Custom Roles Factory](#custom-roles-factory)
- [Tags](#tags)
- [Tags Factory](#tags-factory)
- [Files](#files)
- [Variables](#variables)
- [Outputs](#outputs)
@@ -134,7 +135,7 @@ Refer to the [project module](../project/README.md#iam) for examples of the IAM
### Organization Policy Factory
See the [organization policy factory in the project module](../project#organization-policy-factory).
See the [organization policy factory in the project module](../project/README.md#organization-policy-factory).
### Organization Policy Custom Constraints
@@ -512,6 +513,41 @@ module "org" {
# tftest modules=1 resources=5 inventory=network-tags.yaml e2e serial
```
### Tags Factory
Tags can also be specified via a factory in a similar way to organization policies and policy constraints. Each file is mapped to tag key, where
- the key name defaults to the file name but can be overridden via a `name` attribute in the yaml
- The structure of the YAML file allows defining the `description`, `iam` bindings, and a map of `values` for the tag key, including their own descriptions and IAM.
- Tags defined via the `tags` and `network_tags` variables are merged with those from the factory, and will override factory definitions in case of duplicate names.
The example below deploys a `cost-center` tag key and its values from a YAML file.
```hcl
module "org" {
source = "./fabric/modules/organization"
organization_id = var.organization_id
factories_config = {
tags = "data/tags"
}
}
# tftest modules=1 resources=4 files=cost-center inventory=tags-factory.yaml
```
```yaml
# tftest-file id=cost-center path=data/tags/cost-center.yaml
description: "Tag for internal cost allocation."
iam:
"roles/resourcemanager.tagViewer":
- "group:finance-team@example.com"
values:
engineering:
description: "Engineering department."
marketing:
description: "Marketing department."
```
<!-- TFDOC OPTS files:1 -->
<!-- BEGIN TFDOC -->
## Files
@@ -524,7 +560,7 @@ module "org" {
| [org-policy-custom-constraints.tf](./org-policy-custom-constraints.tf) | None | <code>google_org_policy_custom_constraint</code> |
| [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | <code>google_org_policy_policy</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [tags.tf](./tags.tf) | None | <code>google_tags_tag_binding</code> · <code>google_tags_tag_key</code> · <code>google_tags_tag_key_iam_binding</code> · <code>google_tags_tag_key_iam_member</code> · <code>google_tags_tag_value</code> · <code>google_tags_tag_value_iam_binding</code> · <code>google_tags_tag_value_iam_member</code> |
| [tags.tf](./tags.tf) | Manages GCP Secure Tags, keys, values, and IAM. | <code>google_tags_tag_binding</code> · <code>google_tags_tag_key</code> · <code>google_tags_tag_key_iam_binding</code> · <code>google_tags_tag_key_iam_member</code> · <code>google_tags_tag_value</code> · <code>google_tags_tag_value_iam_binding</code> · <code>google_tags_tag_value_iam_member</code> |
| [variables-iam.tf](./variables-iam.tf) | None | |
| [variables-logging.tf](./variables-logging.tf) | None | |
| [variables-tags.tf](./variables-tags.tf) | None | |
@@ -535,11 +571,11 @@ module "org" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [organization_id](variables.tf#L96) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
| [organization_id](variables.tf#L99) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
| [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [factories_config](variables.tf#L31) | Paths to data files and folders that enable factory functionality. | <code title="object&#40;&#123;&#10; custom_roles &#61; optional&#40;string&#41;&#10; org_policies &#61; optional&#40;string&#41;&#10; org_policy_custom_constraints &#61; optional&#40;string&#41;&#10; context &#61; optional&#40;object&#40;&#123;&#10; org_policies &#61; optional&#40;map&#40;map&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policy](variables.tf#L45) | Hierarchical firewall policies to associate to the organization. | <code title="object&#40;&#123;&#10; name &#61; string&#10; policy &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [factories_config](variables.tf#L31) | Paths to data files and folders that enable factory functionality. | <code title="object&#40;&#123;&#10; custom_roles &#61; optional&#40;string&#41;&#10; org_policies &#61; optional&#40;string&#41;&#10; org_policy_custom_constraints &#61; optional&#40;string&#41;&#10; tags &#61; optional&#40;string&#41;&#10; context &#61; optional&#40;object&#40;&#123;&#10; org_policies &#61; optional&#40;map&#40;map&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; tag_keys &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; tag_values &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policy](variables.tf#L48) | Hierarchical firewall policies to associate to the organization. | <code title="object&#40;&#123;&#10; name &#61; string&#10; policy &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [iam](variables-iam.tf#L17) | IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
@@ -550,8 +586,8 @@ module "org" {
| [logging_settings](variables-logging.tf#L35) | Default settings for logging resources. | <code title="object&#40;&#123;&#10; disable_default_sink &#61; optional&#40;bool&#41;&#10; storage_location &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [logging_sinks](variables-logging.tf#L45) | Logging sinks to create for the organization. | <code title="map&#40;object&#40;&#123;&#10; bq_partitioned_table &#61; optional&#40;bool, false&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; string&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; optional&#40;string&#41;&#10; iam &#61; optional&#40;bool, true&#41;&#10; include_children &#61; optional&#40;bool, true&#41;&#10; intercept_children &#61; optional&#40;bool, false&#41;&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [network_tags](variables-tags.tf#L17) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings &#61; optional&#40;map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings_additive &#61; optional&#40;map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; network &#61; string &#35; project_id&#47;vpc_name&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings &#61; optional&#40;map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings_additive &#61; optional&#40;map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L54) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; parameters &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policy_custom_constraints](variables.tf#L82) | Organization policy custom constraints keyed by constraint name. | <code title="map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string&#41;&#10; action_type &#61; string&#10; condition &#61; string&#10; method_types &#61; list&#40;string&#41;&#10; resource_types &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L57) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; parameters &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policy_custom_constraints](variables.tf#L85) | Organization policy custom constraints keyed by constraint name. | <code title="map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string&#41;&#10; action_type &#61; string&#10; condition &#61; string&#10; method_types &#61; list&#40;string&#41;&#10; resource_types &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [tag_bindings](variables-tags.tf#L81) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [tags](variables-tags.tf#L88) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings &#61; optional&#40;map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings_additive &#61; optional&#40;map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings &#61; optional&#40;map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings_additive &#61; optional&#40;map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |

View File

@@ -5,7 +5,7 @@
* 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
* 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,
@@ -14,14 +14,65 @@
* limitations under the License.
*/
# tfdoc:file:description Manages GCP Secure Tags, keys, values, and IAM.
locals {
_factory_tags_data_path = pathexpand(coalesce(var.factories_config.tags, "-"))
_factory_tags_data_raw = {
for f in try(fileset(local._factory_tags_data_path, "*.yaml"), []) :
f => yamldecode(file("${local._factory_tags_data_path}/${f}"))
}
_factory_tags_data = {
for f, v_raw in local._factory_tags_data_raw :
coalesce(lookup(v_raw, "name", null), trimsuffix(f, ".yaml")) => {
description = lookup(v_raw, "description", null)
iam = lookup(v_raw, "iam", {})
iam_bindings = lookup(v_raw, "iam_bindings", {})
iam_bindings_additive = lookup(v_raw, "iam_bindings_additive", {})
network = lookup(v_raw, "network", null)
values = {
for vk, vv_raw in lookup(v_raw, "values", {}) : vk => {
description = lookup(vv_raw, "description", null)
iam = lookup(vv_raw, "iam", {})
iam_bindings = lookup(vv_raw, "iam_bindings", {})
iam_bindings_additive = lookup(vv_raw, "iam_bindings_additive", {})
}
}
}
}
_tags_merged = merge(local._factory_tags_data, var.tags, var.network_tags)
tags = {
for k, v in local._tags_merged : k => {
description = v.description
iam = v.iam
iam_bindings = v.iam_bindings
iam_bindings_additive = v.iam_bindings_additive
network = lookup(v, "network", null)
id = try(coalesce(
lookup(v, "id", null),
lookup(var.factories_config.context.tag_keys, k, null)
), null)
values = {
for vk, vv in lookup(v, "values", {}) : vk => {
description = vv.description
iam = vv.iam
iam_bindings = vv.iam_bindings
iam_bindings_additive = vv.iam_bindings_additive
id = try(coalesce(
lookup(vv, "id", null),
lookup(var.factories_config.context.tag_values, "${k}/${vk}", null)
), null)
}
}
}
}
_tag_iam = flatten([
for k, v in local.tags : [
for role in keys(v.iam) : {
# we cycle on keys here so we don't risk injecting dynamic values
for role in keys(lookup(v, "iam", {})) : {
# We cycle on keys here so we don't risk injecting dynamic values.
role = role
tag = k
tag_id = v.id
tag_id = lookup(v, "id", null)
}
]
])
@@ -38,18 +89,18 @@ locals {
])
_tag_values = flatten([
for k, v in local.tags : [
for vk, vv in v.values : {
for vk, vv in lookup(v, "values", {}) : {
description = vv.description,
key = "${k}/${vk}"
iam_bindings = keys(vv.iam_bindings)
iam_bindings_additive = keys(vv.iam_bindings_additive)
id = try(vv.id, null)
iam_bindings = keys(lookup(vv, "iam_bindings", {}))
iam_bindings_additive = keys(lookup(vv, "iam_bindings_additive", {}))
id = lookup(vv, "id", null)
name = vk
# we only store keys here so we don't risk injecting dynamic values
roles = keys(vv.iam)
roles = keys(lookup(vv, "iam", {}))
tag = k
tag_id = v.id
tag_network = try(v.network, null) != null
tag_id = lookup(v, "id", null)
tag_network = lookup(v, "network", null) != null
}
]
])
@@ -58,19 +109,19 @@ locals {
}
tag_iam_bindings = merge([
for k, v in local.tags : {
for bk in keys(v.iam_bindings) : "${k}:${bk}" => {
for bk in keys(lookup(v, "iam_bindings", {})) : "${k}:${bk}" => {
binding = bk
tag = k
tag_id = v.id
tag_id = lookup(v, "id", null)
}
}
]...)
tag_iam_bindings_additive = merge([
for k, v in local.tags : {
for bk in keys(v.iam_bindings_additive) : "${k}:${bk}" => {
for bk in keys(lookup(v, "iam_bindings_additive", {})) : "${k}:${bk}" => {
binding = bk
tag = k
tag_id = v.id
tag_id = lookup(v, "id", null)
}
}
]...)
@@ -104,13 +155,12 @@ locals {
tag_values = {
for v in local._tag_values : v.key => v
}
tags = merge(var.tags, var.network_tags)
}
# keys
resource "google_tags_tag_key" "default" {
for_each = { for k, v in local.tags : k => v if v.id == null }
for_each = { for k, v in local.tags : k => v if lookup(v, "id", null) == null }
parent = var.organization_id
purpose = (
lookup(each.value, "network", null) == null ? null : "GCE_FIREWALL"
@@ -167,7 +217,7 @@ resource "google_tags_tag_key_iam_member" "bindings" {
# values
resource "google_tags_tag_value" "default" {
for_each = { for k, v in local.tag_values : k => v if v.id == null }
for_each = { for k, v in local.tag_values : k => v if lookup(v, "id", null) == null }
parent = (
each.value.tag_id == null
? google_tags_tag_key.default[each.value.tag].id

View File

@@ -34,8 +34,11 @@ variable "factories_config" {
custom_roles = optional(string)
org_policies = optional(string)
org_policy_custom_constraints = optional(string)
tags = optional(string)
context = optional(object({
org_policies = optional(map(map(string)), {})
tag_keys = optional(map(string), {})
tag_values = optional(map(string), {})
}), {})
})
nullable = false

File diff suppressed because one or more lines are too long

View File

@@ -14,14 +14,65 @@
* limitations under the License.
*/
# tfdoc:file:description Manages GCP Secure Tags, keys, values, and IAM.
locals {
_factory_tags_data_path = pathexpand(coalesce(var.factories_config.tags, "-"))
_factory_tags_data_raw = {
for f in try(fileset(local._factory_tags_data_path, "*.yaml"), []) :
f => yamldecode(file("${local._factory_tags_data_path}/${f}"))
}
_factory_tags_data = {
for f, v_raw in local._factory_tags_data_raw :
coalesce(lookup(v_raw, "name", null), trimsuffix(f, ".yaml")) => {
description = lookup(v_raw, "description", null)
iam = lookup(v_raw, "iam", {})
iam_bindings = lookup(v_raw, "iam_bindings", {})
iam_bindings_additive = lookup(v_raw, "iam_bindings_additive", {})
network = lookup(v_raw, "network", null)
values = {
for vk, vv_raw in lookup(v_raw, "values", {}) : vk => {
description = lookup(vv_raw, "description", null)
iam = lookup(vv_raw, "iam", {})
iam_bindings = lookup(vv_raw, "iam_bindings", {})
iam_bindings_additive = lookup(vv_raw, "iam_bindings_additive", {})
}
}
}
}
_tags_merged = merge(local._factory_tags_data, var.tags, var.network_tags)
tags = {
for k, v in local._tags_merged : k => {
description = v.description
iam = v.iam
iam_bindings = v.iam_bindings
iam_bindings_additive = v.iam_bindings_additive
network = lookup(v, "network", null)
id = try(coalesce(
lookup(v, "id", null),
lookup(var.factories_config.context.tag_keys, k, null)
), null)
values = {
for vk, vv in lookup(v, "values", {}) : vk => {
description = vv.description
iam = vv.iam
iam_bindings = vv.iam_bindings
iam_bindings_additive = vv.iam_bindings_additive
id = try(coalesce(
lookup(vv, "id", null),
lookup(var.factories_config.context.tag_values, "${k}/${vk}", null)
), null)
}
}
}
}
_tag_iam = flatten([
for k, v in local.tags : [
for role in keys(v.iam) : {
for role in keys(lookup(v, "iam", {})) : {
# we cycle on keys here so we don't risk injecting dynamic values
role = role
tag = k
tag_id = v.id
tag_id = lookup(v, "id", null)
}
]
])
@@ -38,18 +89,18 @@ locals {
])
_tag_values = flatten([
for k, v in local.tags : [
for vk, vv in v.values : {
for vk, vv in lookup(v, "values", {}) : {
description = vv.description,
key = "${k}/${vk}"
iam_bindings = keys(vv.iam_bindings)
iam_bindings_additive = keys(vv.iam_bindings_additive)
id = try(vv.id, null)
iam_bindings = keys(lookup(vv, "iam_bindings", {}))
iam_bindings_additive = keys(lookup(vv, "iam_bindings_additive", {}))
id = lookup(vv, "id", null)
name = vk
# we only store keys here so we don't risk injecting dynamic values
roles = keys(vv.iam)
roles = keys(lookup(vv, "iam", {}))
tag = k
tag_id = v.id
tag_network = try(v.network, null) != null
tag_id = lookup(v, "id", null)
tag_network = lookup(v, "network", null) != null
}
]
])
@@ -58,19 +109,19 @@ locals {
}
tag_iam_bindings = merge([
for k, v in local.tags : {
for bk in keys(v.iam_bindings) : "${k}:${bk}" => {
for bk in keys(lookup(v, "iam_bindings", {})) : "${k}:${bk}" => {
binding = bk
tag = k
tag_id = v.id
tag_id = lookup(v, "id", null)
}
}
]...)
tag_iam_bindings_additive = merge([
for k, v in local.tags : {
for bk in keys(v.iam_bindings_additive) : "${k}:${bk}" => {
for bk in keys(lookup(v, "iam_bindings_additive", {})) : "${k}:${bk}" => {
binding = bk
tag = k
tag_id = v.id
tag_id = lookup(v, "id", null)
}
}
]...)
@@ -104,13 +155,12 @@ locals {
tag_values = {
for v in local._tag_values : v.key => v
}
tags = merge(var.tags, var.network_tags)
}
# keys
resource "google_tags_tag_key" "default" {
for_each = { for k, v in local.tags : k => v if v.id == null }
for_each = { for k, v in local.tags : k => v if lookup(v, "id", null) == null }
parent = "projects/${local.project.project_id}"
purpose = (
lookup(each.value, "network", null) == null ? null : "GCE_FIREWALL"
@@ -167,7 +217,7 @@ resource "google_tags_tag_key_iam_member" "bindings" {
# values
resource "google_tags_tag_value" "default" {
for_each = { for k, v in local.tag_values : k => v if v.id == null }
for_each = { for k, v in local.tag_values : k => v if lookup(v, "id", null) == null }
parent = (
each.value.tag_id == null
? google_tags_tag_key.default[each.value.tag].id

View File

@@ -90,9 +90,12 @@ variable "factories_config" {
observability = optional(string)
org_policies = optional(string)
quotas = optional(string)
tags = optional(string)
context = optional(object({
notification_channels = optional(map(string), {})
org_policies = optional(map(map(string)), {})
tag_keys = optional(map(string), {})
tag_values = optional(map(string), {})
}), {})
})
nullable = false

View File

@@ -0,0 +1,42 @@
# Copyright 2023 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.org.google_tags_tag_key.default["cost-center"]:
description: Tag for internal cost allocation.
parent: organizations/1122334455
purpose: null
purpose_data: null
short_name: cost-center
timeouts: null
module.org.google_tags_tag_key_iam_binding.default["cost-center:roles/resourcemanager.tagViewer"]:
condition: []
members:
- group:finance-team@example.com
role: roles/resourcemanager.tagViewer
module.org.google_tags_tag_value.default["cost-center/engineering"]:
description: Engineering department.
short_name: engineering
timeouts: null
module.org.google_tags_tag_value.default["cost-center/marketing"]:
description: "Marketing department."
short_name: marketing
timeouts: null
counts:
google_tags_tag_key: 1
google_tags_tag_key_iam_binding: 1
google_tags_tag_value: 2
modules: 1
resources: 4

View File

@@ -0,0 +1,53 @@
# Copyright 2023 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.project.google_project.project[0]:
auto_create_network: false
billing_account: 123456-123456-123456
deletion_policy: 'DELETE'
folder_id: '1122334455'
labels: null
name: test-project
org_id: null
project_id: test-project
timeouts: null
module.project.google_tags_tag_key.default["workloads"]:
description: Tag for workload classifications.
parent: projects/test-project
purpose: null
purpose_data: null
short_name: workloads
timeouts: null
module.project.google_tags_tag_key_iam_binding.default["workloads:roles/resourcemanager.tagViewer"]:
condition: []
members:
- group:devops@example.com
role: roles/resourcemanager.tagViewer
module.project.google_tags_tag_value.default["workloads/frontend"]:
description: "Frontend workloads."
short_name: frontend
timeouts: null
module.project.google_tags_tag_value.default["workloads/backend"]:
description: "Backend workloads."
short_name: backend
timeouts: null
counts:
google_project: 1
google_tags_tag_key: 1
google_tags_tag_key_iam_binding: 1
google_tags_tag_value: 2
modules: 1
resources: 5