From 3ba695b0edd64421436b4bf5dc3599f34b75be93 Mon Sep 17 00:00:00 2001 From: apichick Date: Wed, 18 Jun 2025 10:44:00 +0200 Subject: [PATCH 01/18] DNS zones added you Apigee module (#3149) --- modules/apigee/README.md | 32 ++++++++++++++++++++++++++------ modules/apigee/main.tf | 12 ++++++++++++ modules/apigee/variables.tf | 12 ++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/modules/apigee/README.md b/modules/apigee/README.md index 45229c5bc..d4a1d30fb 100644 --- a/modules/apigee/README.md +++ b/modules/apigee/README.md @@ -17,6 +17,7 @@ This module simplifies the creation of a Apigee resources (organization, environ - [New instance (Non VPC Peering Provisioning Mode)](#new-instance-non-vpc-peering-provisioning-mode) - [New endpoint attachment](#new-endpoint-attachment) - [Apigee add-ons](#apigee-add-ons) +- [New DNS ZONE](#new-dns-zone) - [IAM](#iam) - [Recipes](#recipes) - [Variables](#variables) @@ -306,6 +307,24 @@ module "apigee" { # tftest modules=1 resources=1 ``` +## New DNS ZONE + +``` +module "apigee" { + source = "./fabric/modules/apigee" + project_id = "my-project" + dns_zones = { + test = { + domain = "mydomain.com" + description = "Zone for mydomain.com" + target_project_id = "my-other-project" + target_network_id = "projects/my-other-projects/global/networks/vpc" + } + } +} +# tftest modules=1 resources=1 +``` + ### IAM ```hcl @@ -364,13 +383,14 @@ module "apigee" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L132) | Project ID. | string | ✓ | | +| [project_id](variables.tf#L144) | Project ID. | string | ✓ | | | [addons_config](variables.tf#L17) | Addons configuration. | object({…}) | | null | -| [endpoint_attachments](variables.tf#L29) | Endpoint attachments. | map(object({…})) | | {} | -| [envgroups](variables.tf#L39) | Environment groups (NAME => [HOSTNAMES]). | map(list(string)) | | {} | -| [environments](variables.tf#L46) | Environments. | map(object({…})) | | {} | -| [instances](variables.tf#L74) | Instances ([REGION] => [INSTANCE]). | map(object({…})) | | {} | -| [organization](variables.tf#L100) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | +| [dns_zones](variables.tf#L29) | DNS zones. | map(object({…})) | | {} | +| [endpoint_attachments](variables.tf#L41) | Endpoint attachments. | map(object({…})) | | {} | +| [envgroups](variables.tf#L51) | Environment groups (NAME => [HOSTNAMES]). | map(list(string)) | | {} | +| [environments](variables.tf#L58) | Environments. | map(object({…})) | | {} | +| [instances](variables.tf#L86) | Instances ([REGION] => [INSTANCE]). | map(object({…})) | | {} | +| [organization](variables.tf#L112) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | ## Outputs diff --git a/modules/apigee/main.tf b/modules/apigee/main.tf index 0c8f6bcfa..130314797 100644 --- a/modules/apigee/main.tf +++ b/modules/apigee/main.tf @@ -173,3 +173,15 @@ resource "google_apigee_addons_config" "addons_config" { } } } + +resource "google_apigee_dns_zone" "dns_zones" { + for_each = var.dns_zones + org_id = google_apigee_organization.organization[0].id + dns_zone_id = each.key + domain = each.value.domain + description = each.value.description + peering_config { + target_project_id = each.value.target_project_id + target_network_id = each.value.target_network_id + } +} diff --git a/modules/apigee/variables.tf b/modules/apigee/variables.tf index b7f9aa6d8..b450b47f5 100644 --- a/modules/apigee/variables.tf +++ b/modules/apigee/variables.tf @@ -26,6 +26,18 @@ variable "addons_config" { default = null } +variable "dns_zones" { + description = "DNS zones." + type = map(object({ + domain = string + description = string + target_project_id = string + target_network_id = string + })) + default = {} + nullable = false +} + variable "endpoint_attachments" { description = "Endpoint attachments." type = map(object({ From c0bac5acc0a383b52383d24c83c9f08593ee7aef Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Mon, 16 Jun 2025 10:42:21 +0200 Subject: [PATCH 02/18] feat: adds support for direct cloud run iap --- modules/cloud-run-v2/README.md | 30 +++++++++++----------- modules/cloud-run-v2/service.tf | 38 ++++++++++++++++++++++++++++ modules/cloud-run-v2/variables.tf | 42 +++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 14 deletions(-) diff --git a/modules/cloud-run-v2/README.md b/modules/cloud-run-v2/README.md index b237175b3..91ef4887e 100644 --- a/modules/cloud-run-v2/README.md +++ b/modules/cloud-run-v2/README.md @@ -813,9 +813,9 @@ module "cloud_run" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L181) | Name used for Cloud Run service. | string | ✓ | | -| [project_id](variables.tf#L196) | Project id used for all resources. | string | ✓ | | -| [region](variables.tf#L201) | Region used for all resources. | string | ✓ | | +| [name](variables.tf#L223) | Name used for Cloud Run service. | string | ✓ | | +| [project_id](variables.tf#L238) | Project id used for all resources. | string | ✓ | | +| [region](variables.tf#L243) | Region used for all resources. | string | ✓ | | | [containers](variables.tf#L17) | Containers in name => attributes format. | map(object({…})) | | {} | | [create_job](variables.tf#L80) | Create Cloud Run Job instead of Service. | bool | | false | | [custom_audiences](variables.tf#L86) | Custom audiences for service. | list(string) | | null | @@ -823,17 +823,19 @@ module "cloud_run" { | [encryption_key](variables.tf#L98) | The full resource name of the Cloud KMS CryptoKey. | string | | null | | [eventarc_triggers](variables.tf#L104) | Event arc triggers for different sources. | object({…}) | | {} | | [iam](variables.tf#L122) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [ingress](variables.tf#L128) | Ingress settings. | string | | null | -| [invoker_iam_disabled](variables.tf#L145) | Disables IAM permission check for run.routes.invoke for callers of this service. | bool | | false | -| [labels](variables.tf#L151) | Resource labels. | map(string) | | {} | -| [launch_stage](variables.tf#L157) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | -| [managed_revision](variables.tf#L174) | Whether the Terraform module should control the deployment of revisions. | bool | | true | -| [prefix](variables.tf#L186) | Optional prefix used for resource names. | string | | null | -| [revision](variables.tf#L206) | Revision template configurations. | object({…}) | | {} | -| [service_account](variables.tf#L245) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L251) | Auto-create service account. | bool | | false | -| [tag_bindings](variables.tf#L257) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | -| [volumes](variables.tf#L264) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | +| [iap_enabled](variables.tf#L128) | <<-EOT Enables Identity-Aware Proxy (IAP) for this service. IAP can only be enabled for Cloud Run services (create_job = false). EOT | bool | | false | +| [iap_http_resource_accessors_config](variables.tf#L142) | <<-EOT IAP HTTP resource accessors configuration. When authoritative_mode is true, the google_iap_web_cloud_run_service_iam_binding resource is used which replaces any existing IAM policy attached to the IAP web service. When authoritative_mode is false (default), the google_iap_web_cloud_run_service_iam_member resource is used which adds the IAM policies to the service. EOT | object({…}) | | null | +| [ingress](variables.tf#L166) | Ingress settings. | string | | null | +| [invoker_iam_disabled](variables.tf#L183) | Disables IAM permission check for run.routes.invoke for callers of this service. | bool | | false | +| [labels](variables.tf#L189) | Resource labels. | map(string) | | {} | +| [launch_stage](variables.tf#L195) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | +| [managed_revision](variables.tf#L216) | Whether the Terraform module should control the deployment of revisions. | bool | | true | +| [prefix](variables.tf#L228) | Optional prefix used for resource names. | string | | null | +| [revision](variables.tf#L248) | Revision template configurations. | object({…}) | | {} | +| [service_account](variables.tf#L287) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L293) | Auto-create service account. | bool | | false | +| [tag_bindings](variables.tf#L299) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | +| [volumes](variables.tf#L306) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | | [vpc_connector_create](variables-vpcconnector.tf#L17) | Populate this to create a Serverless VPC Access connector. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index a9b1e0fea..808b9ecd2 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -30,6 +30,7 @@ resource "google_cloud_run_v2_service" "service" { launch_stage = var.launch_stage custom_audiences = var.custom_audiences deletion_protection = var.deletion_protection + iap_enabled = var.iap_enabled template { labels = var.revision.labels @@ -274,6 +275,7 @@ resource "google_cloud_run_v2_service" "service_unmanaged" { launch_stage = var.launch_stage custom_audiences = var.custom_audiences deletion_protection = var.deletion_protection + iap_enabled = var.iap_enabled template { labels = var.revision.labels @@ -522,3 +524,39 @@ resource "google_cloud_run_v2_service_iam_binding" "binding" { ) ) } + +locals { + use_iap_member = ( + !var.create_job && + var.iap_http_resource_accessors_config != null && + !var.iap_http_resource_accessors_config.authoritative_mode + ) + + iap_member_list = local.use_iap_member ? toset(var.iap_http_resource_accessors_config.iam_emails) : [] + + use_iap_iam_binding = ( + !var.create_job && + var.iap_http_resource_accessors_config != null && + var.iap_http_resource_accessors_config.authoritative_mode + ) + iap_binding_dict = local.use_iap_iam_binding ? { "iap" = var.iap_http_resource_accessors_config.iam_emails } : {} + +} + +resource "google_iap_web_cloud_run_service_iam_member" "member" { + for_each = local.iap_member_list + project = local.service.project + location = local.service.location + cloud_run_service_name = local.service.name + role = "roles/iap.httpsResourceAccessor" + member = each.key +} + +resource "google_iap_web_cloud_run_service_iam_binding" "binding" { + for_each = local.iap_binding_dict + project = local.service.project + location = local.service.location + cloud_run_service_name = local.service.name + role = "roles/iap.httpsResourceAccessor" + members = each.value +} \ No newline at end of file diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index 61a9ab95c..15649e55f 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -125,6 +125,44 @@ variable "iam" { default = {} } +variable "iap_enabled" { + description = <<-EOT + Enables Identity-Aware Proxy (IAP) for this service. + IAP can only be enabled for Cloud Run services (create_job = false). + EOT + type = bool + default = false + + validation { + condition = !var.iap_enabled || (var.iap_enabled && !var.create_job) + error_message = "IAP can only be enabled for Cloud Run services (create_job = false), not for jobs." + } +} + +variable "iap_http_resource_accessors_config" { + description = <<-EOT + IAP HTTP resource accessors configuration. + When authoritative_mode is true, the google_iap_web_cloud_run_service_iam_binding resource is used + which replaces any existing IAM policy attached to the IAP web service. + When authoritative_mode is false (default), the google_iap_web_cloud_run_service_iam_member resource is used + which adds the IAM policies to the service. + EOT + type = object({ + iam_emails = list(string) + authoritative_mode = optional(bool, false) + }) + default = null + + validation { + condition = var.iap_http_resource_accessors_config == null || var.iap_enabled + error_message = "iap_http_resource_accessors_config can only be set when iap_enabled = true." + } + validation { + condition = var.iap_http_resource_accessors_config == null || (var.iap_http_resource_accessors_config != null && length(var.iap_http_resource_accessors_config.iam_emails) > 0) + error_message = "When iap_http_resource_accessors_config is set, iam_emails must not be empty." + } +} + variable "ingress" { description = "Ingress settings." type = string @@ -169,6 +207,10 @@ variable "launch_stage" { BETA, GA, DEPRECATED. EOF } + validation { + condition = !var.iap_enabled || (var.iap_enabled && var.launch_stage == "BETA") + error_message = "When IAP is enabled (iap_enabled = true), launch_stage must be set to 'BETA'." + } } variable "managed_revision" { From 998919d08155784d58e98915a9bfdf6440b74b2d Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Mon, 16 Jun 2025 10:56:05 +0200 Subject: [PATCH 03/18] fixes error --- modules/cloud-run-v2/service.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index 808b9ecd2..553ec5a49 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -532,7 +532,7 @@ locals { !var.iap_http_resource_accessors_config.authoritative_mode ) - iap_member_list = local.use_iap_member ? toset(var.iap_http_resource_accessors_config.iam_emails) : [] + iap_member_list = local.use_iap_member ? toset(var.iap_http_resource_accessors_config.iam_emails) : () use_iap_iam_binding = ( !var.create_job && From 1aec8bb43e4536d2f567ac4df15c3377ef121a95 Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Mon, 16 Jun 2025 10:58:08 +0200 Subject: [PATCH 04/18] fixes error --- modules/cloud-run-v2/service.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index 553ec5a49..6d7823412 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -532,7 +532,7 @@ locals { !var.iap_http_resource_accessors_config.authoritative_mode ) - iap_member_list = local.use_iap_member ? toset(var.iap_http_resource_accessors_config.iam_emails) : () + iap_member_list = local.use_iap_member ? toset(var.iap_http_resource_accessors_config.iam_emails) : toset([]) use_iap_iam_binding = ( !var.create_job && From 1cc50e30ae73be1e2bb3ace0bd7e590998dc5ad1 Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Mon, 16 Jun 2025 14:37:09 +0200 Subject: [PATCH 05/18] simplifies iap configuration --- modules/cloud-run-v2/main.tf | 2 ++ modules/cloud-run-v2/service.tf | 19 +++------- modules/cloud-run-v2/variables.tf | 60 +++++++++++++++---------------- 3 files changed, 36 insertions(+), 45 deletions(-) diff --git a/modules/cloud-run-v2/main.tf b/modules/cloud-run-v2/main.tf index 4c0a0243b..84212ba07 100644 --- a/modules/cloud-run-v2/main.tf +++ b/modules/cloud-run-v2/main.tf @@ -54,6 +54,8 @@ locals { var.eventarc_triggers.service_account_email, null ) + + iap_enabled = var.iap_config != null } resource "google_cloud_run_v2_service_iam_member" "default" { diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index 6d7823412..7451412c1 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -30,7 +30,7 @@ resource "google_cloud_run_v2_service" "service" { launch_stage = var.launch_stage custom_audiences = var.custom_audiences deletion_protection = var.deletion_protection - iap_enabled = var.iap_enabled + iap_enabled = local.iap_enabled template { labels = var.revision.labels @@ -275,7 +275,7 @@ resource "google_cloud_run_v2_service" "service_unmanaged" { launch_stage = var.launch_stage custom_audiences = var.custom_audiences deletion_protection = var.deletion_protection - iap_enabled = var.iap_enabled + iap_enabled = local.iap_enabled template { labels = var.revision.labels @@ -526,20 +526,11 @@ resource "google_cloud_run_v2_service_iam_binding" "binding" { } locals { - use_iap_member = ( - !var.create_job && - var.iap_http_resource_accessors_config != null && - !var.iap_http_resource_accessors_config.authoritative_mode - ) - iap_member_list = local.use_iap_member ? toset(var.iap_http_resource_accessors_config.iam_emails) : toset([]) + iap_member_list = toset(coalesce(var.iap_config.iam_additive, [])) - use_iap_iam_binding = ( - !var.create_job && - var.iap_http_resource_accessors_config != null && - var.iap_http_resource_accessors_config.authoritative_mode - ) - iap_binding_dict = local.use_iap_iam_binding ? { "iap" = var.iap_http_resource_accessors_config.iam_emails } : {} + use_iap_iam_binding = var.iap_config != null && var.iap_config.iam != null + iap_binding_dict = local.use_iap_iam_binding ? { "iap" = var.iap_config.iam } : {} } diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index 15649e55f..a23d81eb4 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -125,41 +125,43 @@ variable "iam" { default = {} } -variable "iap_enabled" { +variable "iap_config" { description = <<-EOT - Enables Identity-Aware Proxy (IAP) for this service. - IAP can only be enabled for Cloud Run services (create_job = false). - EOT - type = bool - default = false - - validation { - condition = !var.iap_enabled || (var.iap_enabled && !var.create_job) - error_message = "IAP can only be enabled for Cloud Run services (create_job = false), not for jobs." - } -} - -variable "iap_http_resource_accessors_config" { - description = <<-EOT - IAP HTTP resource accessors configuration. - When authoritative_mode is true, the google_iap_web_cloud_run_service_iam_binding resource is used - which replaces any existing IAM policy attached to the IAP web service. - When authoritative_mode is false (default), the google_iap_web_cloud_run_service_iam_member resource is used - which adds the IAM policies to the service. + If present, it turns on Identity-Aware Proxy (IAP) for this service. + iam (resource google_iap_web_cloud_run_service_iam_binding) - list of iam emails (e.g. "group:abc@domain.com") to be granted with iap.httpsResourceAccessor role. + iam_additive (resource google_iap_web_cloud_run_service_iam_member ) - list of iam emails (e.g. "group:abc@domain.com") to be granted with iap.httpsResourceAccessor. + iam and iam_additive are mutually exclusive. EOT type = object({ - iam_emails = list(string) - authoritative_mode = optional(bool, false) + iam = optional(list(string)) + iam_additive = optional(list(string)) }) default = null - + validation { - condition = var.iap_http_resource_accessors_config == null || var.iap_enabled - error_message = "iap_http_resource_accessors_config can only be set when iap_enabled = true." + condition = var.iap_config == null || ( + (var.iap_config.iam != null && var.iap_config.iam_additive == null) || + (var.iap_config.iam == null && var.iap_config.iam_additive != null) + ) + error_message = "When iap_config is provided, exactly one of 'iam' or 'iam_additive' must be specified." } + validation { - condition = var.iap_http_resource_accessors_config == null || (var.iap_http_resource_accessors_config != null && length(var.iap_http_resource_accessors_config.iam_emails) > 0) - error_message = "When iap_http_resource_accessors_config is set, iam_emails must not be empty." + condition = var.iap_config == null || ( + (var.iap_config.iam != null ? length(var.iap_config.iam) > 0 : true) && + (var.iap_config.iam_additive != null ? length(var.iap_config.iam_additive) > 0 : true) + ) + error_message = "When 'iam' or 'iam_additive' lists are provided in iap_config, they must not be empty." + } + + validation { + condition = var.iap_config == null || !var.create_job + error_message = "IAP is only supported for Cloud Run services, not Cloud Run jobs. Set create_job to false when using iap_config." + } + + validation { + condition = var.iap_config == null || var.launch_stage != "GA" + error_message = "iap is currently not supported in GA. Set launch_stage to 'BETA' or lower." } } @@ -207,10 +209,6 @@ variable "launch_stage" { BETA, GA, DEPRECATED. EOF } - validation { - condition = !var.iap_enabled || (var.iap_enabled && var.launch_stage == "BETA") - error_message = "When IAP is enabled (iap_enabled = true), launch_stage must be set to 'BETA'." - } } variable "managed_revision" { From fd8f659a3ab3f1bd11e9b122bb47af0fbcfa33a5 Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Mon, 16 Jun 2025 14:58:31 +0200 Subject: [PATCH 06/18] docs && fmt --- modules/cloud-run-v2/README.md | 31 +++++++++++++++---------------- modules/cloud-run-v2/service.tf | 4 ++-- modules/cloud-run-v2/variables.tf | 8 ++++---- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/modules/cloud-run-v2/README.md b/modules/cloud-run-v2/README.md index 91ef4887e..ac2408b23 100644 --- a/modules/cloud-run-v2/README.md +++ b/modules/cloud-run-v2/README.md @@ -813,9 +813,9 @@ module "cloud_run" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L223) | Name used for Cloud Run service. | string | ✓ | | -| [project_id](variables.tf#L238) | Project id used for all resources. | string | ✓ | | -| [region](variables.tf#L243) | Region used for all resources. | string | ✓ | | +| [name](variables.tf#L221) | Name used for Cloud Run service. | string | ✓ | | +| [project_id](variables.tf#L236) | Project id used for all resources. | string | ✓ | | +| [region](variables.tf#L241) | Region used for all resources. | string | ✓ | | | [containers](variables.tf#L17) | Containers in name => attributes format. | map(object({…})) | | {} | | [create_job](variables.tf#L80) | Create Cloud Run Job instead of Service. | bool | | false | | [custom_audiences](variables.tf#L86) | Custom audiences for service. | list(string) | | null | @@ -823,19 +823,18 @@ module "cloud_run" { | [encryption_key](variables.tf#L98) | The full resource name of the Cloud KMS CryptoKey. | string | | null | | [eventarc_triggers](variables.tf#L104) | Event arc triggers for different sources. | object({…}) | | {} | | [iam](variables.tf#L122) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iap_enabled](variables.tf#L128) | <<-EOT Enables Identity-Aware Proxy (IAP) for this service. IAP can only be enabled for Cloud Run services (create_job = false). EOT | bool | | false | -| [iap_http_resource_accessors_config](variables.tf#L142) | <<-EOT IAP HTTP resource accessors configuration. When authoritative_mode is true, the google_iap_web_cloud_run_service_iam_binding resource is used which replaces any existing IAM policy attached to the IAP web service. When authoritative_mode is false (default), the google_iap_web_cloud_run_service_iam_member resource is used which adds the IAM policies to the service. EOT | object({…}) | | null | -| [ingress](variables.tf#L166) | Ingress settings. | string | | null | -| [invoker_iam_disabled](variables.tf#L183) | Disables IAM permission check for run.routes.invoke for callers of this service. | bool | | false | -| [labels](variables.tf#L189) | Resource labels. | map(string) | | {} | -| [launch_stage](variables.tf#L195) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | -| [managed_revision](variables.tf#L216) | Whether the Terraform module should control the deployment of revisions. | bool | | true | -| [prefix](variables.tf#L228) | Optional prefix used for resource names. | string | | null | -| [revision](variables.tf#L248) | Revision template configurations. | object({…}) | | {} | -| [service_account](variables.tf#L287) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L293) | Auto-create service account. | bool | | false | -| [tag_bindings](variables.tf#L299) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | -| [volumes](variables.tf#L306) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | +| [iap_config](variables.tf#L128) | <<-EOT If present, it turns on Identity-Aware Proxy (IAP) for this service. iam (resource google_iap_web_cloud_run_service_iam_binding) - list of iam emails (e.g. "group:abc@domain.com") to be granted with iap.httpsResourceAccessor role. iam_additive (resource google_iap_web_cloud_run_service_iam_member ) - list of iam emails (e.g. "group:abc@domain.com") to be granted with iap.httpsResourceAccessor. iam and iam_additive are mutually exclusive. EOT | object({…}) | | null | +| [ingress](variables.tf#L168) | Ingress settings. | string | | null | +| [invoker_iam_disabled](variables.tf#L185) | Disables IAM permission check for run.routes.invoke for callers of this service. | bool | | false | +| [labels](variables.tf#L191) | Resource labels. | map(string) | | {} | +| [launch_stage](variables.tf#L197) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | +| [managed_revision](variables.tf#L214) | Whether the Terraform module should control the deployment of revisions. | bool | | true | +| [prefix](variables.tf#L226) | Optional prefix used for resource names. | string | | null | +| [revision](variables.tf#L246) | Revision template configurations. | object({…}) | | {} | +| [service_account](variables.tf#L285) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L291) | Auto-create service account. | bool | | false | +| [tag_bindings](variables.tf#L297) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | +| [volumes](variables.tf#L304) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | | [vpc_connector_create](variables-vpcconnector.tf#L17) | Populate this to create a Serverless VPC Access connector. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index 7451412c1..b93f2efe3 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -529,8 +529,8 @@ locals { iap_member_list = toset(coalesce(var.iap_config.iam_additive, [])) - use_iap_iam_binding = var.iap_config != null && var.iap_config.iam != null - iap_binding_dict = local.use_iap_iam_binding ? { "iap" = var.iap_config.iam } : {} + use_iap_iam_binding = var.iap_config != null && var.iap_config.iam != null + iap_binding_dict = local.use_iap_iam_binding ? { "iap" = var.iap_config.iam } : {} } diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index a23d81eb4..8f507c54c 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -137,7 +137,7 @@ variable "iap_config" { iam_additive = optional(list(string)) }) default = null - + validation { condition = var.iap_config == null || ( (var.iap_config.iam != null && var.iap_config.iam_additive == null) || @@ -145,7 +145,7 @@ variable "iap_config" { ) error_message = "When iap_config is provided, exactly one of 'iam' or 'iam_additive' must be specified." } - + validation { condition = var.iap_config == null || ( (var.iap_config.iam != null ? length(var.iap_config.iam) > 0 : true) && @@ -153,12 +153,12 @@ variable "iap_config" { ) error_message = "When 'iam' or 'iam_additive' lists are provided in iap_config, they must not be empty." } - + validation { condition = var.iap_config == null || !var.create_job error_message = "IAP is only supported for Cloud Run services, not Cloud Run jobs. Set create_job to false when using iap_config." } - + validation { condition = var.iap_config == null || var.launch_stage != "GA" error_message = "iap is currently not supported in GA. Set launch_stage to 'BETA' or lower." From 9dcd0c86abfe08a634918bf40e136b4b5f4093a8 Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Mon, 16 Jun 2025 16:49:47 +0200 Subject: [PATCH 07/18] ensures tf < 1.2 compatibility --- modules/cloud-run-v2/service.tf | 4 ++-- modules/cloud-run-v2/variables.tf | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index b93f2efe3..ac175f7a1 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -527,9 +527,9 @@ resource "google_cloud_run_v2_service_iam_binding" "binding" { locals { - iap_member_list = toset(coalesce(var.iap_config.iam_additive, [])) + iap_member_list = toset(try(coalesce(var.iap_config.iam_additive, []), [])) - use_iap_iam_binding = var.iap_config != null && var.iap_config.iam != null + use_iap_iam_binding = var.iap_config != null && try(var.iap_config.iam,null) != null iap_binding_dict = local.use_iap_iam_binding ? { "iap" = var.iap_config.iam } : {} } diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index 8f507c54c..dfbe9531d 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -140,16 +140,16 @@ variable "iap_config" { validation { condition = var.iap_config == null || ( - (var.iap_config.iam != null && var.iap_config.iam_additive == null) || - (var.iap_config.iam == null && var.iap_config.iam_additive != null) + (try(var.iap_config.iam,null) != null && try(var.iap_config.iam_additive,null) == null) || + (try(var.iap_config.iam,null) == null && try(var.iap_config.iam_additive,null) != null) ) error_message = "When iap_config is provided, exactly one of 'iam' or 'iam_additive' must be specified." } validation { condition = var.iap_config == null || ( - (var.iap_config.iam != null ? length(var.iap_config.iam) > 0 : true) && - (var.iap_config.iam_additive != null ? length(var.iap_config.iam_additive) > 0 : true) + (try(var.iap_config.iam,null) != null ? length(var.iap_config.iam) > 0 : true) && + (try(var.iap_config.iam_additive,null) != null ? length(var.iap_config.iam_additive) > 0 : true) ) error_message = "When 'iam' or 'iam_additive' lists are provided in iap_config, they must not be empty." } From d2e9c238f63833a003c8ee051d5ccb4e3105d653 Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Mon, 16 Jun 2025 16:50:04 +0200 Subject: [PATCH 08/18] ensures tf < 1.2 compatibility --- modules/cloud-run-v2/service.tf | 2 +- modules/cloud-run-v2/variables.tf | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index ac175f7a1..691d9c3fc 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -529,7 +529,7 @@ locals { iap_member_list = toset(try(coalesce(var.iap_config.iam_additive, []), [])) - use_iap_iam_binding = var.iap_config != null && try(var.iap_config.iam,null) != null + use_iap_iam_binding = var.iap_config != null && try(var.iap_config.iam, null) != null iap_binding_dict = local.use_iap_iam_binding ? { "iap" = var.iap_config.iam } : {} } diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index dfbe9531d..d6528192b 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -140,16 +140,16 @@ variable "iap_config" { validation { condition = var.iap_config == null || ( - (try(var.iap_config.iam,null) != null && try(var.iap_config.iam_additive,null) == null) || - (try(var.iap_config.iam,null) == null && try(var.iap_config.iam_additive,null) != null) + (try(var.iap_config.iam, null) != null && try(var.iap_config.iam_additive, null) == null) || + (try(var.iap_config.iam, null) == null && try(var.iap_config.iam_additive, null) != null) ) error_message = "When iap_config is provided, exactly one of 'iam' or 'iam_additive' must be specified." } validation { condition = var.iap_config == null || ( - (try(var.iap_config.iam,null) != null ? length(var.iap_config.iam) > 0 : true) && - (try(var.iap_config.iam_additive,null) != null ? length(var.iap_config.iam_additive) > 0 : true) + (try(var.iap_config.iam, null) != null ? length(var.iap_config.iam) > 0 : true) && + (try(var.iap_config.iam_additive, null) != null ? length(var.iap_config.iam_additive) > 0 : true) ) error_message = "When 'iam' or 'iam_additive' lists are provided in iap_config, they must not be empty." } From 461c0b8e4446367e5fab1b58df507543e90a9795 Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Mon, 16 Jun 2025 19:06:01 +0200 Subject: [PATCH 09/18] adds iap configuration test --- modules/cloud-run-v2/README.md | 60 +++++++++++++++++++++++-------- modules/cloud-run-v2/variables.tf | 7 +--- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/modules/cloud-run-v2/README.md b/modules/cloud-run-v2/README.md index ac2408b23..2ef6008eb 100644 --- a/modules/cloud-run-v2/README.md +++ b/modules/cloud-run-v2/README.md @@ -19,6 +19,7 @@ Cloud Run Services and Jobs, with support for IAM roles and Eventarc trigger cre - [Cloud Run Service Account](#cloud-run-service-account) - [Creating Cloud Run Jobs](#creating-cloud-run-jobs) - [Tag bindings](#tag-bindings) +- [Tag bindings](#tag-bindings) - [Variables](#variables) - [Outputs](#outputs) - [Fixtures](#fixtures) @@ -808,14 +809,43 @@ module "cloud_run" { } # tftest modules=2 resources=7 ``` + +## IAP Configuration + +IAP is only supported for service. Refer to the [Configure IAP directly on cloud run](https://cloud.google.com/run/docs/securing/identity-aware-proxy-cloud-run) documentation for details on usage. + +```hcl +module "cloud_run" { + source = "./fabric/modules/cloud-run-v2" + project_id = var.project_id + name = "hello" + region = var.region + launch_stage = "BETA" + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + env = { + VAR1 = "VALUE1" + VAR2 = "VALUE2" + } + } + } + + iap_config = { + iam = ["group:abc@domain.com"] + } + +} +# tftest modules=1 resources=2 +``` ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L221) | Name used for Cloud Run service. | string | ✓ | | -| [project_id](variables.tf#L236) | Project id used for all resources. | string | ✓ | | -| [region](variables.tf#L241) | Region used for all resources. | string | ✓ | | +| [name](variables.tf#L216) | Name used for Cloud Run service. | string | ✓ | | +| [project_id](variables.tf#L231) | Project id used for all resources. | string | ✓ | | +| [region](variables.tf#L236) | Region used for all resources. | string | ✓ | | | [containers](variables.tf#L17) | Containers in name => attributes format. | map(object({…})) | | {} | | [create_job](variables.tf#L80) | Create Cloud Run Job instead of Service. | bool | | false | | [custom_audiences](variables.tf#L86) | Custom audiences for service. | list(string) | | null | @@ -823,18 +853,18 @@ module "cloud_run" { | [encryption_key](variables.tf#L98) | The full resource name of the Cloud KMS CryptoKey. | string | | null | | [eventarc_triggers](variables.tf#L104) | Event arc triggers for different sources. | object({…}) | | {} | | [iam](variables.tf#L122) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iap_config](variables.tf#L128) | <<-EOT If present, it turns on Identity-Aware Proxy (IAP) for this service. iam (resource google_iap_web_cloud_run_service_iam_binding) - list of iam emails (e.g. "group:abc@domain.com") to be granted with iap.httpsResourceAccessor role. iam_additive (resource google_iap_web_cloud_run_service_iam_member ) - list of iam emails (e.g. "group:abc@domain.com") to be granted with iap.httpsResourceAccessor. iam and iam_additive are mutually exclusive. EOT | object({…}) | | null | -| [ingress](variables.tf#L168) | Ingress settings. | string | | null | -| [invoker_iam_disabled](variables.tf#L185) | Disables IAM permission check for run.routes.invoke for callers of this service. | bool | | false | -| [labels](variables.tf#L191) | Resource labels. | map(string) | | {} | -| [launch_stage](variables.tf#L197) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | -| [managed_revision](variables.tf#L214) | Whether the Terraform module should control the deployment of revisions. | bool | | true | -| [prefix](variables.tf#L226) | Optional prefix used for resource names. | string | | null | -| [revision](variables.tf#L246) | Revision template configurations. | object({…}) | | {} | -| [service_account](variables.tf#L285) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L291) | Auto-create service account. | bool | | false | -| [tag_bindings](variables.tf#L297) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | -| [volumes](variables.tf#L304) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | +| [iap_config](variables.tf#L128) | If present, turns on Identity-Aware Proxy (IAP) for the Cloud Run service. | object({…}) | | null | +| [ingress](variables.tf#L163) | Ingress settings. | string | | null | +| [invoker_iam_disabled](variables.tf#L180) | Disables IAM permission check for run.routes.invoke for callers of this service. | bool | | false | +| [labels](variables.tf#L186) | Resource labels. | map(string) | | {} | +| [launch_stage](variables.tf#L192) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | +| [managed_revision](variables.tf#L209) | Whether the Terraform module should control the deployment of revisions. | bool | | true | +| [prefix](variables.tf#L221) | Optional prefix used for resource names. | string | | null | +| [revision](variables.tf#L241) | Revision template configurations. | object({…}) | | {} | +| [service_account](variables.tf#L280) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L286) | Auto-create service account. | bool | | false | +| [tag_bindings](variables.tf#L292) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | +| [volumes](variables.tf#L299) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | | [vpc_connector_create](variables-vpcconnector.tf#L17) | Populate this to create a Serverless VPC Access connector. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index d6528192b..b289f4062 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -126,12 +126,7 @@ variable "iam" { } variable "iap_config" { - description = <<-EOT - If present, it turns on Identity-Aware Proxy (IAP) for this service. - iam (resource google_iap_web_cloud_run_service_iam_binding) - list of iam emails (e.g. "group:abc@domain.com") to be granted with iap.httpsResourceAccessor role. - iam_additive (resource google_iap_web_cloud_run_service_iam_member ) - list of iam emails (e.g. "group:abc@domain.com") to be granted with iap.httpsResourceAccessor. - iam and iam_additive are mutually exclusive. - EOT + description = "If present, turns on Identity-Aware Proxy (IAP) for the Cloud Run service." type = object({ iam = optional(list(string)) iam_additive = optional(list(string)) From 0ead6ba89423bd99c8898f42e2fc86121fb80949 Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Tue, 17 Jun 2025 09:09:16 +0200 Subject: [PATCH 10/18] format readme example --- modules/cloud-run-v2/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/cloud-run-v2/README.md b/modules/cloud-run-v2/README.md index 2ef6008eb..145964139 100644 --- a/modules/cloud-run-v2/README.md +++ b/modules/cloud-run-v2/README.md @@ -816,11 +816,10 @@ IAP is only supported for service. Refer to the [Configure IAP directly on clou ```hcl module "cloud_run" { - source = "./fabric/modules/cloud-run-v2" - project_id = var.project_id - name = "hello" - region = var.region - launch_stage = "BETA" + source = "./fabric/modules/cloud-run-v2" + project_id = var.project_id + name = "hello" + region = var.region containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" From a83c8a69deddbd1e45c25d6f85563d31f23f622d Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Tue, 17 Jun 2025 11:00:45 +0200 Subject: [PATCH 11/18] fixes test --- modules/cloud-run-v2/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/cloud-run-v2/README.md b/modules/cloud-run-v2/README.md index 145964139..4821a73df 100644 --- a/modules/cloud-run-v2/README.md +++ b/modules/cloud-run-v2/README.md @@ -19,7 +19,7 @@ Cloud Run Services and Jobs, with support for IAM roles and Eventarc trigger cre - [Cloud Run Service Account](#cloud-run-service-account) - [Creating Cloud Run Jobs](#creating-cloud-run-jobs) - [Tag bindings](#tag-bindings) -- [Tag bindings](#tag-bindings) +- [IAP Configuration](#iap-configuration) - [Variables](#variables) - [Outputs](#outputs) - [Fixtures](#fixtures) @@ -816,10 +816,10 @@ IAP is only supported for service. Refer to the [Configure IAP directly on clou ```hcl module "cloud_run" { - source = "./fabric/modules/cloud-run-v2" - project_id = var.project_id - name = "hello" - region = var.region + source = "./fabric/modules/cloud-run-v2" + project_id = var.project_id + name = "hello" + region = var.region containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" From 1d8e48f9e4027fd11d85301b241f99c59673ba9e Mon Sep 17 00:00:00 2001 From: Maciej Sikora <96724614+msikora-rtb@users.noreply.github.com> Date: Tue, 17 Jun 2025 11:13:41 +0200 Subject: [PATCH 12/18] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Wiktor Niesiobędzki --- modules/cloud-run-v2/service.tf | 10 +++++----- modules/cloud-run-v2/variables.tf | 8 +++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index 691d9c3fc..0d4d05340 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -527,15 +527,15 @@ resource "google_cloud_run_v2_service_iam_binding" "binding" { locals { - iap_member_list = toset(try(coalesce(var.iap_config.iam_additive, []), [])) + iap_iam_additive = local.iap_enabled ? var.iap_config.iam_additive : [] use_iap_iam_binding = var.iap_config != null && try(var.iap_config.iam, null) != null - iap_binding_dict = local.use_iap_iam_binding ? { "iap" = var.iap_config.iam } : {} + iap_iam = local.iap_enabled ? var.iap_config.iam : [] } resource "google_iap_web_cloud_run_service_iam_member" "member" { - for_each = local.iap_member_list + for_each = local.iap_iam_additive project = local.service.project location = local.service.location cloud_run_service_name = local.service.name @@ -544,10 +544,10 @@ resource "google_iap_web_cloud_run_service_iam_member" "member" { } resource "google_iap_web_cloud_run_service_iam_binding" "binding" { - for_each = local.iap_binding_dict + for_each = length(local.iap_iam) == 0 ? {} : {1 = 1} project = local.service.project location = local.service.location cloud_run_service_name = local.service.name role = "roles/iap.httpsResourceAccessor" - members = each.value + member = local.iap_iam } \ No newline at end of file diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index b289f4062..d279e0d4f 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -128,8 +128,8 @@ variable "iam" { variable "iap_config" { description = "If present, turns on Identity-Aware Proxy (IAP) for the Cloud Run service." type = object({ - iam = optional(list(string)) - iam_additive = optional(list(string)) + iam = optional(list(string), []) + iam_additive = optional(list(string), []) }) default = null @@ -142,9 +142,7 @@ variable "iap_config" { } validation { - condition = var.iap_config == null || ( - (try(var.iap_config.iam, null) != null ? length(var.iap_config.iam) > 0 : true) && - (try(var.iap_config.iam_additive, null) != null ? length(var.iap_config.iam_additive) > 0 : true) + condition = !(length(try(var.iap_config.iam, [])) > 0 && length(try(var.iap_config.iam_additive, [])) > 0) ) error_message = "When 'iam' or 'iam_additive' lists are provided in iap_config, they must not be empty." } From 1dd4873a02de7a63f6147a53d2bb98807b141adc Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Tue, 17 Jun 2025 11:20:13 +0200 Subject: [PATCH 13/18] fixes validation --- modules/cloud-run-v2/README.md | 30 +++++++++++++++--------------- modules/cloud-run-v2/service.tf | 8 ++++---- modules/cloud-run-v2/variables.tf | 10 +--------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/modules/cloud-run-v2/README.md b/modules/cloud-run-v2/README.md index 4821a73df..5c577f528 100644 --- a/modules/cloud-run-v2/README.md +++ b/modules/cloud-run-v2/README.md @@ -842,9 +842,9 @@ module "cloud_run" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L216) | Name used for Cloud Run service. | string | ✓ | | -| [project_id](variables.tf#L231) | Project id used for all resources. | string | ✓ | | -| [region](variables.tf#L236) | Region used for all resources. | string | ✓ | | +| [name](variables.tf#L206) | Name used for Cloud Run service. | string | ✓ | | +| [project_id](variables.tf#L221) | Project id used for all resources. | string | ✓ | | +| [region](variables.tf#L226) | Region used for all resources. | string | ✓ | | | [containers](variables.tf#L17) | Containers in name => attributes format. | map(object({…})) | | {} | | [create_job](variables.tf#L80) | Create Cloud Run Job instead of Service. | bool | | false | | [custom_audiences](variables.tf#L86) | Custom audiences for service. | list(string) | | null | @@ -852,18 +852,18 @@ module "cloud_run" { | [encryption_key](variables.tf#L98) | The full resource name of the Cloud KMS CryptoKey. | string | | null | | [eventarc_triggers](variables.tf#L104) | Event arc triggers for different sources. | object({…}) | | {} | | [iam](variables.tf#L122) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iap_config](variables.tf#L128) | If present, turns on Identity-Aware Proxy (IAP) for the Cloud Run service. | object({…}) | | null | -| [ingress](variables.tf#L163) | Ingress settings. | string | | null | -| [invoker_iam_disabled](variables.tf#L180) | Disables IAM permission check for run.routes.invoke for callers of this service. | bool | | false | -| [labels](variables.tf#L186) | Resource labels. | map(string) | | {} | -| [launch_stage](variables.tf#L192) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | -| [managed_revision](variables.tf#L209) | Whether the Terraform module should control the deployment of revisions. | bool | | true | -| [prefix](variables.tf#L221) | Optional prefix used for resource names. | string | | null | -| [revision](variables.tf#L241) | Revision template configurations. | object({…}) | | {} | -| [service_account](variables.tf#L280) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L286) | Auto-create service account. | bool | | false | -| [tag_bindings](variables.tf#L292) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | -| [volumes](variables.tf#L299) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | +| [iap_config](variables.tf#L128) | If present, turns on Identity-Aware Proxy (IAP) for the Cloud Run service. | object({…}) | | null | +| [ingress](variables.tf#L153) | Ingress settings. | string | | null | +| [invoker_iam_disabled](variables.tf#L170) | Disables IAM permission check for run.routes.invoke for callers of this service. | bool | | false | +| [labels](variables.tf#L176) | Resource labels. | map(string) | | {} | +| [launch_stage](variables.tf#L182) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | +| [managed_revision](variables.tf#L199) | Whether the Terraform module should control the deployment of revisions. | bool | | true | +| [prefix](variables.tf#L211) | Optional prefix used for resource names. | string | | null | +| [revision](variables.tf#L231) | Revision template configurations. | object({…}) | | {} | +| [service_account](variables.tf#L270) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L276) | Auto-create service account. | bool | | false | +| [tag_bindings](variables.tf#L282) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | +| [volumes](variables.tf#L289) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | | [vpc_connector_create](variables-vpcconnector.tf#L17) | Populate this to create a Serverless VPC Access connector. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index 0d4d05340..1b8418716 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -530,12 +530,12 @@ locals { iap_iam_additive = local.iap_enabled ? var.iap_config.iam_additive : [] use_iap_iam_binding = var.iap_config != null && try(var.iap_config.iam, null) != null - iap_iam = local.iap_enabled ? var.iap_config.iam : [] + iap_iam = local.iap_enabled ? var.iap_config.iam : [] } resource "google_iap_web_cloud_run_service_iam_member" "member" { - for_each = local.iap_iam_additive + for_each = toset(local.iap_iam_additive) project = local.service.project location = local.service.location cloud_run_service_name = local.service.name @@ -544,10 +544,10 @@ resource "google_iap_web_cloud_run_service_iam_member" "member" { } resource "google_iap_web_cloud_run_service_iam_binding" "binding" { - for_each = length(local.iap_iam) == 0 ? {} : {1 = 1} + for_each = length(local.iap_iam) == 0 ? {} : { 1 = 1 } project = local.service.project location = local.service.location cloud_run_service_name = local.service.name role = "roles/iap.httpsResourceAccessor" - member = local.iap_iam + members = local.iap_iam } \ No newline at end of file diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index d279e0d4f..8eeb32baa 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -133,17 +133,9 @@ variable "iap_config" { }) default = null - validation { - condition = var.iap_config == null || ( - (try(var.iap_config.iam, null) != null && try(var.iap_config.iam_additive, null) == null) || - (try(var.iap_config.iam, null) == null && try(var.iap_config.iam_additive, null) != null) - ) - error_message = "When iap_config is provided, exactly one of 'iam' or 'iam_additive' must be specified." - } - validation { condition = !(length(try(var.iap_config.iam, [])) > 0 && length(try(var.iap_config.iam_additive, [])) > 0) - ) + error_message = "When 'iam' or 'iam_additive' lists are provided in iap_config, they must not be empty." } From f2401ed4c80604cf02b96cde2b5b939ee0716835 Mon Sep 17 00:00:00 2001 From: Maciej Sikora Date: Wed, 18 Jun 2025 09:14:23 +0200 Subject: [PATCH 14/18] fmt --- modules/cloud-run-v2/service.tf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index 1b8418716..d493b927d 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -528,9 +528,7 @@ resource "google_cloud_run_v2_service_iam_binding" "binding" { locals { iap_iam_additive = local.iap_enabled ? var.iap_config.iam_additive : [] - - use_iap_iam_binding = var.iap_config != null && try(var.iap_config.iam, null) != null - iap_iam = local.iap_enabled ? var.iap_config.iam : [] + iap_iam = local.iap_enabled ? var.iap_config.iam : [] } From b062a8b52111b83f793bacc6566cfb501f8a0144 Mon Sep 17 00:00:00 2001 From: Maciej Sikora <96724614+msikora-rtb@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:19:24 +0200 Subject: [PATCH 15/18] Update modules/cloud-run-v2/variables.tf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Wiktor Niesiobędzki --- modules/cloud-run-v2/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index 8eeb32baa..1de08de08 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -136,7 +136,7 @@ variable "iap_config" { validation { condition = !(length(try(var.iap_config.iam, [])) > 0 && length(try(var.iap_config.iam_additive, [])) > 0) - error_message = "When 'iam' or 'iam_additive' lists are provided in iap_config, they must not be empty." + error_message = "Providing both 'iam' and 'iam_additive' in iap_config is not supported." } validation { From f55278617cec26ff502f4b449f534fa2eedc3912 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 18 Jun 2025 09:38:35 -0400 Subject: [PATCH 16/18] feat: Update session affinity validation for ALB (#3172) --- modules/net-lb-app-ext-regional/variables-backend-service.tf | 4 +++- modules/net-lb-app-ext/variables-backend-service.tf | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/net-lb-app-ext-regional/variables-backend-service.tf b/modules/net-lb-app-ext-regional/variables-backend-service.tf index 78146d431..3cd2933fd 100644 --- a/modules/net-lb-app-ext-regional/variables-backend-service.tf +++ b/modules/net-lb-app-ext-regional/variables-backend-service.tf @@ -125,7 +125,9 @@ variable "backend_service_configs" { for backend_service in values(var.backend_service_configs) : contains( [ "NONE", "CLIENT_IP", "CLIENT_IP_NO_DESTINATION", - "CLIENT_IP_PORT_PROTO", "CLIENT_IP_PROTO" + "CLIENT_IP_PORT_PROTO", "CLIENT_IP_PROTO", + "GENERATED_COOKIE", "HEADER_FIELD", "HTTP_COOKIE", + "STRONG_COOKIE_AFFINITY" ], coalesce(backend_service.session_affinity, "NONE") ) diff --git a/modules/net-lb-app-ext/variables-backend-service.tf b/modules/net-lb-app-ext/variables-backend-service.tf index 63d445a9d..19e88cb49 100644 --- a/modules/net-lb-app-ext/variables-backend-service.tf +++ b/modules/net-lb-app-ext/variables-backend-service.tf @@ -148,7 +148,9 @@ variable "backend_service_configs" { for backend_service in values(var.backend_service_configs) : contains( [ "NONE", "CLIENT_IP", "CLIENT_IP_NO_DESTINATION", - "CLIENT_IP_PORT_PROTO", "CLIENT_IP_PROTO" + "CLIENT_IP_PORT_PROTO", "CLIENT_IP_PROTO", + "GENERATED_COOKIE", "HEADER_FIELD", "HTTP_COOKIE", + "STRONG_COOKIE_AFFINITY" ], coalesce(backend_service.session_affinity, "NONE") ) From 6d8b813e5121e4400934b2bfa5d1d9f8fa2368e3 Mon Sep 17 00:00:00 2001 From: Liam Nesteroff <35284740+lnesteroff@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:00:17 +1000 Subject: [PATCH 17/18] fixed option to set descriptions for environment tag values (#3174) --- fast/stages/1-resman/organization.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast/stages/1-resman/organization.tf b/fast/stages/1-resman/organization.tf index 953234b03..1ab162223 100644 --- a/fast/stages/1-resman/organization.tf +++ b/fast/stages/1-resman/organization.tf @@ -60,7 +60,7 @@ locals { } ) description = try( - local.tags.environment.values[v].description, null + local.tags.environment.values[v.tag_name].description, null ) } } From 3df2c700cace6b056ca0f3803905c2a10b8abe2f Mon Sep 17 00:00:00 2001 From: Luca Prete Date: Thu, 19 Jun 2025 12:36:28 +0300 Subject: [PATCH 18/18] Add new Agentspace module (#3170) --- README.md | 1 + modules/README.md | 4 + modules/agentspace/README.md | 253 +++++++++++++++++++++++++++++++ modules/agentspace/engines.tf | 94 ++++++++++++ modules/agentspace/main.tf | 194 ++++++++++++++++++++++++ modules/agentspace/outputs.tf | 54 +++++++ modules/agentspace/variables.tf | 169 +++++++++++++++++++++ modules/agentspace/versions.tf | 35 +++++ modules/agentspace/versions.tofu | 35 +++++ 9 files changed, 839 insertions(+) create mode 100644 modules/agentspace/README.md create mode 100644 modules/agentspace/engines.tf create mode 100644 modules/agentspace/main.tf create mode 100644 modules/agentspace/outputs.tf create mode 100644 modules/agentspace/variables.tf create mode 100644 modules/agentspace/versions.tf create mode 100644 modules/agentspace/versions.tofu diff --git a/README.md b/README.md index 6c2375998..ec08bbbc8 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Currently available modules: - **networking** - [DNS](./modules/dns), [DNS Response Policy](./modules/dns-response-policy/), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [VLAN Attachment](./modules/net-vlan-attachment/), [External Application LB](./modules/net-lb-app-ext/), [External Passthrough Network LB](./modules/net-lb-ext), [External Regional Application Load Balancer](./modules/net-lb-app-ext-regional/), [Firewall policy](./modules/net-firewall-policy), [Internal Application LB](./modules/net-lb-app-int), [Cross-region Internal Application LB](./modules/net-lb-app-int-cross-region), [Internal Passthrough Network LB](./modules/net-lb-int), [Internal Proxy Network LB](./modules/net-lb-proxy-int), [IPSec over Interconnect](./modules/net-ipsec-over-interconnect), [VPC](./modules/net-vpc), [VPC factory](./modules/net-vpc-factory/README.md), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory), [Secure Web Proxy](./modules/net-swp) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster-standard), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool), [GCVE private cloud](./modules/gcve-private-cloud) - **data** - [AlloyDB instance](./modules/alloydb), [Analytics Hub](./modules/analytics-hub), [BigQuery dataset](./modules/bigquery-dataset), [Biglake Catalog](./modules/biglake-catalog), [Bigtable instance](./modules/bigtable-instance), [Dataplex](./modules/dataplex), [Dataplex Aspect Types](./modules/dataplex-aspect-types/), [Dataplex DataScan](./modules/dataplex-datascan), [Cloud SQL instance](./modules/cloudsql-instance), [Spanner instance](./modules/spanner-instance), [Firestore](./modules/firestore), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Data Catalog Tag](./modules/data-catalog-tag), [Data Catalog Tag Template](./modules/data-catalog-tag-template), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub), [Dataform Repository](./modules/dataform-repository/), [Looker Core](./modules/looker-core) +- **AI** - [Agentspace](./modules/agentspace/README.md) - **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository), [Secure Source Manager instance](./modules/secure-source-manager-instance), [Workstation cluster](./modules/workstation-cluster) - **security** - [Binauthz](./modules/binauthz/), [Certificate Authority Service (CAS)](./modules/certificate-authority-service), [KMS](./modules/kms), [SecretManager](./modules/secret-manager), [VPC Service Control](./modules/vpc-sc), [Certificate Manager](./modules/certificate-manager/) - **serverless** - [Cloud Function v1](./modules/cloud-function-v1), [Cloud Function v2](./modules/cloud-function-v2), [Cloud Run](./modules/cloud-run), [Cloud Run v2](./modules/cloud-run-v2) diff --git a/modules/README.md b/modules/README.md index 6851500f0..48105f494 100644 --- a/modules/README.md +++ b/modules/README.md @@ -101,6 +101,10 @@ These modules are used in the examples included in this repository. If you are u - [Pub/Sub](./pubsub) - [Spanner instance](./spanner-instance) +## AI + +- [Agentspace](./agentspace/README.md) + ## Development - [API Gateway](./api-gateway) diff --git a/modules/agentspace/README.md b/modules/agentspace/README.md new file mode 100644 index 000000000..033517b6d --- /dev/null +++ b/modules/agentspace/README.md @@ -0,0 +1,253 @@ +# Agentspace + +This module handles the creation of Agentspace data sources, engines and related configurations. + + +* [Agentspace module](#agentspace) + * [APIs](#apis) + * [Quota Project](#quota-project) + * [Examples](#examples) + * [Chat Engine](#chat-engine) + * [Search Engine](#search-engine) + * [Deploy your service into a region](#deploy-your-service-into-a-region) + * [Reference Existing Data Sources](#reference-existing-data-sources) + * [Using multiple data stores](#using-multiple-data-stores) + * [Set data store schemas](#set-data-store-schemas) + * [Back data stores with websites data](#back-data-stores-with-websites-data) + * [Variables](#variables) + * [Outputs](#outputs) + + +## APIs + +This module uses the API `discoveryengine.googleapis.com` + +## Quota Project + +To run this module you'll need to set a quota project. + +```shell +export GOOGLE_BILLING_PROJECT=your-project-id +export USER_PROJECT_OVERRIDE=true +``` + +## Examples + +### Chat Engine + +This is a minimal example to create a Chat Engine agent. + +```hcl +module "agentspace" { + source = "./fabric/modules/agentspace" + name = "my-chat-app" + project_id = var.project_id + data_stores_configs = { + data-store-1 = { + solution_types = ["SOLUTION_TYPE_CHAT"] + } + } + engines_configs = { + my-chat-engine = { + data_store_ids = ["data-store-1"] + chat_engine_config = { + company_name = "Google" + default_language_code = "en" + time_zone = "America/Los_Angeles" + } + } + } +} +# tftest modules=1 resources=2 +``` + +### Search Engine + +This is a minimal example to create a Search Engine agent. + +```hcl +module "agentspace" { + source = "./fabric/modules/agentspace" + name = "my-search-app" + project_id = var.project_id + data_stores_configs = { + data-store-1 = { + solution_types = ["SOLUTION_TYPE_SEARCH"] + } + } + engines_configs = { + my-search-engine = { + data_store_ids = ["data-store-1"] + search_engine_config = {} + } + } +} +# tftest modules=1 resources=2 +``` + +### Deploy your service into a region + +By default services are deployed globally. You optionally specify a region where to deploy them. + +```hcl +module "agentspace" { + source = "./fabric/modules/agentspace" + name = "my-chat-app" + project_id = var.project_id + location = var.region + data_stores_configs = { + data-store-1 = { + solution_types = ["SOLUTION_TYPE_CHAT"] + } + } + engines_configs = { + my-chat-engine = { + data_store_ids = ["data-store-1"] + chat_engine_config = { + company_name = "Google" + default_language_code = "en" + time_zone = "America/Los_Angeles" + } + } + } +} +# tftest modules=1 resources=2 +``` + +### Reference existing data sources + +You can reference from engines existing data sources created outside this module, by passing their ids. In this case, you'll need to configure in the engine valid `industry_vertical` and `location`. + +```hcl +module "agentspace" { + source = "./fabric/modules/agentspace" + name = "my-search-app" + project_id = var.project_id + engines_configs = { + my-search-engine = { + data_store_ids = [ + "projects/my-project/locations/global/collections/my-collection/dataStores/data-store-1" + ] + industry_vertical = "GENERIC" + search_engine_config = {} + } + } +} +# tftest modules=1 resources=1 +``` + +### Using multiple data stores + +You can create and connect from your engines multiple data stores. + +```hcl +module "agentspace" { + source = "./fabric/modules/agentspace" + name = "my-chat-app" + project_id = var.project_id + data_stores_configs = { + data-store-1 = { + solution_types = ["SOLUTION_TYPE_CHAT"] + } + data-store-2 = { + solution_types = ["SOLUTION_TYPE_CHAT"] + } + } + engines_configs = { + my-chat-engine = { + data_store_ids = [ + "data-store-1", + "data-store-2", + "projects/my-project/locations/global/collections/default_collection/dataStores/data-store-3" + ] + chat_engine_config = { + company_name = "Google" + default_language_code = "en" + time_zone = "America/Los_Angeles" + } + } + } +} +# tftest modules=1 resources=3 +``` + +### Set data store schemas + +You can configure JSON data store schema definitions directly in your data store configuration. + +```hcl +module "agentspace" { + source = "./fabric/modules/agentspace" + name = "my-search-app" + project_id = var.project_id + data_stores_configs = { + data-store-1 = { + json_schema = "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"datetime_detection\":true,\"type\":\"object\",\"geolocation_detection\":true}" + solution_types = ["SOLUTION_TYPE_SEARCH"] + } + } +} +# tftest modules=1 resources=2 +``` + +### Back data stores with websites data + +You can make data stores point to multiple websites and optionally specify their sitemap. + +```hcl +module "agentspace" { + source = "./fabric/modules/agentspace" + name = "my-search-app" + project_id = var.project_id + data_stores_configs = { + website-search-ds = { + solution_types = ["SOLUTION_TYPE_SEARCH"] + sites_search_config = { + sitemap_uri = "https://cloud.google.com/sitemap.xml" + target_sites = { + include-google-docs = { + provided_uri_pattern = "cloud.google.com/docs/*" + } + exclude-one-page = { + exact_match = true + provided_uri_pattern = "https://cloud.google.com/agentspace" + type = "EXCLUDE" + } + } + } + } + } + engines_configs = { + my-search-engine = { + data_store_ids = [ + "website-search-ds" + ] + industry_vertical = "GENERIC" + search_engine_config = {} + } + } +} +# tftest modules=1 resources=5 +``` + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [name](variables.tf#L159) | The name of the resources. | string | ✓ | | +| [project_id](variables.tf#L165) | The ID of the project where the data stores and the agents will be created. | string | ✓ | | +| [data_stores_configs](variables.tf#L17) | The Agentspace datastore configurations. | map(object({…})) | | {} | +| [engines_configs](variables.tf#L112) | The Agentspace engines configurations. | map(object({…})) | | {} | +| [location](variables.tf#L153) | Location where the data stores and agents will be created. | string | | "global" | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [chat_engine_ids](outputs.tf#L17) | The ids of the chat engines created. | | +| [chat_engines](outputs.tf#L25) | The chat engines created. | | +| [data_store_ids](outputs.tf#L30) | The ids of the data stores created. | | +| [data_stores](outputs.tf#L38) | The data stores resources created. | | +| [search_engine_ids](outputs.tf#L43) | The ids of the search engines created. | | +| [search_engines](outputs.tf#L51) | The search engines created. | | + diff --git a/modules/agentspace/engines.tf b/modules/agentspace/engines.tf new file mode 100644 index 000000000..4c84f9b79 --- /dev/null +++ b/modules/agentspace/engines.tf @@ -0,0 +1,94 @@ +/** + * 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. + */ + +resource "google_discovery_engine_chat_engine" "default" { + for_each = ({ + for k, v in var.engines_configs + : k => v if v.chat_engine_config != null + }) + engine_id = "${var.name}-${each.key}" + display_name = "${var.name}-${each.key}" + collection_id = each.value.collection_id + project = var.project_id + data_store_ids = [ + for ds in each.value.data_store_ids + : coalesce( + try(google_discovery_engine_data_store.default[ds].data_store_id, null), + ds + ) + ] + industry_vertical = coalesce( + try(each.value.industry_vertical, null), + try(google_discovery_engine_data_store.default[each.value.data_store_ids[0]].industry_vertical, null) + ) + location = coalesce( + try(each.value.location, null), + try(google_discovery_engine_data_store.default[each.value.data_store_ids[0]].location, null), + var.location + ) + + chat_engine_config { + allow_cross_region = each.value.chat_engine_config.allow_cross_region + dialogflow_agent_to_link = each.value.chat_engine_config.dialogflow_agent_to_link + + agent_creation_config { + business = each.value.chat_engine_config.business + default_language_code = each.value.chat_engine_config.default_language_code + time_zone = each.value.chat_engine_config.time_zone + location = coalesce( + try(each.value.location, null), + try(google_discovery_engine_data_store.default[each.value.data_store_ids[0]].location, null), + var.location + ) + } + } + + common_config { + company_name = each.value.chat_engine_config.company_name + } +} + +resource "google_discovery_engine_search_engine" "default" { + for_each = ({ + for k, v in var.engines_configs + : k => v if v.search_engine_config != null + }) + engine_id = "${var.name}-${each.key}" + display_name = "${var.name}-${each.key}" + collection_id = each.value.collection_id + project = var.project_id + data_store_ids = [ + for ds in each.value.data_store_ids + : coalesce( + try(google_discovery_engine_data_store.default[ds].data_store_id, null), + ds + ) + ] + industry_vertical = coalesce( + try(each.value.industry_vertical, null), + try(google_discovery_engine_data_store.default[each.value.data_store_ids[0]].industry_vertical, null) + ) + location = coalesce( + try(each.value.location, null), + try(google_discovery_engine_data_store.default[each.value.data_store_ids[0]].location, null), + var.location + ) + + search_engine_config { + search_add_ons = each.value.search_engine_config.search_add_ons + search_tier = each.value.search_engine_config.search_tier + } +} diff --git a/modules/agentspace/main.tf b/modules/agentspace/main.tf new file mode 100644 index 000000000..5ae3d1119 --- /dev/null +++ b/modules/agentspace/main.tf @@ -0,0 +1,194 @@ +/** + * 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. + */ + +locals { + data_stores_target_sites = { + for item in flatten([ + for data_store_id, data_store_config in var.data_stores_configs : + [ + for target_site_key, target_site_config in try( + data_store_config.sites_search_config.target_sites, {}) : + merge({ + key = "${data_store_id}-${target_site_key}" + data_store_id = data_store_id + }, target_site_config) + ] + ]) : item.key => item + } +} + +resource "google_discovery_engine_data_store" "default" { + for_each = var.data_stores_configs + data_store_id = "${var.name}-${each.key}" + project = var.project_id + location = coalesce(each.value.location, var.location) + display_name = each.value.display_name + industry_vertical = each.value.industry_vertical + content_config = each.value.content_config + solution_types = each.value.solution_types + create_advanced_site_search = each.value.create_advanced_site_search + skip_default_schema_creation = each.value.skip_default_schema_creation + + dynamic "advanced_site_search_config" { + for_each = ( + try(each.value.advanced_site_search_config, null) == null + ? [] : [""] + ) + + content { + disable_initial_index = each.value.advanced_site_search_config.disable_initial_index + disable_automatic_refresh = each.value.advanced_site_search_config.disable_automatic_refresh + } + } + + dynamic "document_processing_config" { + for_each = ( + try(each.value.document_processing_config, null) == null + ? [] : [""] + ) + + content { + dynamic "chunking_config" { + for_each = ( + try(each.value.document_processing_config.chunking_config, null) == null + ? [] : [""] + ) + + content { + dynamic "layout_based_chunking_config" { + for_each = ( + try(each.value.document_processing_config.chunking_config.layout_based_chunking_config, null) == null + ? [] : [""] + ) + + content { + chunk_size = each.value.document_processing_config.chunking_config.layout_based_chunking_config.chunk_size + include_ancestor_headings = each.value.document_processing_config.chunking_config.layout_based_chunking_config.include_ancestor_headings + } + } + } + } + + dynamic "default_parsing_config" { + for_each = ( + try(each.value.document_processing_config.default_parsing_config, null) == null + ? [] : [""] + ) + + content { + dynamic "digital_parsing_config" { + for_each = ( + try(each.value.document_processing_config.default_parsing_config.digital_parsing_config, null) == null + ? [] : [""] + ) + + content {} + } + + dynamic "layout_parsing_config" { + for_each = ( + try(each.value.document_processing_config.default_parsing_config.layout_parsing_config, null) == null + ? [] : [""] + ) + + content {} + } + + dynamic "ocr_parsing_config" { + for_each = ( + try(each.value.document_processing_config.default_parsing_config.ocr_parsing_config, null) == null + ? [] : [""] + ) + + content { + use_native_text = each.value.document_processing_config.default_parsing_config.ocr_parsing_config.use_native_text + } + } + } + } + + dynamic "parsing_config_overrides" { + for_each = each.value.document_processing_config.parsing_config_overrides + + content { + file_type = parsing_config_overrides.key + + dynamic "digital_parsing_config" { + for_each = ( + try(parsing_config_overrides.value["digital_parsing_config"], null) == null + ? [] : [""] + ) + + content {} + } + + dynamic "layout_parsing_config" { + for_each = ( + try(parsing_config_overrides.value["layout_parsing_config"], null) == null + ? [] : [""] + ) + + content {} + } + + dynamic "ocr_parsing_config" { + for_each = ( + try(parsing_config_overrides.value["ocr_parsing_config"], null) == null + ? [] : [""] + ) + + content { + use_native_text = parsing_config_overrides.value["ocr_parsing_config"]["use_native_text"] + } + } + } + } + } + } +} + +resource "google_discovery_engine_schema" "default" { + for_each = ({ + for k, v in var.data_stores_configs + : k => v if try(v.json_schema, null) != null + }) + schema_id = "${var.name}-${each.key}" + project = var.project_id + location = google_discovery_engine_data_store.default[each.key].location + data_store_id = google_discovery_engine_data_store.default[each.key].data_store_id + json_schema = each.value.json_schema +} + +resource "google_discovery_engine_sitemap" "default" { + for_each = ({ + for k, v in var.data_stores_configs + : k => v if try(v.sites_search_config.sitemap_uri, null) != null + }) + project = var.project_id + location = google_discovery_engine_data_store.default[each.key].location + data_store_id = google_discovery_engine_data_store.default[each.key].data_store_id + uri = each.value.sites_search_config.sitemap_uri +} + +resource "google_discovery_engine_target_site" "default" { + for_each = local.data_stores_target_sites + project = var.project_id + location = google_discovery_engine_data_store.default[each.value.data_store_id].location + data_store_id = google_discovery_engine_data_store.default[each.value.data_store_id].data_store_id + provided_uri_pattern = each.value.provided_uri_pattern + type = each.value.type + exact_match = each.value.exact_match +} diff --git a/modules/agentspace/outputs.tf b/modules/agentspace/outputs.tf new file mode 100644 index 000000000..10ca868b7 --- /dev/null +++ b/modules/agentspace/outputs.tf @@ -0,0 +1,54 @@ +/** + * Copyright 2024 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. + */ + +output "chat_engine_ids" { + description = "The ids of the chat engines created." + value = { + for k, v in google_discovery_engine_chat_engine.default + : k => v.id + } +} + +output "chat_engines" { + description = "The chat engines created." + value = google_discovery_engine_chat_engine.default +} + +output "data_store_ids" { + description = "The ids of the data stores created." + value = { + for k, v in google_discovery_engine_data_store.default + : k => v.id + } +} + +output "data_stores" { + description = "The data stores resources created." + value = google_discovery_engine_data_store.default +} + +output "search_engine_ids" { + description = "The ids of the search engines created." + value = { + for k, v in google_discovery_engine_search_engine.default + : k => v.id + } +} + +output "search_engines" { + description = "The search engines created." + value = google_discovery_engine_search_engine.default +} diff --git a/modules/agentspace/variables.tf b/modules/agentspace/variables.tf new file mode 100644 index 000000000..8adbd96f1 --- /dev/null +++ b/modules/agentspace/variables.tf @@ -0,0 +1,169 @@ +/** + * 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. + */ + +variable "data_stores_configs" { + description = "The Agentspace datastore configurations." + type = map(object({ + advanced_site_search_config = optional(object({ + disable_initial_index = optional(bool) + disable_automatic_refresh = optional(bool) + })) + content_config = optional(string, "NO_CONTENT") + create_advanced_site_search = optional(bool) + display_name = optional(string, "Terraform managed.") + document_processing_config = optional(object({ + chunking_config = optional(object({ + layout_based_chunking_config = optional(object({ + chunk_size = optional(number) + include_ancestor_headings = optional(bool) + })) + })) + default_parsing_config = optional(object({ + digital_parsing_config = optional(bool) + layout_parsing_config = optional(bool) + ocr_parsing_config = optional(object({ + use_native_text = optional(bool) + })) + })) + # Accepted keys: docx, html, pdf + parsing_config_overrides = map(object({ + digital_parsing_config = optional(bool) + layout_parsing_config = optional(bool) + ocr_parsing_config = optional(object({ + use_native_text = optional(bool) + })) + })) + })) + industry_vertical = optional(string, "GENERIC") + json_schema = optional(string) + location = optional(string) + skip_default_schema_creation = optional(bool) + solution_types = optional(list(string)) + sites_search_config = optional(object({ + sitemap_uri = optional(string) + target_sites = map(object({ + provided_uri_pattern = string + exact_match = optional(bool, false) + type = optional(string, "INCLUDE") + })) + })) + })) + nullable = false + default = {} + validation { + condition = try(contains( + ["CONTENT_REQUIRED", "NO_CONTENT", "PUBLIC_WEBSITE"], + var.data_stores_configs.content_config + ), true) + error_message = "data_store_configs.content_config must be one or more of [CONTENT_REQUIRED, NO_CONTENT, PUBLIC_WEBSITE]." + } + validation { + condition = try(contains( + ["GENERIC", "HEALTHCARE_FHIR", "MEDIA"], + var.data_stores_configs.industry_vertical + ), true) + error_message = "data_store_configs.industry_vertical must be one or more of [GENERIC, HEALTHCARE_FHIR, MEDIA]." + } + validation { + condition = alltrue([ + for st in try(var.data_stores_configs.solution_types, []) + : contains([ + "SOLUTION_TYPE_CHAT", + "SOLUTION_TYPE_GENERATIVE_CHAT", + "SOLUTION_TYPE_RECOMMENDATION", + "SOLUTION_TYPE_SEARCH" + ], st) + ]) + error_message = "data_store_configs.solution_types must be one or more of [SOLUTION_TYPE_CHAT, SOLUTION_TYPE_GENERATIVE_CHAT, SOLUTION_TYPE_RECOMMENDATION, SOLUTION_TYPE_SEARCH]." + } + validation { + condition = alltrue([ + for k, _ in try(var.data_stores_configs.document_processing_config.parsing_config_overrides, {}) + : contains([ + "docx", + "html", + "pdf" + ], k) + ]) + error_message = "keys in var.data_stores_configs.document_processing_config.parsing_config_overrides must be one of [docx, html, pdf]." + } + validation { + condition = try(contains( + ["EXCLUDE", "INCLUDE"], + var.data_stores_configs.target_site_config + ), true) + error_message = "data_store_configs.target_site_config must be one or more of [EXCLUDE, INCLUDE]." + } +} + +variable "engines_configs" { + description = "The Agentspace engines configurations." + type = map(object({ + data_store_ids = list(string) + collection_id = optional(string, "default_collection") + chat_engine_config = optional(object({ + allow_cross_region = optional(bool) + business = optional(string) + company_name = optional(string) + default_language_code = optional(string) + dialogflow_agent_to_link = optional(string) + time_zone = optional(string) + })) + # If industry_vertical and location are not given, + # they are derived from the first datastore attached + # to the engines + industry_vertical = optional(string) + location = optional(string) + search_engine_config = optional(object({ + search_add_ons = optional(list(string), []) + search_tier = optional(string) + })) + })) + nullable = false + default = {} + validation { + condition = alltrue([ + for ao in try(var.engines_configs.search_engine_config.search_add_ons, []) + : contains(["SEARCH_ADD_ON_LLM"], ao) + ]) + error_message = "Elements in engines_configs.search_engine_config.search_add_ons must be one or more of [SEARCH_ADD_ON_LLM]." + } + validation { + condition = try(contains( + ["SEARCH_TIER_ENTERPRISE", "SEARCH_TIER_STANDARD"], + var.engines_configs.search_engine_config.search_tier + ), true) + error_message = "engines_configs.search_engine_config.search_tier must be one of [SEARCH_TIER_ENTERPRISE, SEARCH_TIER_STANDARD]." + } +} + +variable "location" { + description = "Location where the data stores and agents will be created." + type = string + default = "global" +} + +variable "name" { + description = "The name of the resources." + type = string + nullable = false +} + +variable "project_id" { + description = "The ID of the project where the data stores and the agents will be created." + type = string + nullable = false +} diff --git a/modules/agentspace/versions.tf b/modules/agentspace/versions.tf new file mode 100644 index 000000000..00cfef9d6 --- /dev/null +++ b/modules/agentspace/versions.tf @@ -0,0 +1,35 @@ +# 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 +# +# 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. + +# Fabric release: 40.1.0 + +terraform { + required_version = ">= 1.11.4" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 6.33.0, < 7.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 6.33.0, < 7.0.0" # tftest + } + } + provider_meta "google" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/alloydb:40.1.0-tf" + } + provider_meta "google-beta" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/alloydb:40.1.0-tf" + } +} diff --git a/modules/agentspace/versions.tofu b/modules/agentspace/versions.tofu new file mode 100644 index 000000000..b87d6d775 --- /dev/null +++ b/modules/agentspace/versions.tofu @@ -0,0 +1,35 @@ +# 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 +# +# 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. + +# Fabric release: 40.1.0 + +terraform { + required_version = ">= 1.9.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 6.33.0, < 7.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 6.33.0, < 7.0.0" # tftest + } + } + provider_meta "google" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/alloydb:40.1.0-tofu" + } + provider_meta "google-beta" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/alloydb:40.1.0-tofu" + } +}