Add Agent Engine identity type (#3875)

This commit is contained in:
Luca Prete
2026-05-05 10:22:21 +02:00
committed by GitHub
parent 9540b8d1ae
commit ba56d9afbc
23 changed files with 365 additions and 415 deletions

View File

@@ -18,7 +18,7 @@ The module creates Agent Engine and related dependencies.
- [Minimal deployment](#minimal-deployment)
- [Serialized Object Deployment](#serialized-object-deployment)
- [Unmanaged deployments](#unmanaged-deployments)
- [Service accounts](#service-accounts)
- [Identities](#identities)
- [Private networking: setup PSC-I](#private-networking-setup-psc-i)
- [Specify an encryption key](#specify-an-encryption-key)
- [Define environment variables and use secrets](#define-environment-variables-and-use-secrets)
@@ -72,6 +72,7 @@ module "agent_engine" {
deployment_config = {
source_files_config = {
source_path = "assets/src/source.tar.gz"
python_spec = null
image_spec = {
build_args = {
"ENV" = "production"
@@ -159,9 +160,11 @@ module "agent_engine" {
# tftest inventory=unmanaged.yaml
```
## Service accounts
## Identities
You can choose to use a custom service account or let the module create one for you.
By default, the module creates agents with unique **agent identities**.
If you want, you can choose instead to use a custom service account, by changing the `identity_type` to `SERVICE_ACCOUNT`.
```hcl
module "agent_engine" {
@@ -172,6 +175,7 @@ module "agent_engine" {
agent_engine_config = {
agent_framework = "google-adk"
identity_type = "SERVICE_ACCOUNT"
}
deployment_config = {
@@ -180,7 +184,7 @@ module "agent_engine" {
}
}
}
# tftest inventory=sa-default.yaml
# tftest inventory=sa-create.yaml
```
Using a custom service account.
@@ -194,6 +198,7 @@ module "agent_engine" {
agent_engine_config = {
agent_framework = "google-adk"
identity_type = "SERVICE_ACCOUNT"
}
deployment_config = {
@@ -207,7 +212,7 @@ module "agent_engine" {
email = "my-agent@project-id.iam.gserviceaccount.com"
}
}
# tftest inventory=sa-custom.yaml
# tftest inventory=sa-external.yaml
```
## Private networking: setup PSC-I
@@ -355,6 +360,7 @@ module "agent_engine" {
}
}
}
#tftest inventory=memory-bank.yaml
```
## Getting values from context
@@ -449,9 +455,9 @@ module "agent_engine" {
| [project_id](variables.tf#L197) | The id of the project where to deploy the agent. | <code>string</code> | ✓ | |
| [region](variables.tf#L203) | The region where to deploy the agent. | <code>string</code> | ✓ | |
| [agent_engine_config](variables.tf#L17) | The agent configuration. Supported values for agent_framework: 'google-adk', 'langchain', 'langgraph', 'ag2', 'llama-index', 'custom'. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [bucket_config](variables.tf#L41) | The GCS bucket configuration. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [context](variables.tf#L52) | Context-specific interpolations. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [deployment_config](variables.tf#L68) | The deployment configuration. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [bucket_config](variables.tf#L50) | The GCS bucket configuration. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [context](variables.tf#L61) | Context-specific interpolations. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [deployment_config](variables.tf#L77) | The deployment configuration. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [description](variables.tf#L128) | The Agent Engine description. | <code>string</code> | | <code>&#34;Terraform managed.&#34;</code> |
| [enable_deletion_protection](variables.tf#L135) | Whether deletion protection should be enabled. | <code>bool</code> | | <code>true</code> |
| [encryption_key](variables.tf#L142) | The full resource name of the Cloud KMS CryptoKey. | <code>string</code> | | <code>null</code> |
@@ -466,5 +472,5 @@ module "agent_engine" {
|---|---|:---:|
| [agent](outputs.tf#L17) | The Agent Engine object. | |
| [id](outputs.tf#L22) | Fully qualified Agent Engine id. | |
| [service_account](outputs.tf#L27) | Service account resource. | |
| [identity](outputs.tf#L27) | The agent identity. | |
<!-- END TFDOC -->

View File

@@ -15,17 +15,13 @@
*/
resource "google_vertex_ai_reasoning_engine" "managed" {
provider = google-beta
count = var.managed ? 1 : 0
display_name = var.name
project = local.project_id
description = var.description
region = local.location
deletion_policy = (
var.enable_deletion_protection
? null
: "FORCE"
)
provider = google-beta
count = var.managed ? 1 : 0
display_name = var.name
project = local.project_id
description = var.description
region = local.location
deletion_policy = var.enable_deletion_protection ? null : "FORCE"
dynamic "encryption_spec" {
for_each = var.encryption_key == null ? {} : { 1 = 1 }
@@ -46,6 +42,7 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
? null
: var.agent_engine_config.class_methods
)
identity_type = var.agent_engine_config.identity_type
service_account = local.service_account_email
dynamic "deployment_spec" {
@@ -126,7 +123,7 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
}
dynamic "container_spec" {
for_each = var.deployment_config.container_config != null ? { 1 = 1 } : {}
for_each = var.deployment_config.container_config == null ? {} : { 1 = 1 }
content {
image_uri = var.deployment_config.container_config.image_uri
@@ -134,7 +131,7 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
}
dynamic "package_spec" {
for_each = var.deployment_config.package_config != null ? { 1 = 1 } : {}
for_each = var.deployment_config.package_config == null ? {} : { 1 = 1 }
content {
python_version = var.agent_engine_config.python_version
@@ -157,14 +154,18 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
}
dynamic "source_code_spec" {
for_each = var.deployment_config.source_files_config != null ? { 1 = 1 } : {}
for_each = (
var.deployment_config.source_files_config == null
? {}
: { 1 = 1 }
)
content {
dynamic "inline_source" {
for_each = (
try(var.deployment_config.source_files_config.source_path, null) != null
? { 1 = 1 }
: {}
try(var.deployment_config.source_files_config.source_path, null) == null
? {}
: { 1 = 1 }
)
content {
source_archive = filebase64(var.deployment_config.source_files_config.source_path)
@@ -173,9 +174,9 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
dynamic "developer_connect_source" {
for_each = (
try(var.deployment_config.source_files_config.developer_connect_config, null) != null
? { 1 = 1 }
: {}
try(var.deployment_config.source_files_config.developer_connect_config, null) == null
? {}
: { 1 = 1 }
)
content {
config {
@@ -188,9 +189,10 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
dynamic "python_spec" {
for_each = (
try(var.deployment_config.source_files_config.python_spec, null) != null
? { 1 = 1 }
: {}
try(var.deployment_config.source_files_config.python_spec, null) == null
|| try(var.deployment_config.source_files_config.image_spec, null) != null
? {}
: { 1 = 1 }
)
content {
entrypoint_module = var.deployment_config.source_files_config.python_spec.entrypoint_module
@@ -202,9 +204,9 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
dynamic "image_spec" {
for_each = (
try(var.deployment_config.source_files_config.image_spec, null) != null
? { 1 = 1 }
: {}
try(var.deployment_config.source_files_config.image_spec, null) == null
? {}
: { 1 = 1 }
)
content {
build_args = var.deployment_config.source_files_config.image_spec.build_args
@@ -215,7 +217,7 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
}
dynamic "context_spec" {
for_each = var.memory_bank_config != null ? { 1 = 1 } : {}
for_each = var.memory_bank_config == null ? {} : { 1 = 1 }
content {
memory_bank_config {
@@ -223,7 +225,7 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
dynamic "generation_config" {
for_each = (
var.memory_bank_config.generation_config != null ? { 1 = 1 } : {}
var.memory_bank_config.generation_config == null ? {} : { 1 = 1 }
)
content {
model = lookup(
@@ -236,7 +238,9 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
dynamic "similarity_search_config" {
for_each = (
var.memory_bank_config.similarity_search_config != null ? { 1 = 1 } : {}
var.memory_bank_config.similarity_search_config == null
? {}
: { 1 = 1 }
)
content {
embedding_model = lookup(
@@ -249,7 +253,7 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
dynamic "ttl_config" {
for_each = (
var.memory_bank_config.ttl_config != null ? { 1 = 1 } : {}
var.memory_bank_config.ttl_config == null ? {} : { 1 = 1 }
)
content {
default_ttl = var.memory_bank_config.ttl_config.default_ttl
@@ -257,9 +261,9 @@ resource "google_vertex_ai_reasoning_engine" "managed" {
dynamic "granular_ttl_config" {
for_each = (
var.memory_bank_config.ttl_config.granular_ttl_config != null
? { 1 = 1 }
: {}
var.memory_bank_config.ttl_config.granular_ttl_config == null
? {}
: { 1 = 1 }
)
content {
create_ttl = (

View File

@@ -15,17 +15,13 @@
*/
resource "google_vertex_ai_reasoning_engine" "unmanaged" {
provider = google-beta
count = var.managed ? 0 : 1
display_name = var.name
project = local.project_id
description = var.description
region = local.location
deletion_policy = (
var.enable_deletion_protection
? null
: "FORCE"
)
provider = google-beta
count = var.managed ? 0 : 1
display_name = var.name
project = local.project_id
description = var.description
region = local.location
deletion_policy = var.enable_deletion_protection ? null : "FORCE"
dynamic "encryption_spec" {
for_each = var.encryption_key == null ? {} : { 1 = 1 }
@@ -46,6 +42,7 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
? null
: var.agent_engine_config.class_methods
)
identity_type = var.agent_engine_config.identity_type
service_account = local.service_account_email
dynamic "deployment_spec" {
@@ -126,7 +123,7 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
}
dynamic "container_spec" {
for_each = var.deployment_config.container_config != null ? { 1 = 1 } : {}
for_each = var.deployment_config.container_config == null ? {} : { 1 = 1 }
content {
image_uri = var.deployment_config.container_config.image_uri
@@ -134,7 +131,7 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
}
dynamic "package_spec" {
for_each = var.deployment_config.package_config != null ? { 1 = 1 } : {}
for_each = var.deployment_config.package_config == null ? {} : { 1 = 1 }
content {
python_version = var.agent_engine_config.python_version
@@ -157,12 +154,16 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
}
dynamic "source_code_spec" {
for_each = var.deployment_config.source_files_config != null ? { 1 = 1 } : {}
for_each = (
var.deployment_config.source_files_config == null ?
{}
: { 1 = 1 }
)
content {
dynamic "inline_source" {
for_each = (
try(var.deployment_config.source_files_config.source_path, null) != null
try(var.deployment_config.source_files_config.source_path, null) == null
? { 1 = 1 }
: {}
)
@@ -173,9 +174,9 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
dynamic "developer_connect_source" {
for_each = (
try(var.deployment_config.source_files_config.developer_connect_config, null) != null
? { 1 = 1 }
: {}
try(var.deployment_config.source_files_config.developer_connect_config, null) == null
? {}
: { 1 = 1 }
)
content {
config {
@@ -188,9 +189,9 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
dynamic "python_spec" {
for_each = (
try(var.deployment_config.source_files_config.python_spec, null) != null
? { 1 = 1 }
: {}
try(var.deployment_config.source_files_config.python_spec, null) == null
? {}
: { 1 = 1 }
)
content {
entrypoint_module = var.deployment_config.source_files_config.python_spec.entrypoint_module
@@ -202,9 +203,9 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
dynamic "image_spec" {
for_each = (
try(var.deployment_config.source_files_config.image_spec, null) != null
? { 1 = 1 }
: {}
try(var.deployment_config.source_files_config.image_spec, null) == null
? {}
: { 1 = 1 }
)
content {
build_args = var.deployment_config.source_files_config.image_spec.build_args
@@ -215,7 +216,7 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
}
dynamic "context_spec" {
for_each = var.memory_bank_config != null ? { 1 = 1 } : {}
for_each = var.memory_bank_config == null ? {} : { 1 = 1 }
content {
memory_bank_config {
@@ -223,7 +224,7 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
dynamic "generation_config" {
for_each = (
var.memory_bank_config.generation_config != null ? { 1 = 1 } : {}
var.memory_bank_config.generation_config == null ? {} : { 1 = 1 }
)
content {
model = lookup(
@@ -236,7 +237,9 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
dynamic "similarity_search_config" {
for_each = (
var.memory_bank_config.similarity_search_config != null ? { 1 = 1 } : {}
var.memory_bank_config.similarity_search_config == null
? {}
: { 1 = 1 }
)
content {
embedding_model = lookup(
@@ -249,7 +252,7 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
dynamic "ttl_config" {
for_each = (
var.memory_bank_config.ttl_config != null ? { 1 = 1 } : {}
var.memory_bank_config.ttl_config == null ? {} : { 1 = 1 }
)
content {
default_ttl = var.memory_bank_config.ttl_config.default_ttl
@@ -257,9 +260,9 @@ resource "google_vertex_ai_reasoning_engine" "unmanaged" {
dynamic "granular_ttl_config" {
for_each = (
var.memory_bank_config.ttl_config.granular_ttl_config != null
? { 1 = 1 }
: {}
var.memory_bank_config.ttl_config.granular_ttl_config == null
? {}
: { 1 = 1 }
)
content {
create_ttl = (

View File

@@ -0,0 +1,85 @@
/**
* Copyright 2026 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 {
_effective_identity = coalesce(
try(google_vertex_ai_reasoning_engine.managed[0].spec[0].effective_identity, null),
try(google_vertex_ai_reasoning_engine.unmanaged[0].spec[0].effective_identity, null)
)
effective_identity = (
local._effective_identity == null
? "principal://${local._effective_identity}"
: null
)
identity = coalesce(
local.effective_identity,
local.service_account_email,
"service-{your_project_number}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"
)
service_account_email = (
var.service_account_config.create
? try(google_service_account.service_account[0].email, null) # use managed SA, when creating
: (var.service_account_config.email == null ? null # set to null, if no email provided
: lookup( # lookup SA in context
local.ctx.iam_principals,
var.service_account_config.email,
var.service_account_config.email
)
)
)
roles = [
for role in var.service_account_config.roles
: lookup(local.ctx.custom_roles, role, role)
]
}
resource "google_service_account" "service_account" {
count = (
var.service_account_config.create
&& var.agent_engine_config.identity_type == "SERVICE_ACCOUNT"
? 1 : 0
)
project = local.project_id
account_id = coalesce(var.service_account_config.name, var.name)
display_name = coalesce(
var.service_account_config.display_name,
var.service_account_config.name,
var.name
)
}
resource "google_project_iam_member" "iam_member_sa" {
for_each = (
var.service_account_config.create
&& var.agent_engine_config.identity_type == "SERVICE_ACCOUNT"
? toset(local.roles)
: toset([])
)
role = each.key
project = local.project_id
member = google_service_account.service_account[0].member
}
resource "google_project_iam_member" "iam_member_identity" {
for_each = (
var.agent_engine_config.identity_type == "AGENT_IDENTITY"
? toset(local.roles)
: toset([])
)
role = each.key
project = local.project_id
member = local.effective_identity
}

View File

@@ -43,16 +43,6 @@ locals {
}
}
# TODO: fix once eventual consistency issue is solved.
# AE doesn't retry the deployment (yet) if bindings are still not active.
resource "time_sleep" "wait_5_minutes" {
create_duration = "5m"
depends_on = [
google_project_iam_member.default
]
}
resource "google_storage_bucket" "default" {
count = (
var.bucket_config.create

View File

@@ -24,7 +24,7 @@ output "id" {
value = local.resource.id
}
output "service_account" {
description = "Service account resource."
value = try(google_service_account.service_account[0], null)
output "identity" {
description = "The agent identity."
value = local.identity
}

View File

@@ -1,55 +0,0 @@
/**
* 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 {
service_account_email = (
var.service_account_config.create
? google_service_account.service_account[0].email # use managed SA, when creating
: (var.service_account_config.email == null ? null # set to null, if no email provided
: lookup( # lookup SA in context
local.ctx.iam_principals,
var.service_account_config.email,
var.service_account_config.email
)
)
)
service_account_roles = [
for role in var.service_account_config.roles
: lookup(local.ctx.custom_roles, role, role)
]
}
resource "google_service_account" "service_account" {
count = var.service_account_config.create ? 1 : 0
project = local.project_id
account_id = coalesce(var.service_account_config.name, var.name)
display_name = coalesce(
var.service_account_config.display_name,
var.service_account_config.name,
var.name
)
}
resource "google_project_iam_member" "default" {
for_each = (
var.service_account_config.create
? toset(local.service_account_roles)
: toset([])
)
role = each.key
project = local.project_id
member = google_service_account.service_account[0].member
}

View File

@@ -22,6 +22,7 @@ variable "agent_engine_config" {
class_methods = optional(string)
container_concurrency = optional(number)
environment_variables = optional(map(string), {})
identity_type = optional(string, "AGENT_IDENTITY")
max_instances = optional(number)
min_instances = optional(number)
python_version = optional(string, "3.13")
@@ -36,6 +37,14 @@ variable "agent_engine_config" {
})
nullable = false
default = {}
validation {
condition = (
var.agent_engine_config.identity_type == "AGENT_IDENTITY"
|| var.agent_engine_config.identity_type == "SERVICE_ACCOUNT"
)
error_message = "var.agent_engine_config.identity_type must be either AGENT_IDENTITY or SERVICE_ACCOUNT."
}
}
variable "bucket_config" {
@@ -88,7 +97,7 @@ variable "deployment_config" {
entrypoint_module = optional(string, "agent")
entrypoint_object = optional(string, "agent")
requirements_file = optional(string, "requirements.txt")
}))
}), {})
image_spec = optional(object({
build_args = optional(map(string), {})
}))
@@ -114,15 +123,6 @@ variable "deployment_config" {
)
error_message = "Only one of 'source_path' or 'developer_connect_config' can be specified within 'source_files_config'."
}
validation {
condition = (
var.deployment_config.source_files_config == null ? true : (
(var.deployment_config.source_files_config.python_spec != null ? 1 : 0) +
(var.deployment_config.source_files_config.image_spec != null ? 1 : 0)
) <= 1
)
error_message = "Only one of 'python_spec' or 'image_spec' can be specified within 'source_files_config'."
}
}
variable "description" {