Skip to content

Commit deb99f2

Browse files
committed
fix: correct ECR API response format from imageDetails to imageIds
1 parent 0a23494 commit deb99f2

File tree

3 files changed

+70
-7
lines changed

3 files changed

+70
-7
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ app.run()
4545
agentcore configure --entrypoint my_agent.py
4646
agentcore launch # Ready to run on Bedrock AgentCore
4747
agentcore invoke '{"prompt": "tell me a fact"}'
48+
agentcore destroy
4849
```
4950

5051
**What you get with the Starter Toolkit:**
52+
5153
-**Keep your agent logic** - Works with any SDK-built agent
5254
-**Zero infrastructure management** - No servers, containers, or scaling concerns
5355
-**One-command deployment** - From local development to enterprise platform
@@ -60,6 +62,7 @@ Bedrock AgentCore Starter Toolkit is currently in public preview. APIs may chang
6062
## 🛠️ Deployment & Management Tools
6163

6264
**Simple Configuration**
65+
6366
```bash
6467
# Configure your agent for deployment
6568
agentcore configure --entrypoint my_agent.py --name my-production-agent
@@ -72,6 +75,7 @@ agentcore invoke '{"prompt": "Hello from Bedrock AgentCore!"}'
7275
```
7376

7477
**Enterprise Platform Services**
78+
7579
- 🚀 **Runtime** - Serverless deployment and scaling with fast cold starts
7680
- 🧠 **Memory** - Persistent knowledge with event and semantic memory
7781
- 🔗 **Gateway** - Transform existing APIs and Lambda functions into MCP tools

src/bedrock_agentcore_starter_toolkit/cli/runtime/commands.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,16 +752,20 @@ def destroy(
752752
force: bool = typer.Option(
753753
False, "--force", help="Skip confirmation prompts and destroy immediately"
754754
),
755+
delete_ecr_repo: bool = typer.Option(
756+
False, "--delete-ecr-repo", help="Also delete the ECR repository after removing images"
757+
),
755758
) -> None:
756759
"""Destroy Bedrock AgentCore resources.
757760
758761
This command removes the following AWS resources for the specified agent:
759762
- Bedrock AgentCore endpoint (if exists)
760763
- Bedrock AgentCore agent runtime
761-
- ECR images (latest tag only for safety)
764+
- ECR images (all images in the agent's repository)
762765
- CodeBuild project
763766
- IAM execution role (only if not used by other agents)
764767
- Agent deployment configuration
768+
- ECR repository (only if --delete-ecr-repo is specified)
765769
766770
CAUTION: This action cannot be undone. Use --dry-run to preview changes first.
767771
"""
@@ -805,6 +809,8 @@ def destroy(
805809
# Confirmation prompt (unless force or dry_run)
806810
if not dry_run and not force:
807811
console.print("[red]This will permanently delete AWS resources and cannot be undone![/red]")
812+
if delete_ecr_repo:
813+
console.print("[red]This includes deleting the ECR repository itself![/red]")
808814
response = typer.confirm(
809815
f"Are you sure you want to destroy the agent '{actual_agent_name}' and all its resources?"
810816
)
@@ -819,6 +825,7 @@ def destroy(
819825
agent_name=actual_agent_name,
820826
dry_run=dry_run,
821827
force=force,
828+
delete_ecr_repo=delete_ecr_repo,
822829
)
823830

824831
# Display results
@@ -860,7 +867,10 @@ def destroy(
860867
console.print(" • Run 'agentcore launch' to deploy to Bedrock AgentCore")
861868
elif dry_run:
862869
console.print(f"\n[dim]To actually destroy these resources, run:[/dim]")
863-
console.print(f" agentcore destroy{f' --agent {actual_agent_name}' if agent else ''}")
870+
destroy_cmd = f" agentcore destroy{f' --agent {actual_agent_name}' if agent else ''}"
871+
if delete_ecr_repo:
872+
destroy_cmd += " --delete-ecr-repo"
873+
console.print(destroy_cmd)
864874

865875
except FileNotFoundError:
866876
console.print("[red].bedrock_agentcore.yaml not found[/red]")

src/bedrock_agentcore_starter_toolkit/operations/runtime/destroy.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def destroy_bedrock_agentcore(
2121
agent_name: Optional[str] = None,
2222
dry_run: bool = False,
2323
force: bool = False,
24+
delete_ecr_repo: bool = False,
2425
) -> DestroyResult:
2526
"""Destroy Bedrock AgentCore resources.
2627
@@ -29,6 +30,7 @@ def destroy_bedrock_agentcore(
2930
agent_name: Name of the agent to destroy (default: use default agent)
3031
dry_run: If True, only show what would be destroyed without actually doing it
3132
force: If True, skip confirmation prompts
33+
delete_ecr_repo: If True, also delete the ECR repository after removing images
3234
3335
Returns:
3436
DestroyResult: Details of what was destroyed or would be destroyed
@@ -38,7 +40,8 @@ def destroy_bedrock_agentcore(
3840
ValueError: If agent is not found or not deployed
3941
RuntimeError: If destruction fails
4042
"""
41-
log.info("Starting destroy operation for agent: %s (dry_run=%s)", agent_name or "default", dry_run)
43+
log.info("Starting destroy operation for agent: %s (dry_run=%s, delete_ecr_repo=%s)",
44+
agent_name or "default", dry_run, delete_ecr_repo)
4245

4346
try:
4447
# Load configuration
@@ -65,8 +68,8 @@ def destroy_bedrock_agentcore(
6568
# 2. Destroy Bedrock AgentCore agent
6669
_destroy_agentcore_agent(session, agent_config, result, dry_run)
6770

68-
# 3. Remove ECR images (specific tags only)
69-
_destroy_ecr_images(session, agent_config, result, dry_run)
71+
# 3. Remove ECR images and optionally the repository
72+
_destroy_ecr_images(session, agent_config, result, dry_run, delete_ecr_repo)
7073

7174
# 4. Remove CodeBuild project
7275
_destroy_codebuild_project(session, agent_config, result, dry_run)
@@ -179,8 +182,9 @@ def _destroy_ecr_images(
179182
agent_config: BedrockAgentCoreAgentSchema,
180183
result: DestroyResult,
181184
dry_run: bool,
185+
delete_ecr_repo: bool = False,
182186
) -> None:
183-
"""Remove ECR images for this specific agent."""
187+
"""Remove ECR images and optionally the repository for this specific agent."""
184188
if not agent_config.aws.ecr_repository:
185189
result.warnings.append("No ECR repository configured, skipping image cleanup")
186190
return
@@ -204,7 +208,14 @@ def _destroy_ecr_images(
204208
# Fix: use correct response key 'imageIds' instead of 'imageDetails'
205209
all_images = response.get("imageIds", [])
206210
if not all_images:
207-
result.warnings.append(f"No images found in ECR repository: {repo_name}")
211+
if delete_ecr_repo:
212+
# Repository exists but is empty, we can delete it
213+
if dry_run:
214+
result.resources_removed.append(f"ECR repository: {repo_name} (empty, DRY RUN)")
215+
else:
216+
_delete_ecr_repository(ecr_client, repo_name, result)
217+
else:
218+
result.warnings.append(f"No images found in ECR repository: {repo_name}")
208219
return
209220

210221
if dry_run:
@@ -214,6 +225,8 @@ def _destroy_ecr_images(
214225
result.resources_removed.append(
215226
f"ECR images in repository {repo_name}: {tagged_count} tagged, {untagged_count} untagged (DRY RUN)"
216227
)
228+
if delete_ecr_repo:
229+
result.resources_removed.append(f"ECR repository: {repo_name} (DRY RUN)")
217230
return
218231

219232
# Prepare images for deletion - imageIds are already in the correct format
@@ -264,6 +277,12 @@ def _destroy_ecr_images(
264277
result.warnings.append(
265278
f"Some ECR images could not be deleted: {failed_count} out of {len(images_to_delete)} failed"
266279
)
280+
281+
# Delete the repository if requested and all images were deleted successfully
282+
if delete_ecr_repo and total_deleted == len(images_to_delete):
283+
_delete_ecr_repository(ecr_client, repo_name, result)
284+
elif delete_ecr_repo and total_deleted < len(images_to_delete):
285+
result.warnings.append(f"Cannot delete ECR repository {repo_name}: some images failed to delete")
267286
else:
268287
result.warnings.append(f"No valid image identifiers found in {repo_name}")
269288

@@ -282,6 +301,36 @@ def _destroy_ecr_images(
282301
log.warning("Error during ECR cleanup: %s", e)
283302

284303

304+
def _delete_ecr_repository(ecr_client, repo_name: str, result: DestroyResult) -> None:
305+
"""Delete an ECR repository after ensuring it's empty."""
306+
try:
307+
# Verify repository is empty before deletion
308+
response = ecr_client.list_images(repositoryName=repo_name)
309+
remaining_images = response.get("imageIds", [])
310+
311+
if remaining_images:
312+
result.warnings.append(f"Cannot delete ECR repository {repo_name}: repository is not empty")
313+
return
314+
315+
# Delete the empty repository
316+
ecr_client.delete_repository(repositoryName=repo_name)
317+
result.resources_removed.append(f"ECR repository: {repo_name}")
318+
log.info("Deleted ECR repository: %s", repo_name)
319+
320+
except ClientError as e:
321+
error_code = e.response["Error"]["Code"]
322+
if error_code == "RepositoryNotFoundException":
323+
result.warnings.append(f"ECR repository {repo_name} not found (may have been deleted already)")
324+
elif error_code == "RepositoryNotEmptyException":
325+
result.warnings.append(f"Cannot delete ECR repository {repo_name}: repository is not empty")
326+
else:
327+
result.warnings.append(f"Failed to delete ECR repository {repo_name}: {e}")
328+
log.warning("Failed to delete ECR repository: %s", e)
329+
except Exception as e:
330+
result.warnings.append(f"Error deleting ECR repository {repo_name}: {e}")
331+
log.warning("Error deleting ECR repository: %s", e)
332+
333+
285334
def _destroy_codebuild_project(
286335
session: boto3.Session,
287336
agent_config: BedrockAgentCoreAgentSchema,

0 commit comments

Comments
 (0)