Skip to content

Commit 7a1530b

Browse files
author
Bob Strahan
committed
Add CLI stack deletion functionality with safety features
1 parent 8b91163 commit 7a1530b

File tree

4 files changed

+444
-3
lines changed

4 files changed

+444
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ SPDX-License-Identifier: MIT-0
1717

1818
- **IDP CLI - Command Line Interface for Batch Document Processing**
1919
- Added CLI tool (`idp_cli/`) for programmatic batch document processing and stack management
20-
- **Key Features**: Deploy and update CloudFormation stacks, process documents from local directories or S3 URIs, live progress monitoring with rich terminal UI, download processing results locally, validate manifests before processing, generate manifests from directories with automatic baseline matching
20+
- **Key Features**: Deploy/update/delete CloudFormation stacks, process documents from local directories or S3 URIs, live progress monitoring with rich terminal UI, download processing results locally, validate manifests before processing, generate manifests from directories with automatic baseline matching
21+
- **Stack Lifecycle Management**: Complete stack management including deletion with safety confirmations, bucket analysis, and automatic cleanup options
2122
- **Evaluation Framework**: Workflow for accuracy testing including initial processing, manual validation, baseline creation, and automated evaluation with detailed metrics
2223
- **Analytics Integration**: Query aggregated results via Athena SQL or use Agent Analytics in Web UI for visual analysis
2324
- **Manifest Formats**: Support for CSV and JSON manifests with auto-generated document IDs from filenames and optional baseline references for evaluation
24-
- **Use Cases**: Rapid configuration iteration, large-scale batch processing, CI/CD integration, automated accuracy testing
25+
- **Use Cases**: Rapid configuration iteration, large-scale batch processing, CI/CD integration, automated accuracy testing, automated environment cleanup
2526
- **Documentation**: README with Quick Start, Commands Reference, Evaluation Workflow, and troubleshooting guides
2627

2728
### Changed

docs/idp-cli.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ https://github.com/user-attachments/assets/3d448a74-ba5b-4a4a-96ad-ec03ac0b4d7d
2424
- [Quick Start](#quick-start)
2525
- [Commands Reference](#commands-reference)
2626
- [deploy](#deploy)
27+
- [delete](#delete)
2728
- [run-inference](#run-inference)
2829
- [status](#status)
2930
- [download-results](#download-results)
@@ -149,6 +150,76 @@ idp-cli deploy \
149150

150151
---
151152

153+
### `delete`
154+
155+
Delete an IDP CloudFormation stack.
156+
157+
**⚠️ WARNING:** This permanently deletes all stack resources.
158+
159+
**Usage:**
160+
```bash
161+
idp-cli delete [OPTIONS]
162+
```
163+
164+
**Options:**
165+
- `--stack-name` (required): CloudFormation stack name
166+
- `--force`: Skip confirmation prompt
167+
- `--empty-buckets`: Empty S3 buckets before deletion (required if buckets contain data)
168+
- `--wait / --no-wait`: Wait for deletion to complete (default: wait)
169+
- `--region`: AWS region (optional)
170+
171+
**S3 Bucket Behavior:**
172+
- **LoggingBucket**: `DeletionPolicy: Retain` - Always kept
173+
- **All other buckets**: `DeletionPolicy: RetainExceptOnCreate` - Deleted if empty
174+
- CloudFormation can ONLY delete S3 buckets if they're empty
175+
- Use `--empty-buckets` to automatically empty buckets before deletion
176+
177+
**Examples:**
178+
179+
```bash
180+
# Interactive deletion with confirmation
181+
idp-cli delete --stack-name test-stack
182+
183+
# Automated deletion (CI/CD)
184+
idp-cli delete --stack-name test-stack --force
185+
186+
# Delete with automatic bucket emptying
187+
idp-cli delete --stack-name test-stack --empty-buckets --force
188+
189+
# Delete without waiting
190+
idp-cli delete --stack-name test-stack --force --no-wait
191+
```
192+
193+
**What you'll see:**
194+
```
195+
⚠️ WARNING: Stack Deletion
196+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
197+
Stack: test-stack
198+
Region: us-east-1
199+
200+
S3 Buckets:
201+
• InputBucket: 20 objects (45.3 MB)
202+
• OutputBucket: 20 objects (123.7 MB)
203+
• WorkingBucket: empty
204+
205+
⚠️ Buckets contain data!
206+
This action cannot be undone.
207+
208+
Are you sure you want to delete this stack? [y/N]: _
209+
```
210+
211+
**Use Cases:**
212+
- Cleanup test/development environments to avoid charges
213+
- CI/CD pipelines that provision and teardown stacks
214+
- Automated testing with temporary stack creation
215+
216+
**Note:** LoggingBucket is retained by design. To delete it manually:
217+
```bash
218+
aws s3 rb s3://<logging-bucket-name> --force
219+
```
220+
221+
---
222+
152223
### `run-inference`
153224

154225
Process a batch of documents.
@@ -914,4 +985,3 @@ For issues or questions:
914985
- Check CloudWatch logs for Lambda functions
915986
- Review AWS Console for resource status
916987
- Open an issue on GitHub
917-

idp_cli/idp_cli/cli.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,162 @@ def deploy(
283283
sys.exit(1)
284284

285285

286+
@cli.command()
287+
@click.option("--stack-name", required=True, help="CloudFormation stack name")
288+
@click.option("--force", is_flag=True, help="Skip confirmation prompt")
289+
@click.option(
290+
"--empty-buckets",
291+
is_flag=True,
292+
help="Empty S3 buckets before deletion (required if buckets contain data)",
293+
)
294+
@click.option(
295+
"--wait/--no-wait",
296+
default=True,
297+
help="Wait for deletion to complete (default: wait)",
298+
)
299+
@click.option("--region", help="AWS region (optional)")
300+
def delete(
301+
stack_name: str,
302+
force: bool,
303+
empty_buckets: bool,
304+
wait: bool,
305+
region: Optional[str],
306+
):
307+
"""
308+
Delete an IDP CloudFormation stack
309+
310+
⚠️ WARNING: This permanently deletes all stack resources.
311+
312+
S3 buckets configured with RetainExceptOnCreate will be deleted if empty.
313+
Use --empty-buckets to automatically empty buckets before deletion.
314+
315+
Examples:
316+
317+
# Interactive deletion with confirmation
318+
idp-cli delete --stack-name test-stack
319+
320+
# Automated deletion (skip confirmation)
321+
idp-cli delete --stack-name test-stack --force
322+
323+
# Delete with automatic bucket emptying
324+
idp-cli delete --stack-name test-stack --empty-buckets --force
325+
326+
# Delete without waiting for completion
327+
idp-cli delete --stack-name test-stack --force --no-wait
328+
"""
329+
try:
330+
deployer = StackDeployer(region=region)
331+
332+
# Check if stack exists
333+
if not deployer._stack_exists(stack_name):
334+
console.print(f"[red]✗ Stack '{stack_name}' does not exist[/red]")
335+
sys.exit(1)
336+
337+
# Get bucket information
338+
console.print(f"[bold blue]Analyzing stack: {stack_name}[/bold blue]")
339+
bucket_info = deployer.get_bucket_info(stack_name)
340+
341+
# Show warning with bucket details
342+
console.print()
343+
console.print("[bold red]⚠️ WARNING: Stack Deletion[/bold red]")
344+
console.print("━" * 60)
345+
console.print(f"Stack: [cyan]{stack_name}[/cyan]")
346+
console.print(f"Region: {region or 'default'}")
347+
348+
if bucket_info:
349+
console.print()
350+
console.print("[bold]S3 Buckets:[/bold]")
351+
has_data = False
352+
for bucket in bucket_info:
353+
obj_count = bucket.get("object_count", 0)
354+
size = bucket.get("size_display", "Unknown")
355+
logical_id = bucket.get("logical_id", "Unknown")
356+
357+
if obj_count > 0:
358+
has_data = True
359+
console.print(
360+
f" • {logical_id}: [yellow]{obj_count} objects ({size})[/yellow]"
361+
)
362+
else:
363+
console.print(f" • {logical_id}: [green]empty[/green]")
364+
365+
if has_data and not empty_buckets:
366+
console.print()
367+
console.print("[bold red]⚠️ Buckets contain data![/bold red]")
368+
console.print("Deletion will FAIL unless you:")
369+
console.print(" 1. Use --empty-buckets flag to auto-delete data, OR")
370+
console.print(" 2. Manually empty buckets first")
371+
372+
console.print()
373+
console.print("[bold red]This action cannot be undone.[/bold red]")
374+
console.print("━" * 60)
375+
console.print()
376+
377+
# Confirmation unless --force
378+
if not force:
379+
response = click.confirm(
380+
"Are you sure you want to delete this stack?", default=False
381+
)
382+
if not response:
383+
console.print("[yellow]Deletion cancelled[/yellow]")
384+
return
385+
386+
# Double confirmation if --empty-buckets
387+
if empty_buckets:
388+
console.print()
389+
console.print(
390+
"[bold red]⚠️ You are about to permanently delete all bucket data![/bold red]"
391+
)
392+
response = click.confirm(
393+
"Are you ABSOLUTELY sure you want to empty buckets and delete the stack?",
394+
default=False,
395+
)
396+
if not response:
397+
console.print("[yellow]Deletion cancelled[/yellow]")
398+
return
399+
400+
# Perform deletion
401+
console.print()
402+
with console.status("[bold red]Deleting stack..."):
403+
result = deployer.delete_stack(
404+
stack_name=stack_name,
405+
empty_buckets=empty_buckets,
406+
wait=wait,
407+
)
408+
409+
# Show results
410+
if result.get("success"):
411+
console.print("\n[green]✓ Stack deleted successfully![/green]")
412+
console.print(f"Stack: {stack_name}")
413+
console.print(f"Status: {result.get('status')}")
414+
415+
# Note about LoggingBucket
416+
console.print()
417+
console.print(
418+
"[bold]Note:[/bold] LoggingBucket (if exists) is retained by design."
419+
)
420+
console.print("Delete it manually if no longer needed:")
421+
console.print(" [cyan]aws s3 rb s3://<logging-bucket-name> --force[/cyan]")
422+
console.print()
423+
else:
424+
console.print("\n[red]✗ Stack deletion failed![/red]")
425+
console.print(f"Status: {result.get('status')}")
426+
console.print(f"Error: {result.get('error', 'Unknown')}")
427+
428+
if "bucket" in result.get("error", "").lower():
429+
console.print()
430+
console.print(
431+
"[yellow]Tip: Try again with --empty-buckets flag[/yellow]"
432+
)
433+
434+
sys.exit(1)
435+
436+
except Exception as e:
437+
logger.error(f"Error deleting stack: {e}", exc_info=True)
438+
console.print(f"[red]✗ Error: {e}[/red]")
439+
sys.exit(1)
440+
441+
286442
@cli.command()
287443
@click.option("--stack-name", required=True, help="CloudFormation stack name")
288444
@click.option(

0 commit comments

Comments
 (0)