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.
- bedrock_agentcore_starter_toolkit/cli/cli.py +1 -1
- bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +65 -46
- bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +15 -1
- bedrock_agentcore_starter_toolkit/operations/gateway/client.py +139 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/destroy.py +61 -56
- bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +1 -1
- bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +15 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/scripts/base_bedrock_translate.py +4 -3
- bedrock_agentcore_starter_toolkit/services/import_agent/utils.py +14 -0
- bedrock_agentcore_starter_toolkit/services/runtime.py +5 -1
- bedrock_agentcore_starter_toolkit/services/xray.py +161 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/logs.py +12 -0
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/Dockerfile.j2 +11 -25
- {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/METADATA +18 -2
- {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/RECORD +19 -18
- {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/WHEEL +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/entry_points.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.10.dist-info → bedrock_agentcore_starter_toolkit-0.1.12.dist-info}/licenses/LICENSE.txt +0 -0
- {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
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|