2424import click
2525import google .auth
2626import vertexai
27+ from google .cloud import resourcemanager_v3
28+ from google .iam .v1 import iam_policy_pb2 , policy_pb2
2729from vertexai ._genai import _agent_engines_utils
28- from vertexai ._genai .types import AgentEngine , AgentEngineConfig {% - if cookiecutter .is_adk_live % }, AgentServerMode {% - endif % }
30+ {% - if cookiecutter .is_adk_live % }
31+ from vertexai ._genai .types import (
32+ AgentEngine ,
33+ AgentEngineConfig ,
34+ AgentServerMode ,
35+ IdentityType ,
36+ )
37+ {% - else % }
38+ from vertexai ._genai .types import AgentEngine , AgentEngineConfig , IdentityType
39+ {% - endif % }
2940{% - if cookiecutter .is_adk_live % }
3041
3142from {{cookiecutter .agent_directory }}.app_utils .gcs import create_bucket_if_not_exists
@@ -129,6 +140,36 @@ def print_deployment_success(
129140{% - endif % }
130141
131142
143+ def grant_agent_identity_roles (agent : Any , project : str ) -> None :
144+ """Grant required IAM roles to the agent identity principal.
145+
146+ Uses get-modify-set pattern with etag for optimistic concurrency control.
147+ The policy object returned by get_iam_policy includes an etag that is
148+ automatically validated by set_iam_policy to prevent race conditions.
149+ """
150+ roles = [
151+ "roles/aiplatform.expressUser" ,
152+ "roles/serviceusage.serviceUsageConsumer" ,
153+ "roles/browser" ,
154+ ]
155+ principal = f"principal://{ agent .api_resource .spec .effective_identity } "
156+ click .echo (f"\n 🔐 Granting IAM roles to agent identity: { principal } " )
157+ proj_client = resourcemanager_v3 .ProjectsClient ()
158+ # Policy includes etag for optimistic locking - set_iam_policy will fail
159+ # if policy was modified between get and set operations
160+ policy = proj_client .get_iam_policy (
161+ request = iam_policy_pb2 .GetIamPolicyRequest (resource = f"projects/{ project } " )
162+ )
163+ for role in roles :
164+ policy .bindings .append (policy_pb2 .Binding (role = role , members = [principal ]))
165+ proj_client .set_iam_policy (
166+ request = iam_policy_pb2 .SetIamPolicyRequest (
167+ resource = f"projects/{ project } " , policy = policy
168+ )
169+ )
170+ click .echo (" ✅ Granted IAM roles" )
171+
172+
132173@click .command ()
133174@click .option (
134175 "--project" ,
@@ -220,6 +261,12 @@ def print_deployment_success(
220261 default = 1 ,
221262 help = "Number of worker processes (default: 1)" ,
222263)
264+ @click .option (
265+ "--agent-identity" ,
266+ is_flag = True ,
267+ default = False ,
268+ help = "Enable agent identity for per-agent IAM access control (Preview feature)" ,
269+ )
223270def deploy_agent_engine_app (
224271 project : str | None ,
225272 location : str ,
@@ -238,6 +285,7 @@ def deploy_agent_engine_app(
238285 memory : str ,
239286 container_concurrency : int ,
240287 num_workers : int ,
288+ agent_identity : bool ,
241289) -> AgentEngine :
242290 """Deploy the agent engine app to Vertex AI."""
243291
@@ -279,6 +327,8 @@ def deploy_agent_engine_app(
279327 click .echo (f" Container Concurrency: { container_concurrency } " )
280328 if service_account :
281329 click .echo (f" Service Account: { service_account } " )
330+ if agent_identity :
331+ click .echo (" Agent Identity: Enabled (Preview)" )
282332 if env_vars :
283333 click .echo ("\n 🌍 Environment Variables:" )
284334 for key , value in sorted (env_vars .items ()):
@@ -287,9 +337,12 @@ def deploy_agent_engine_app(
287337 source_packages_list = list (source_packages )
288338
289339 # Initialize vertexai client
340+ # Use v1beta1 API when agent identity is enabled (required for identity_type)
341+ http_options = {"api_version" : "v1beta1" } if agent_identity else None
290342 client = vertexai .Client (
291343 project = project ,
292344 location = location ,
345+ http_options = http_options ,
293346 )
294347 vertexai .init (project = project , location = location )
295348
@@ -333,6 +386,7 @@ def deploy_agent_engine_app(
333386 gcs_dir_name = display_name ,
334387 agent_server_mode = AgentServerMode .EXPERIMENTAL , # Enable bidi streaming
335388 resource_limits = {"cpu" : cpu , "memory" : memory },
389+ identity_type = IdentityType .AGENT_IDENTITY if agent_identity else None ,
336390 )
337391
338392 agent_config = {
@@ -361,6 +415,7 @@ def deploy_agent_engine_app(
361415{% - if cookiecutter .is_adk and not cookiecutter .is_a2a and not cookiecutter .is_adk_live % }
362416 agent_framework = "google-adk" ,
363417{% - endif % }
418+ identity_type = IdentityType .AGENT_IDENTITY if agent_identity else None ,
364419 )
365420{% - endif % }
366421
@@ -372,32 +427,44 @@ def deploy_agent_engine_app(
372427 if agent .api_resource .display_name == display_name
373428 ]
374429
430+ # Handle agent identity: create empty agent if needed, then grant roles
431+ if agent_identity :
432+ if not matching_agents :
433+ click .echo (f"\n 🔧 Creating agent identity for: { display_name } " )
434+ empty_agent = client .agent_engines .create (
435+ config = {"identity_type" : IdentityType .AGENT_IDENTITY }
436+ )
437+ matching_agents = [empty_agent ]
438+ grant_agent_identity_roles (matching_agents [0 ], project )
439+
375440 # Deploy the agent (create or update)
376441 if matching_agents :
377- click .echo (f"\n 📝 Updating existing agent: { display_name } " )
378- else :
379- click .echo (f"\n 🚀 Creating new agent: { display_name } " )
380-
381- click .echo ("🚀 Deploying to Vertex AI Agent Engine (this can take 3-5 minutes)..." )
382-
442+ click .echo (f"\n 📝 Updating agent: { display_name } " )
443+ click .echo (
444+ "🚀 Deploying to Vertex AI Agent Engine (this can take 3-5 minutes)..."
445+ )
383446{% - if cookiecutter .is_adk_live % }
384- if matching_agents :
385447 remote_agent = client .agent_engines .update (
386448 name = matching_agents [0 ].api_resource .name ,
387449 agent = agent_instance ,
388450 config = config ,
389451 )
452+ {% - else % }
453+ remote_agent = client .agent_engines .update (
454+ name = matching_agents [0 ].api_resource .name , config = config
455+ )
456+ {% - endif % }
390457 else :
458+ click .echo (f"\n 🚀 Creating new agent: { display_name } " )
459+ click .echo (
460+ "🚀 Deploying to Vertex AI Agent Engine (this can take 3-5 minutes)..."
461+ )
462+ {% - if cookiecutter .is_adk_live % }
391463 remote_agent = client .agent_engines .create (
392464 agent = agent_instance ,
393465 config = config ,
394466 )
395467{% - else % }
396- if matching_agents :
397- remote_agent = client .agent_engines .update (
398- name = matching_agents [0 ].api_resource .name , config = config
399- )
400- else :
401468 remote_agent = client .agent_engines .create (config = config )
402469{% - endif % }
403470
0 commit comments