Merge remote-tracking branch 'origin/master' into fast-dev
This commit is contained in:
@@ -2,11 +2,20 @@
|
||||
|
||||
This module allows simplified creation and management of one a service account and its IAM bindings.
|
||||
|
||||
The Service Account `key` can be generated with `openssl` library and only the public part uploaded to the Service Account, for more refer to the [Onprem SA Key Management](../../blueprints/cloud-operations/onprem-sa-key-management/) example.
|
||||
|
||||
Note that outputs have no dependencies on IAM bindings to prevent resource cycles.
|
||||
|
||||
## Example
|
||||
## TOC
|
||||
|
||||
<!-- BEGIN TOC -->
|
||||
- [TOC](#toc)
|
||||
- [Simple Example](#simple-example)
|
||||
- [Tag Bindings](#tag-bindings)
|
||||
- [Files](#files)
|
||||
- [Variables](#variables)
|
||||
- [Outputs](#outputs)
|
||||
<!-- END TOC -->
|
||||
|
||||
## Simple Example
|
||||
|
||||
```hcl
|
||||
module "myproject-default-service-accounts" {
|
||||
@@ -27,6 +36,25 @@ module "myproject-default-service-accounts" {
|
||||
}
|
||||
# tftest modules=1 resources=4 inventory=basic.yaml e2e
|
||||
```
|
||||
|
||||
## Tag Bindings
|
||||
|
||||
Use the `tag_bindings` variable to attach tags to the service account. Provide `project_number` to prevent potential permadiffs with the tag binding resource.
|
||||
|
||||
```hcl
|
||||
module "service-account-with-tags" {
|
||||
source = "./fabric/modules/iam-service-account"
|
||||
project_id = var.project_id
|
||||
name = "test-service-account"
|
||||
project_number = var.project_number
|
||||
tag_bindings = {
|
||||
foo = "tagValues/123456789"
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=2 inventory=tags.yaml
|
||||
```
|
||||
|
||||
|
||||
<!-- TFDOC OPTS files:1 -->
|
||||
<!-- BEGIN TFDOC -->
|
||||
## Files
|
||||
@@ -34,7 +62,7 @@ module "myproject-default-service-accounts" {
|
||||
| name | description | resources |
|
||||
|---|---|---|
|
||||
| [iam.tf](./iam.tf) | IAM bindings. | <code>google_billing_account_iam_member</code> · <code>google_folder_iam_member</code> · <code>google_organization_iam_member</code> · <code>google_project_iam_member</code> · <code>google_service_account_iam_binding</code> · <code>google_service_account_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
|
||||
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_service_account</code> · <code>google_service_account_key</code> |
|
||||
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_service_account</code> · <code>google_tags_tag_binding</code> |
|
||||
| [outputs.tf](./outputs.tf) | Module outputs. | |
|
||||
| [variables.tf](./variables.tf) | Module variables. | |
|
||||
| [versions.tf](./versions.tf) | Version pins. | |
|
||||
@@ -43,22 +71,24 @@ module "myproject-default-service-accounts" {
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [name](variables.tf#L108) | Name of the service account to create. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L123) | Project id where service account will be created. | <code>string</code> | ✓ | |
|
||||
| [description](variables.tf#L17) | Optional description. | <code>string</code> | | <code>null</code> |
|
||||
| [display_name](variables.tf#L23) | Display name of the service account to create. | <code>string</code> | | <code>"Terraform-managed."</code> |
|
||||
| [iam](variables.tf#L29) | IAM bindings on the service account in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_billing_roles](variables.tf#L36) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L43) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_bindings_additive](variables.tf#L58) | Individual additive IAM bindings on the service account. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_folder_roles](variables.tf#L73) | Folder roles granted to this service account, by folder id. Non-authoritative. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_organization_roles](variables.tf#L80) | Organization roles granted to this service account, by organization id. Non-authoritative. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_project_roles](variables.tf#L87) | Project roles granted to this service account, by project id. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_sa_roles](variables.tf#L94) | Service account roles granted to this service account, by service account name. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_storage_roles](variables.tf#L101) | Storage roles granted to this service account, by bucket name. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [prefix](variables.tf#L113) | Prefix applied to service account names. | <code>string</code> | | <code>null</code> |
|
||||
| [public_keys_directory](variables.tf#L128) | Path to public keys data files to upload to the service account (should have `.pem` extension). | <code>string</code> | | <code>""</code> |
|
||||
| [service_account_create](variables.tf#L134) | Create service account. When set to false, uses a data source to reference an existing service account. | <code>bool</code> | | <code>true</code> |
|
||||
| [name](variables.tf#L118) | Name of the service account to create. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L133) | Project id where service account will be created. | <code>string</code> | ✓ | |
|
||||
| [create_ignore_already_exists](variables.tf#L17) | If set to true, skip service account creation if a service account with the same email already exists. | <code>bool</code> | | <code>null</code> |
|
||||
| [description](variables.tf#L27) | Optional description. | <code>string</code> | | <code>null</code> |
|
||||
| [display_name](variables.tf#L33) | Display name of the service account to create. | <code>string</code> | | <code>"Terraform-managed."</code> |
|
||||
| [iam](variables.tf#L39) | IAM bindings on the service account in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_billing_roles](variables.tf#L46) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L53) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_bindings_additive](variables.tf#L68) | Individual additive IAM bindings on the service account. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_folder_roles](variables.tf#L83) | Folder roles granted to this service account, by folder id. Non-authoritative. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_organization_roles](variables.tf#L90) | Organization roles granted to this service account, by organization id. Non-authoritative. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_project_roles](variables.tf#L97) | Project roles granted to this service account, by project id. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_sa_roles](variables.tf#L104) | Service account roles granted to this service account, by service account name. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_storage_roles](variables.tf#L111) | Storage roles granted to this service account, by bucket name. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [prefix](variables.tf#L123) | Prefix applied to service account names. | <code>string</code> | | <code>null</code> |
|
||||
| [project_number](variables.tf#L138) | Project number of var.project_id. Set this to avoid permadiffs when creating tag bindings. | <code>string</code> | | <code>null</code> |
|
||||
| [service_account_create](variables.tf#L144) | Create service account. When set to false, uses a data source to reference an existing service account. | <code>bool</code> | | <code>true</code> |
|
||||
| [tag_bindings](variables.tf#L151) | Tag bindings for this service accounts, in key => tag value id format. | <code>map(string)</code> | | <code>{}</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
@@ -69,5 +99,4 @@ module "myproject-default-service-accounts" {
|
||||
| [id](outputs.tf#L33) | Fully qualified service account id. | |
|
||||
| [name](outputs.tf#L41) | Service account name. | |
|
||||
| [service_account](outputs.tf#L49) | Service account resource. | |
|
||||
| [service_account_credentials](outputs.tf#L54) | Service account json credential templates for uploaded public keys data. | |
|
||||
<!-- END TFDOC -->
|
||||
|
||||
@@ -30,36 +30,13 @@ locals {
|
||||
? try(google_service_account.service_account[0], null)
|
||||
: try(data.google_service_account.service_account[0], null)
|
||||
)
|
||||
service_account_credential_templates = {
|
||||
for file, _ in local.public_keys_data : file => jsonencode(
|
||||
{
|
||||
type : "service_account",
|
||||
project_id : var.project_id,
|
||||
private_key_id : split("/", google_service_account_key.upload_key[file].id)[5]
|
||||
private_key : "REPLACE_ME_WITH_PRIVATE_KEY_DATA"
|
||||
client_email : local.resource_email_static
|
||||
client_id : local.service_account.unique_id,
|
||||
auth_uri : "https://accounts.google.com/o/oauth2/auth",
|
||||
token_uri : "https://oauth2.googleapis.com/token",
|
||||
auth_provider_x509_cert_url : "https://www.googleapis.com/oauth2/v1/certs",
|
||||
client_x509_cert_url : "https://www.googleapis.com/robot/v1/metadata/x509/${urlencode(local.resource_email_static)}"
|
||||
}
|
||||
)
|
||||
}
|
||||
public_keys_data = (
|
||||
var.public_keys_directory != ""
|
||||
? {
|
||||
for file in fileset("${path.root}/${var.public_keys_directory}", "*.pem")
|
||||
: file => filebase64("${path.root}/${var.public_keys_directory}/${file}") }
|
||||
: {}
|
||||
)
|
||||
|
||||
# universe-related locals
|
||||
universe = try(regex("^([^:]*):[a-z]", var.project_id)[0], "")
|
||||
project_id_no_universe = element(split(":", var.project_id), 1)
|
||||
sa_domain = join(".", compact([local.project_id_no_universe, local.universe]))
|
||||
}
|
||||
|
||||
|
||||
data "google_service_account" "service_account" {
|
||||
count = var.service_account_create ? 0 : 1
|
||||
project = var.project_id
|
||||
@@ -67,15 +44,16 @@ data "google_service_account" "service_account" {
|
||||
}
|
||||
|
||||
resource "google_service_account" "service_account" {
|
||||
count = var.service_account_create ? 1 : 0
|
||||
project = var.project_id
|
||||
account_id = "${local.prefix}${local.name}"
|
||||
display_name = var.display_name
|
||||
description = var.description
|
||||
count = var.service_account_create ? 1 : 0
|
||||
project = var.project_id
|
||||
account_id = "${local.prefix}${local.name}"
|
||||
display_name = var.display_name
|
||||
description = var.description
|
||||
create_ignore_already_exists = var.create_ignore_already_exists
|
||||
}
|
||||
|
||||
resource "google_service_account_key" "upload_key" {
|
||||
for_each = local.public_keys_data
|
||||
service_account_id = local.service_account.email
|
||||
public_key_data = each.value
|
||||
resource "google_tags_tag_binding" "binding" {
|
||||
for_each = var.tag_bindings
|
||||
parent = "//iam.googleapis.com/projects/${coalesce(var.project_number, var.project_id)}/serviceAccounts/${local.service_account.unique_id}"
|
||||
tag_value = each.value
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2022 Google LLC
|
||||
* 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.
|
||||
@@ -50,8 +50,3 @@ output "service_account" {
|
||||
description = "Service account resource."
|
||||
value = local.service_account
|
||||
}
|
||||
|
||||
output "service_account_credentials" {
|
||||
description = "Service account json credential templates for uploaded public keys data."
|
||||
value = local.service_account_credential_templates
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2022 Google LLC
|
||||
* 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.
|
||||
@@ -14,6 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
variable "create_ignore_already_exists" {
|
||||
description = "If set to true, skip service account creation if a service account with the same email already exists."
|
||||
type = bool
|
||||
default = null
|
||||
validation {
|
||||
condition = !(var.create_ignore_already_exists == true && var.service_account_create == false)
|
||||
error_message = "Cannot set create_ignore_already_exists when service_account_create is false."
|
||||
}
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Optional description."
|
||||
type = string
|
||||
@@ -125,14 +135,22 @@ variable "project_id" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "public_keys_directory" {
|
||||
description = "Path to public keys data files to upload to the service account (should have `.pem` extension)."
|
||||
variable "project_number" {
|
||||
description = "Project number of var.project_id. Set this to avoid permadiffs when creating tag bindings."
|
||||
type = string
|
||||
default = ""
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "service_account_create" {
|
||||
description = "Create service account. When set to false, uses a data source to reference an existing service account."
|
||||
type = bool
|
||||
default = true
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "tag_bindings" {
|
||||
description = "Tag bindings for this service accounts, in key => tag value id format."
|
||||
type = map(string)
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
@@ -494,10 +494,10 @@ service_accounts:
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [factories_config](variables.tf#L120) | Path to folder with YAML resource description data files. | <code title="object({ budgets = optional(object({ billing_account = string budgets_data_path = string notification_channels = optional(map(any), {}) })) context = optional(object({ folder_ids = optional(map(string), {}) iam_principals = optional(map(string), {}) perimeters = optional(map(string), {}) tag_values = optional(map(string), {}) vpc_host_projects = optional(map(string), {}) notification_channels = optional(map(string), {}) }), {}) folders_data_path = optional(string) projects_data_path = optional(string) })">object({…})</code> | ✓ | |
|
||||
| [factories_config](variables.tf#L121) | Path to folder with YAML resource description data files. | <code title="object({ budgets = optional(object({ billing_account = string budgets_data_path = string notification_channels = optional(map(any), {}) })) context = optional(object({ folder_ids = optional(map(string), {}) iam_principals = optional(map(string), {}) tag_values = optional(map(string), {}) vpc_host_projects = optional(map(string), {}) notification_channels = optional(map(string), {}) }), {}) folders_data_path = optional(string) projects_data_path = optional(string) })">object({…})</code> | ✓ | |
|
||||
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string)), {}) factories_config = optional(object({ custom_roles = optional(string) observability = optional(string) org_policies = optional(string) quotas = optional(string) }), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string)), {}) services = optional(list(string), []) shared_vpc_service_config = optional(object({ host_project = string network_users = optional(list(string), []) service_agent_iam = optional(map(list(string)), {}) service_agent_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) network_subnet_users = optional(map(list(string)), {}) }), { host_project = null }) storage_location = optional(string) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_self_roles = optional(list(string)) })), {}) vpc_sc = optional(object({ perimeter_name = string perimeter_bridges = optional(list(string), []) is_dry_run = optional(bool, false) })) logging_data_access = optional(map(object({ ADMIN_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_WRITE = optional(object({ exempted_members = optional(list(string)) })) })), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [data_merges](variables.tf#L64) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | <code title="object({ contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) service_encryption_key_ids = optional(map(list(string)), {}) services = optional(list(string), []) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_self_roles = optional(list(string)) })), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [data_overrides](variables.tf#L83) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string))) factories_config = optional(object({ custom_roles = optional(string) observability = optional(string) org_policies = optional(string) quotas = optional(string) }), {}) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string))) storage_location = optional(string) tag_bindings = optional(map(string)) services = optional(list(string)) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_self_roles = optional(list(string)) }))) vpc_sc = optional(object({ perimeter_name = string perimeter_bridges = optional(list(string), []) is_dry_run = optional(bool, false) })) logging_data_access = optional(map(object({ ADMIN_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_WRITE = optional(object({ exempted_members = optional(list(string)) })) })), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [data_overrides](variables.tf#L83) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string))) factories_config = optional(object({ custom_roles = optional(string) observability = optional(string) org_policies = optional(string) quotas = optional(string) }), {}) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string))) storage_location = optional(string) tag_bindings = optional(map(string)) services = optional(list(string)) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_self_roles = optional(list(string)) }))) vpc_sc = optional(object({ perimeter_name = string perimeter_bridges = optional(list(string), []) is_dry_run = optional(bool, false) })) logging_data_access = optional(map(object({ ADMIN_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_READ = optional(object({ exempted_members = optional(list(string)) })), DATA_WRITE = optional(object({ exempted_members = optional(list(string)) })) }))) })">object({…})</code> | | <code>{}</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
# data_defaults = ...
|
||||
# })
|
||||
# outputs:
|
||||
# projects - map
|
||||
# local._projects_output - map
|
||||
locals {
|
||||
__projects_config = {
|
||||
data_defaults = merge({
|
||||
@@ -78,9 +78,10 @@ locals {
|
||||
},
|
||||
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 = {}
|
||||
contacts = null
|
||||
factories_config = merge({
|
||||
custom_roles = null
|
||||
observability = null
|
||||
@@ -95,40 +96,48 @@ locals {
|
||||
)
|
||||
parent = null
|
||||
prefix = null
|
||||
service_encryption_key_ids = {}
|
||||
service_encryption_key_ids = null
|
||||
storage_location = null
|
||||
tag_bindings = {}
|
||||
services = []
|
||||
service_accounts = {}
|
||||
vpc_sc = merge({
|
||||
perimeter_name = null
|
||||
perimeter_bridges = []
|
||||
is_dry_run = false
|
||||
}, try(local._projects_config.data_overrides.vpc_sc, {
|
||||
perimeter_name = null
|
||||
perimeter_bridges = []
|
||||
is_dry_run = false
|
||||
})
|
||||
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 = {}
|
||||
logging_data_access = null
|
||||
},
|
||||
try(local._projects_config.data_overrides, {})
|
||||
)
|
||||
}
|
||||
_projects_output = {
|
||||
for k, v in local._projects_input : lookup(v, "name", k) => merge(v, {
|
||||
billing_account = try(coalesce(
|
||||
# Semantics of the merges are:
|
||||
# * if data_overrides.<field> is not null, use this value
|
||||
# * if _projects_inputs.<field> 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.<field> 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(
|
||||
contacts = coalesce( # type: map
|
||||
local.__projects_config.data_overrides.contacts,
|
||||
try(v.contacts, null),
|
||||
local.__projects_config.data_defaults.contacts
|
||||
)
|
||||
factories_config = {
|
||||
custom_roles = try(
|
||||
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),
|
||||
@@ -136,21 +145,21 @@ locals {
|
||||
),
|
||||
null
|
||||
)
|
||||
observability = try(
|
||||
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(
|
||||
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(
|
||||
quotas = try( # type: string
|
||||
coalesce(
|
||||
local.__projects_config.data_overrides.factories_config.quotas,
|
||||
try(v.factories_config.quotas, null),
|
||||
@@ -158,36 +167,46 @@ locals {
|
||||
),
|
||||
null)
|
||||
}
|
||||
labels = coalesce(
|
||||
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(
|
||||
metric_scopes = coalesce( # type: list(string)
|
||||
try(v.metric_scopes, null),
|
||||
local.__projects_config.data_defaults.metric_scopes
|
||||
)
|
||||
org_policies = try(v.org_policies, {})
|
||||
parent = coalesce(
|
||||
local.__projects_config.data_overrides.parent,
|
||||
try(v.parent, null),
|
||||
local.__projects_config.data_defaults.parent
|
||||
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 = coalesce(
|
||||
local.__projects_config.data_overrides.prefix,
|
||||
try(v.prefix, null),
|
||||
local.__projects_config.data_defaults.prefix
|
||||
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(
|
||||
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(
|
||||
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 = (
|
||||
shared_vpc_host_config = ( # type: object({...})
|
||||
try(v.shared_vpc_host_config, null) != null
|
||||
? merge(
|
||||
{ service_projects = [] },
|
||||
@@ -195,7 +214,7 @@ locals {
|
||||
)
|
||||
: null
|
||||
)
|
||||
shared_vpc_service_config = (
|
||||
shared_vpc_service_config = ( # type: object({...})
|
||||
try(v.shared_vpc_service_config, null) != null
|
||||
? merge(
|
||||
{
|
||||
@@ -210,7 +229,7 @@ locals {
|
||||
)
|
||||
: local.__projects_config.data_defaults.shared_vpc_service_config
|
||||
)
|
||||
tag_bindings = coalesce(
|
||||
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
|
||||
@@ -246,7 +265,7 @@ locals {
|
||||
: local.__projects_config.data_defaults.vpc_sc
|
||||
)
|
||||
)
|
||||
logging_data_access = coalesce(
|
||||
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
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
# tfdoc:file:description Projects factory locals.
|
||||
|
||||
locals {
|
||||
_hierarchy_projects = (
|
||||
_hierarchy_projects_full_path = (
|
||||
{
|
||||
for f in try(fileset(local._folders_path, "**/*.yaml"), []) :
|
||||
basename(trimsuffix(f, ".yaml")) => merge(
|
||||
trimsuffix(f, ".yaml") => merge(
|
||||
{ parent = dirname(f) == "." ? "default" : dirname(f) },
|
||||
yamldecode(file("${local._folders_path}/${f}"))
|
||||
)
|
||||
@@ -28,13 +28,16 @@ locals {
|
||||
}
|
||||
)
|
||||
_project_path = try(pathexpand(var.factories_config.projects_data_path), null)
|
||||
_projects_input = merge(
|
||||
{
|
||||
for f in try(fileset(local._project_path, "**/*.yaml"), []) :
|
||||
basename(trimsuffix(f, ".yaml")) => yamldecode(file("${local._project_path}/${f}"))
|
||||
},
|
||||
local._hierarchy_projects
|
||||
)
|
||||
_projects_full_path = {
|
||||
for f in try(fileset(local._project_path, "**/*.yaml"), []) :
|
||||
trimsuffix(f, ".yaml") => yamldecode(file("${local._project_path}/${f}"))
|
||||
}
|
||||
_projects_input = {
|
||||
# will raise error, if the same filename is used multiple times
|
||||
# and project name is not set via name in YAML
|
||||
for k, v in merge(local._hierarchy_projects_full_path, local._projects_full_path) :
|
||||
lookup(v, "name", basename(k)) => v
|
||||
}
|
||||
_project_budgets = flatten([
|
||||
for k, v in local._projects_input : [
|
||||
for b in try(v.billing_budgets, []) : {
|
||||
@@ -59,7 +62,8 @@ locals {
|
||||
buckets = flatten([
|
||||
for k, v in local.projects : [
|
||||
for name, opts in v.buckets : {
|
||||
project = k
|
||||
project_key = k
|
||||
project_name = v.name
|
||||
name = name
|
||||
description = lookup(opts, "description", "Terraform-managed.")
|
||||
encryption_key = lookup(opts, "encryption_key", null)
|
||||
@@ -87,10 +91,10 @@ locals {
|
||||
]
|
||||
])
|
||||
service_accounts = flatten([
|
||||
for k, v in local.projects : [
|
||||
for name, opts in v.service_accounts : {
|
||||
project = k
|
||||
name = name
|
||||
for k, project in local.projects : [
|
||||
for name, opts in project.service_accounts : {
|
||||
project_key = k
|
||||
name = name
|
||||
display_name = coalesce(
|
||||
try(var.data_overrides.service_accounts.display_name, null),
|
||||
try(opts.display_name, null),
|
||||
|
||||
@@ -232,11 +232,11 @@ module "projects-iam" {
|
||||
module "buckets" {
|
||||
source = "../gcs"
|
||||
for_each = {
|
||||
for k in local.buckets : "${k.project}/${k.name}" => k
|
||||
for k in local.buckets : "${k.project_key}/${k.name}" => k
|
||||
}
|
||||
project_id = module.projects[each.value.project].project_id
|
||||
project_id = module.projects[each.value.project_key].project_id
|
||||
prefix = each.value.prefix
|
||||
name = "${each.value.project}-${each.value.name}"
|
||||
name = "${each.value.project_name}-${each.value.name}"
|
||||
encryption_key = each.value.encryption_key
|
||||
iam = {
|
||||
for k, v in each.value.iam : k => [
|
||||
@@ -320,9 +320,9 @@ module "buckets" {
|
||||
module "service-accounts" {
|
||||
source = "../iam-service-account"
|
||||
for_each = {
|
||||
for k in local.service_accounts : "${k.project}/${k.name}" => k
|
||||
for k in local.service_accounts : "${k.project_key}/${k.name}" => k
|
||||
}
|
||||
project_id = module.projects[each.value.project].project_id
|
||||
project_id = module.projects[each.value.project_key].project_id
|
||||
name = each.value.name
|
||||
display_name = each.value.display_name
|
||||
iam = {
|
||||
@@ -349,7 +349,7 @@ module "service-accounts" {
|
||||
lookup(var.factories_config.context.vpc_host_projects, k, k) => v
|
||||
},
|
||||
each.value.iam_self_roles == null ? {} : {
|
||||
(module.projects[each.value.project].project_id) = each.value.iam_self_roles
|
||||
(module.projects[each.value.project_key].project_id) = each.value.iam_self_roles
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ variable "data_merges" {
|
||||
variable "data_overrides" {
|
||||
description = "Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`."
|
||||
type = object({
|
||||
# data overrides default to null to mark that they should not override
|
||||
billing_account = optional(string)
|
||||
contacts = optional(map(list(string)))
|
||||
factories_config = optional(object({
|
||||
@@ -111,7 +112,7 @@ variable "data_overrides" {
|
||||
ADMIN_READ = optional(object({ exempted_members = optional(list(string)) })),
|
||||
DATA_READ = optional(object({ exempted_members = optional(list(string)) })),
|
||||
DATA_WRITE = optional(object({ exempted_members = optional(list(string)) }))
|
||||
})), {})
|
||||
})))
|
||||
})
|
||||
nullable = false
|
||||
default = {}
|
||||
|
||||
@@ -120,9 +120,7 @@ The regular perimeters variable exposes all the complexity of the underlying res
|
||||
|
||||
If you need to refer to access levels created by the same module in regular service perimeters, you can either use the module's outputs in the provided variables, or the key used to identify the relevant access level. The example below shows how to do this in practice.
|
||||
|
||||
/*
|
||||
Resources for both perimeters have a `lifecycle` block that ignores changes to `spec` and `status` resources (projects), to allow using the additive resource `google_access_context_manager_service_perimeter_resource` at project creation. If this is not needed, the `lifecycle` blocks can be safely commented in the code.
|
||||
*/
|
||||
If you are managing perimeter membership outside of this module via `google_access_context_manager_service_perimeter_resource`, for example at project creation in a project factory, you might want to uncomment the lifecycle blocks that are defined but currently unused in `service-perimeters-regular.tf` and `service-perimeters-bridge.tf`.
|
||||
|
||||
#### Bridge type
|
||||
|
||||
|
||||
Reference in New Issue
Block a user