bedrock-agentcore-starter-toolkit 0.1.10__py3-none-any.whl → 0.1.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bedrock-agentcore-starter-toolkit might be problematic. Click here for more details.

Files changed (19) hide show
  1. bedrock_agentcore_starter_toolkit/cli/cli.py +1 -1
  2. bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +65 -46
  3. bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +15 -1
  4. bedrock_agentcore_starter_toolkit/operations/gateway/client.py +139 -0
  5. bedrock_agentcore_starter_toolkit/operations/runtime/destroy.py +61 -56
  6. bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +1 -1
  7. bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +15 -0
  8. bedrock_agentcore_starter_toolkit/services/import_agent/scripts/base_bedrock_translate.py +4 -3
  9. bedrock_agentcore_starter_toolkit/services/import_agent/utils.py +14 -0
  10. bedrock_agentcore_starter_toolkit/services/runtime.py +5 -1
  11. bedrock_agentcore_starter_toolkit/services/xray.py +161 -0
  12. bedrock_agentcore_starter_toolkit/utils/runtime/logs.py +12 -0
  13. bedrock_agentcore_starter_toolkit/utils/runtime/templates/Dockerfile.j2 +11 -25
  14. {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/METADATA +18 -2
  15. {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/RECORD +19 -18
  16. {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/WHEEL +0 -0
  17. {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/entry_points.txt +0 -0
  18. {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/licenses/LICENSE.txt +0 -0
  19. {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/licenses/NOTICE.txt +0 -0
@@ -5,7 +5,7 @@ import typer
5
5
  from ..cli.gateway.commands import create_mcp_gateway, create_mcp_gateway_target, gateway_app
6
6
  from ..utils.logging_config import setup_toolkit_logging
7
7
  from .import_agent.commands import import_agent
8
- from .runtime.commands import configure_app, invoke, launch, status, destroy
8
+ from .runtime.commands import configure_app, destroy, invoke, launch, status
9
9
 
10
10
  app = typer.Typer(name="agentcore", help="BedrockAgentCore CLI", add_completion=False, rich_markup_mode="rich")
11
11
 
@@ -20,7 +20,9 @@ from ...operations.runtime import (
20
20
  launch_bedrock_agentcore,
21
21
  validate_agent_name,
22
22
  )
23
+ from ...utils.runtime.config import load_config
23
24
  from ...utils.runtime.entrypoint import parse_entrypoint
25
+ from ...utils.runtime.logs import get_agent_log_paths, get_aws_tail_commands, get_genai_observability_url
24
26
  from ..common import _handle_error, _print_success, console
25
27
  from .configuration_manager import ConfigurationManager
26
28
 
@@ -66,7 +68,7 @@ def _prompt_for_requirements_file(prompt_text: str, default: str = "") -> Option
66
68
  return None
67
69
 
68
70
 
69
- def _handle_requirements_file_display(requirements_file: Optional[str]) -> Optional[str]:
71
+ def _handle_requirements_file_display(requirements_file: Optional[str], non_interactive: bool = False) -> Optional[str]:
70
72
  """Handle requirements file with display logic for CLI."""
71
73
  from ...utils.runtime.entrypoint import detect_dependencies
72
74
 
@@ -74,6 +76,15 @@ def _handle_requirements_file_display(requirements_file: Optional[str]) -> Optio
74
76
  # User provided file - validate and show confirmation
75
77
  return _validate_requirements_file(requirements_file)
76
78
 
79
+ if non_interactive:
80
+ # Auto-detection for non-interactive mode
81
+ deps = detect_dependencies(Path.cwd())
82
+ if deps.found:
83
+ _print_success(f"Using detected file: [dim]{deps.file}[/dim]")
84
+ return None # Use detected file
85
+ else:
86
+ _handle_error("No requirements file specified and none found automatically")
87
+
77
88
  # Auto-detection with interactive prompt
78
89
  deps = detect_dependencies(Path.cwd())
79
90
 
@@ -112,8 +123,6 @@ def list_agents():
112
123
  """List configured agents."""
113
124
  config_path = Path.cwd() / ".bedrock_agentcore.yaml"
114
125
  try:
115
- from ...utils.runtime.config import load_config
116
-
117
126
  project_config = load_config(config_path)
118
127
  if not project_config.agents:
119
128
  console.print("[yellow]No agents configured.[/yellow]")
@@ -170,6 +179,9 @@ def configure(
170
179
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output"),
171
180
  region: Optional[str] = typer.Option(None, "--region", "-r"),
172
181
  protocol: Optional[str] = typer.Option(None, "--protocol", "-p", help="Server protocol (HTTP or MCP)"),
182
+ non_interactive: bool = typer.Option(
183
+ False, "--non-interactive", "-ni", help="Skip prompts; use defaults unless overridden"
184
+ ),
173
185
  ):
174
186
  """Configure a Bedrock AgentCore agent. The agent name defaults to your Python file name."""
175
187
  if ctx.invoked_subcommand is not None:
@@ -196,7 +208,7 @@ def configure(
196
208
 
197
209
  # Create configuration manager for clean, elegant prompting
198
210
  config_path = Path.cwd() / ".bedrock_agentcore.yaml"
199
- config_manager = ConfigurationManager(config_path)
211
+ config_manager = ConfigurationManager(config_path, non_interactive)
200
212
 
201
213
  # Interactive prompts for missing values - clean and elegant
202
214
  if not execution_role:
@@ -217,7 +229,7 @@ def configure(
217
229
  _print_success(f"Using existing ECR repository: [dim]{ecr_repository}[/dim]")
218
230
 
219
231
  # Handle dependency file selection with simplified logic
220
- final_requirements_file = _handle_requirements_file_display(requirements_file)
232
+ final_requirements_file = _handle_requirements_file_display(requirements_file, non_interactive)
221
233
 
222
234
  # Handle OAuth authorization configuration
223
235
  oauth_config = None
@@ -404,6 +416,8 @@ def launch(
404
416
  auto_update_on_conflict=auto_update_on_conflict,
405
417
  )
406
418
 
419
+ project_config = load_config(config_path)
420
+ agent_config = project_config.get_agent_config(agent)
407
421
  # Handle result based on mode
408
422
  if result.mode == "local":
409
423
  _print_success(f"Docker image built: {result.tag}")
@@ -422,6 +436,10 @@ def launch(
422
436
  elif result.mode == "codebuild":
423
437
  # Show deployment success panel
424
438
  agent_name = result.tag.split(":")[0].replace("bedrock_agentcore-", "")
439
+
440
+ # Get region from configuration
441
+ region = agent_config.aws.region if agent_config else "us-east-1"
442
+
425
443
  deploy_panel = (
426
444
  f"✅ [green]CodeBuild Deployment Successful![/green]\n\n"
427
445
  f"[bold]Agent Details:[/bold]\n"
@@ -437,18 +455,18 @@ def launch(
437
455
 
438
456
  # Add log information if we have agent_id
439
457
  if result.agent_id:
440
- from ...utils.runtime.logs import get_agent_log_paths, get_aws_tail_commands
441
-
442
458
  runtime_logs, otel_logs = get_agent_log_paths(result.agent_id)
443
459
  follow_cmd, since_cmd = get_aws_tail_commands(runtime_logs)
444
- deploy_panel += (
445
- f"\n\n📋 [cyan]CloudWatch Logs:[/cyan]\n"
446
- f" {runtime_logs}\n"
447
- f" {otel_logs}\n\n"
448
- f"💡 [dim]Tail logs with:[/dim]\n"
449
- f" {follow_cmd}\n"
450
- f" {since_cmd}"
451
- )
460
+ deploy_panel += f"\n\n📋 [cyan]CloudWatch Logs:[/cyan]\n {runtime_logs}\n {otel_logs}\n\n"
461
+ # Only show GenAI Observability Dashboard if OTEL is enabled
462
+ if agent_config and agent_config.aws.observability.enabled:
463
+ deploy_panel += (
464
+ f"🔍 [cyan]GenAI Observability Dashboard:[/cyan]\n"
465
+ f" {get_genai_observability_url(region)}\n\n"
466
+ f"⏱️ [dim]Note: Observability data may take up to 10 minutes to appear "
467
+ f"after first launch[/dim]\n\n"
468
+ )
469
+ deploy_panel += f"💡 [dim]Tail logs with:[/dim]\n {follow_cmd}\n {since_cmd}"
452
470
 
453
471
  console.print(
454
472
  Panel(
@@ -483,8 +501,6 @@ def launch(
483
501
  )
484
502
 
485
503
  if result.agent_id:
486
- from ...utils.runtime.logs import get_agent_log_paths, get_aws_tail_commands
487
-
488
504
  runtime_logs, otel_logs = get_agent_log_paths(result.agent_id)
489
505
  follow_cmd, since_cmd = get_aws_tail_commands(runtime_logs)
490
506
  deploy_panel += (
@@ -530,15 +546,17 @@ def _show_invoke_info_panel(agent_name: str, invoke_result=None, config=None):
530
546
  # Agent ARN
531
547
  if invoke_result and invoke_result.agent_arn:
532
548
  info_lines.append(f"ARN: [cyan]{invoke_result.agent_arn}[/cyan]")
533
- # CloudWatch logs (if we have config with agent_id)
549
+ # CloudWatch logs and GenAI Observability Dashboard (if we have config with agent_id)
534
550
  if config and hasattr(config, "bedrock_agentcore") and config.bedrock_agentcore.agent_id:
535
551
  try:
536
- from ...utils.runtime.logs import get_agent_log_paths, get_aws_tail_commands
537
-
538
552
  runtime_logs, _ = get_agent_log_paths(config.bedrock_agentcore.agent_id)
539
553
  follow_cmd, since_cmd = get_aws_tail_commands(runtime_logs)
540
554
  info_lines.append(f"Logs: {follow_cmd}")
541
555
  info_lines.append(f" {since_cmd}")
556
+
557
+ # Only show GenAI Observability Dashboard if OTEL is enabled
558
+ if config.aws.observability.enabled:
559
+ info_lines.append(f"GenAI Dashboard: {get_genai_observability_url(config.aws.region)}")
542
560
  except Exception:
543
561
  pass # nosec B110
544
562
  panel_content = "\n".join(info_lines) if info_lines else "Invoke information unavailable"
@@ -580,8 +598,6 @@ def invoke(
580
598
  config_path = Path.cwd() / ".bedrock_agentcore.yaml"
581
599
 
582
600
  try:
583
- from ...utils.runtime.config import load_config
584
-
585
601
  # Load project configuration to check if auth is configured
586
602
  project_config = load_config(config_path)
587
603
  config = project_config.get_agent_config(agent)
@@ -671,8 +687,6 @@ def invoke(
671
687
  agent_name = config.name if config else (agent or "unknown")
672
688
  except (NameError, AttributeError):
673
689
  try:
674
- from ...utils.runtime.config import load_config
675
-
676
690
  fallback_project_config = load_config(config_path)
677
691
  agent_config = fallback_project_config.get_agent_config(agent)
678
692
  agent_name = agent_config.name if agent_config else (agent or "unknown")
@@ -777,20 +791,24 @@ def status(
777
791
  agent_id = status_json.get("config", {}).get("agent_id")
778
792
  if agent_id:
779
793
  try:
780
- from ...utils.runtime.logs import get_agent_log_paths, get_aws_tail_commands
781
-
782
794
  endpoint_name = endpoint_data.get("name")
783
795
  runtime_logs, otel_logs = get_agent_log_paths(agent_id, endpoint_name)
784
796
  follow_cmd, since_cmd = get_aws_tail_commands(runtime_logs)
785
797
 
786
- panel_content += (
787
- f"📋 [cyan]CloudWatch Logs:[/cyan]\n"
788
- f" {runtime_logs}\n"
789
- f" {otel_logs}\n\n"
790
- f"💡 [dim]Tail logs with:[/dim]\n"
791
- f" {follow_cmd}\n"
792
- f" {since_cmd}\n\n"
793
- )
798
+ panel_content += f"📋 [cyan]CloudWatch Logs:[/cyan]\n {runtime_logs}\n {otel_logs}\n\n"
799
+
800
+ # Only show GenAI Observability Dashboard if OTEL is enabled
801
+ project_config = load_config(config_path)
802
+ agent_config = project_config.get_agent_config(agent)
803
+ if agent_config and agent_config.aws.observability.enabled:
804
+ panel_content += (
805
+ f"🔍 [cyan]GenAI Observability Dashboard:[/cyan]\n"
806
+ f" {get_genai_observability_url(status_json['config']['region'])}\n\n"
807
+ f"⏱️ [dim]Note: Observability data may take up to 10 minutes to appear "
808
+ f"after first launch[/dim]\n\n"
809
+ )
810
+
811
+ panel_content += f"💡 [dim]Tail logs with:[/dim]\n {follow_cmd}\n {since_cmd}\n\n"
794
812
  except Exception: # nosec B110
795
813
  # If log retrieval fails, continue without logs section
796
814
  pass
@@ -870,15 +888,13 @@ def destroy(
870
888
  dry_run: bool = typer.Option(
871
889
  False, "--dry-run", help="Show what would be destroyed without actually destroying anything"
872
890
  ),
873
- force: bool = typer.Option(
874
- False, "--force", help="Skip confirmation prompts and destroy immediately"
875
- ),
891
+ force: bool = typer.Option(False, "--force", help="Skip confirmation prompts and destroy immediately"),
876
892
  delete_ecr_repo: bool = typer.Option(
877
893
  False, "--delete-ecr-repo", help="Also delete the ECR repository after removing images"
878
894
  ),
879
895
  ) -> None:
880
896
  """Destroy Bedrock AgentCore resources.
881
-
897
+
882
898
  This command removes the following AWS resources for the specified agent:
883
899
  - Bedrock AgentCore endpoint (if exists)
884
900
  - Bedrock AgentCore agent runtime
@@ -887,26 +903,27 @@ def destroy(
887
903
  - IAM execution role (only if not used by other agents)
888
904
  - Agent deployment configuration
889
905
  - ECR repository (only if --delete-ecr-repo is specified)
890
-
906
+
891
907
  CAUTION: This action cannot be undone. Use --dry-run to preview changes first.
892
908
  """
893
909
  config_path = Path.cwd() / ".bedrock_agentcore.yaml"
894
910
 
895
911
  try:
896
- from ...utils.runtime.config import load_config
897
-
898
912
  # Load project configuration to get agent details
899
913
  project_config = load_config(config_path)
900
914
  agent_config = project_config.get_agent_config(agent)
901
-
915
+
902
916
  if not agent_config:
903
917
  _handle_error(f"Agent '{agent or 'default'}' not found in configuration")
904
-
918
+
905
919
  actual_agent_name = agent_config.name
906
920
 
907
921
  # Show what will be destroyed
908
922
  if dry_run:
909
- console.print(f"[cyan]🔍 Dry run: Preview of resources that would be destroyed for agent '{actual_agent_name}'[/cyan]\n")
923
+ console.print(
924
+ f"[cyan]🔍 Dry run: Preview of resources that would be destroyed for agent "
925
+ f"'{actual_agent_name}'[/cyan]\n"
926
+ )
910
927
  else:
911
928
  console.print(f"[yellow]⚠️ About to destroy resources for agent '{actual_agent_name}'[/yellow]\n")
912
929
 
@@ -956,7 +973,9 @@ def destroy(
956
973
  color = "cyan"
957
974
  else:
958
975
  if result.errors:
959
- console.print(f"[yellow]⚠️ Destruction completed with errors for agent '{result.agent_name}'[/yellow]\n")
976
+ console.print(
977
+ f"[yellow]⚠️ Destruction completed with errors for agent '{result.agent_name}'[/yellow]\n"
978
+ )
960
979
  title = "Destruction Results (With Errors)"
961
980
  color = "yellow"
962
981
  else:
@@ -987,7 +1006,7 @@ def destroy(
987
1006
  console.print(" • Run 'agentcore configure --entrypoint <file>' to set up a new agent")
988
1007
  console.print(" • Run 'agentcore launch' to deploy to Bedrock AgentCore")
989
1008
  elif dry_run:
990
- console.print(f"\n[dim]To actually destroy these resources, run:[/dim]")
1009
+ console.print("\n[dim]To actually destroy these resources, run:[/dim]")
991
1010
  destroy_cmd = f" agentcore destroy{f' --agent {actual_agent_name}' if agent else ''}"
992
1011
  if delete_ecr_repo:
993
1012
  destroy_cmd += " --delete-ecr-repo"
@@ -10,19 +10,25 @@ from ..common import _handle_error, _print_success, _prompt_with_default, consol
10
10
  class ConfigurationManager:
11
11
  """Manages interactive configuration prompts with existing configuration defaults."""
12
12
 
13
- def __init__(self, config_path: Path):
13
+ def __init__(self, config_path: Path, non_interactive: bool = False):
14
14
  """Initialize the ConfigPrompt with a configuration path.
15
15
 
16
16
  Args:
17
17
  config_path: Path to the configuration file
18
+ non_interactive: If True, use defaults without prompting
18
19
  """
19
20
  from ...utils.runtime.config import load_config_if_exists
20
21
 
21
22
  project_config = load_config_if_exists(config_path)
22
23
  self.existing_config = project_config.get_agent_config() if project_config else None
24
+ self.non_interactive = non_interactive
23
25
 
24
26
  def prompt_execution_role(self) -> Optional[str]:
25
27
  """Prompt for execution role. Returns role name/ARN or None for auto-creation."""
28
+ if self.non_interactive:
29
+ _print_success("Will auto-create execution role")
30
+ return None
31
+
26
32
  console.print("\n🔐 [cyan]Execution Role[/cyan]")
27
33
  console.print(
28
34
  "[dim]Press Enter to auto-create execution role, or provide execution role ARN/name to use existing[/dim]"
@@ -43,6 +49,10 @@ class ConfigurationManager:
43
49
 
44
50
  def prompt_ecr_repository(self) -> tuple[Optional[str], bool]:
45
51
  """Prompt for ECR repository. Returns (repository, auto_create_flag)."""
52
+ if self.non_interactive:
53
+ _print_success("Will auto-create ECR repository")
54
+ return None, True
55
+
46
56
  console.print("\n🏗️ [cyan]ECR Repository[/cyan]")
47
57
  console.print(
48
58
  "[dim]Press Enter to auto-create ECR repository, or provide ECR Repository URI to use existing[/dim]"
@@ -63,6 +73,10 @@ class ConfigurationManager:
63
73
 
64
74
  def prompt_oauth_config(self) -> Optional[dict]:
65
75
  """Prompt for OAuth configuration. Returns OAuth config dict or None."""
76
+ if self.non_interactive:
77
+ _print_success("Using default IAM authorization")
78
+ return None
79
+
66
80
  console.print("\n🔐 [cyan]Authorization Configuration[/cyan]")
67
81
  console.print("[dim]By default, Bedrock AgentCore uses IAM authorization.[/dim]")
68
82
 
@@ -176,6 +176,145 @@ class GatewayClient:
176
176
  self.logger.info("\n✅Target is ready")
177
177
  return target
178
178
 
179
+ def fix_iam_permissions(self, gateway: dict) -> None:
180
+ """Fix IAM role trust policy for the gateway.
181
+
182
+ :param gateway: the gateway dict containing roleArn
183
+ """
184
+ # Check for None gateway
185
+ if gateway is None:
186
+ return
187
+
188
+ # Check for missing roleArn
189
+ role_arn = gateway.get("roleArn")
190
+ if not role_arn:
191
+ return
192
+
193
+ sts = boto3.client("sts")
194
+ iam = boto3.client("iam")
195
+
196
+ account_id = sts.get_caller_identity()["Account"]
197
+ role_name = role_arn.split("/")[-1]
198
+
199
+ # Update trust policy
200
+ trust_policy = {
201
+ "Version": "2012-10-17",
202
+ "Statement": [
203
+ {
204
+ "Effect": "Allow",
205
+ "Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
206
+ "Action": "sts:AssumeRole",
207
+ "Condition": {
208
+ "StringEquals": {"aws:SourceAccount": account_id},
209
+ "ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{self.region}:{account_id}:*"},
210
+ },
211
+ }
212
+ ],
213
+ }
214
+
215
+ try:
216
+ iam.update_assume_role_policy(RoleName=role_name, PolicyDocument=json.dumps(trust_policy))
217
+
218
+ # Add Lambda permissions
219
+ iam.put_role_policy(
220
+ RoleName=role_name,
221
+ PolicyName="LambdaInvokePolicy",
222
+ PolicyDocument=json.dumps(
223
+ {
224
+ "Version": "2012-10-17",
225
+ "Statement": [
226
+ {
227
+ "Effect": "Allow",
228
+ "Action": ["lambda:InvokeFunction"],
229
+ "Resource": (
230
+ f"arn:aws:lambda:{self.region}:{account_id}:function:AgentCoreLambdaTestFunction"
231
+ ),
232
+ }
233
+ ],
234
+ }
235
+ ),
236
+ )
237
+ self.logger.info("✓ Fixed IAM permissions for Gateway")
238
+ except Exception as e:
239
+ self.logger.warning("⚠️ IAM role update failed: %s. Continuing with best effort.", str(e))
240
+
241
+ def cleanup_gateway(self, gateway_id: str, client_info: Optional[Dict] = None) -> None:
242
+ """Remove all resources associated with a gateway.
243
+
244
+ :param gateway_id: the ID of the gateway to clean up
245
+ :param client_info: optional Cognito client info for cleanup
246
+ """
247
+ self.logger.info("🧹 Cleaning up Gateway resources...")
248
+
249
+ gateway_client = self.client
250
+
251
+ # Step 1: List and delete all targets
252
+ self.logger.info(" • Finding targets for gateway: %s", gateway_id)
253
+
254
+ try:
255
+ response = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id)
256
+ # API returns targets in 'items' field
257
+ targets = response.get("items", [])
258
+ self.logger.info(" Found %s targets to delete", len(targets))
259
+
260
+ for target in targets:
261
+ target_id = target["targetId"]
262
+ self.logger.info(" • Deleting target: %s", target_id)
263
+ try:
264
+ gateway_client.delete_gateway_target(gatewayIdentifier=gateway_id, targetId=target_id)
265
+ self.logger.info(" ✓ Target deletion initiated: %s", target_id)
266
+ # Wait for deletion to complete
267
+ time.sleep(5)
268
+ except Exception as e:
269
+ self.logger.warning(" ⚠️ Error deleting target %s: %s", target_id, str(e))
270
+
271
+ # Verify all targets are deleted
272
+ self.logger.info(" • Verifying targets deletion...")
273
+ time.sleep(5) # Additional wait
274
+ verify_response = gateway_client.list_gateway_targets(gatewayIdentifier=gateway_id)
275
+ remaining_targets = verify_response.get("items", [])
276
+ if remaining_targets:
277
+ self.logger.warning(" ⚠️ %s targets still remain", len(remaining_targets))
278
+ else:
279
+ self.logger.info(" ✓ All targets deleted")
280
+
281
+ except Exception as e:
282
+ self.logger.warning(" ⚠️ Error managing targets: %s", str(e))
283
+
284
+ # Step 2: Delete the gateway
285
+ try:
286
+ self.logger.info(" • Deleting gateway: %s", gateway_id)
287
+ gateway_client.delete_gateway(gatewayIdentifier=gateway_id)
288
+ self.logger.info(" ✓ Gateway deleted: %s", gateway_id)
289
+ except Exception as e:
290
+ self.logger.warning(" ⚠️ Error deleting gateway: %s", str(e))
291
+
292
+ # Step 3: Delete Cognito resources if provided
293
+ if client_info and "user_pool_id" in client_info:
294
+ cognito = boto3.client("cognito-idp", region_name=self.region)
295
+ user_pool_id = client_info["user_pool_id"]
296
+
297
+ # Delete domain first
298
+ if "domain_prefix" in client_info:
299
+ domain_prefix = client_info["domain_prefix"]
300
+ self.logger.info(" • Deleting Cognito domain: %s", domain_prefix)
301
+ try:
302
+ cognito.delete_user_pool_domain(UserPoolId=user_pool_id, Domain=domain_prefix)
303
+ self.logger.info(" ✓ Cognito domain deleted")
304
+ time.sleep(5) # Wait for domain deletion
305
+ except Exception as e:
306
+ self.logger.warning(" ⚠️ Error deleting Cognito domain: %s", str(e))
307
+
308
+ # Now delete the user pool
309
+ self.logger.info(" • Deleting Cognito user pool: %s", user_pool_id)
310
+ try:
311
+ cognito.delete_user_pool(UserPoolId=user_pool_id)
312
+ self.logger.info(" ✓ Cognito user pool deleted")
313
+ except Exception as e:
314
+ self.logger.warning(" ⚠️ Error deleting Cognito user pool: %s", str(e))
315
+
316
+ self.logger.info("✅ Cleanup complete")
317
+
179
318
  def __handle_lambda_target_creation(self, role_arn: str) -> Dict[str, Any]:
180
319
  """Create a test lambda.
181
320