4040
4141
4242@click .group ()
43- @click .version_option (version = "1.0.0 " )
43+ @click .version_option (version = "0.4.2 " )
4444def cli ():
4545 """
4646 IDP CLI - Batch document processing for IDP Accelerator
@@ -301,6 +301,11 @@ def deploy(
301301 is_flag = True ,
302302 help = "Empty S3 buckets before deletion (required if buckets contain data)" ,
303303)
304+ @click .option (
305+ "--force-delete-all" ,
306+ is_flag = True ,
307+ help = "Force delete ALL remaining resources after CloudFormation deletion (S3 buckets, CloudWatch logs, DynamoDB tables). This cannot be undone." ,
308+ )
304309@click .option (
305310 "--wait/--no-wait" ,
306311 default = True ,
@@ -311,6 +316,7 @@ def delete(
311316 stack_name : str ,
312317 force : bool ,
313318 empty_buckets : bool ,
319+ force_delete_all : bool ,
314320 wait : bool ,
315321 region : Optional [str ],
316322):
@@ -321,6 +327,7 @@ def delete(
321327
322328 S3 buckets configured with RetainExceptOnCreate will be deleted if empty.
323329 Use --empty-buckets to automatically empty buckets before deletion.
330+ Use --force-delete-all to delete ALL remaining resources after CloudFormation deletion.
324331
325332 Examples:
326333
@@ -333,6 +340,9 @@ def delete(
333340 # Delete with automatic bucket emptying
334341 idp-cli delete --stack-name test-stack --empty-buckets --force
335342
343+ # Force delete ALL remaining resources (S3, logs, DynamoDB)
344+ idp-cli delete --stack-name test-stack --force-delete-all --force
345+
336346 # Delete without waiting for completion
337347 idp-cli delete --stack-name test-stack --force --no-wait
338348 """
@@ -350,7 +360,10 @@ def delete(
350360
351361 # Show warning with bucket details
352362 console .print ()
353- console .print ("[bold red]⚠️ WARNING: Stack Deletion[/bold red]" )
363+ if force_delete_all :
364+ console .print ("[bold red]⚠️ WARNING: FORCE DELETE ALL RESOURCES[/bold red]" )
365+ else :
366+ console .print ("[bold red]⚠️ WARNING: Stack Deletion[/bold red]" )
354367 console .print ("━" * 60 )
355368 console .print (f"Stack: [cyan]{ stack_name } [/cyan]" )
356369 console .print (f"Region: { region or 'default' } " )
@@ -372,12 +385,25 @@ def delete(
372385 else :
373386 console .print (f" • { logical_id } : [green]empty[/green]" )
374387
375- if has_data and not empty_buckets :
388+ if has_data and not empty_buckets and not force_delete_all :
376389 console .print ()
377390 console .print ("[bold red]⚠️ Buckets contain data![/bold red]" )
378391 console .print ("Deletion will FAIL unless you:" )
379392 console .print (" 1. Use --empty-buckets flag to auto-delete data, OR" )
380- console .print (" 2. Manually empty buckets first" )
393+ console .print (" 2. Use --force-delete-all to delete everything, OR" )
394+ console .print (" 3. Manually empty buckets first" )
395+
396+ if force_delete_all :
397+ console .print ()
398+ console .print ("[bold red]⚠️ FORCE DELETE ALL will remove:[/bold red]" )
399+ console .print (" • All S3 buckets (including LoggingBucket)" )
400+ console .print (" • All CloudWatch Log Groups" )
401+ console .print (" • All DynamoDB Tables" )
402+ console .print (" • Any other retained resources" )
403+ console .print ()
404+ console .print (
405+ "[bold yellow]This happens AFTER CloudFormation deletion completes[/bold yellow]"
406+ )
381407
382408 console .print ()
383409 console .print ("[bold red]This action cannot be undone.[/bold red]" )
@@ -386,15 +412,22 @@ def delete(
386412
387413 # Confirmation unless --force
388414 if not force :
389- response = click .confirm (
390- "Are you sure you want to delete this stack?" , default = False
391- )
415+ if force_delete_all :
416+ response = click .confirm (
417+ "Are you ABSOLUTELY sure you want to force delete ALL resources?" ,
418+ default = False ,
419+ )
420+ else :
421+ response = click .confirm (
422+ "Are you sure you want to delete this stack?" , default = False
423+ )
424+
392425 if not response :
393426 console .print ("[yellow]Deletion cancelled[/yellow]" )
394427 return
395428
396- # Double confirmation if --empty-buckets
397- if empty_buckets :
429+ # Double confirmation if --empty-buckets (and not force-delete-all)
430+ if empty_buckets and not force_delete_all :
398431 console .print ()
399432 console .print (
400433 "[bold red]⚠️ You are about to permanently delete all bucket data![/bold red]"
@@ -416,20 +449,11 @@ def delete(
416449 wait = wait ,
417450 )
418451
419- # Show results
452+ # Show CloudFormation deletion results
420453 if result .get ("success" ):
421454 console .print ("\n [green]✓ Stack deleted successfully![/green]" )
422455 console .print (f"Stack: { stack_name } " )
423456 console .print (f"Status: { result .get ('status' )} " )
424-
425- # Note about LoggingBucket
426- console .print ()
427- console .print (
428- "[bold]Note:[/bold] LoggingBucket (if exists) is retained by design."
429- )
430- console .print ("Delete it manually if no longer needed:" )
431- console .print (" [cyan]aws s3 rb s3://<logging-bucket-name> --force[/cyan]" )
432- console .print ()
433457 else :
434458 console .print ("\n [red]✗ Stack deletion failed![/red]" )
435459 console .print (f"Status: { result .get ('status' )} " )
@@ -438,10 +462,96 @@ def delete(
438462 if "bucket" in result .get ("error" , "" ).lower ():
439463 console .print ()
440464 console .print (
441- "[yellow]Tip: Try again with --empty-buckets flag[/yellow]"
465+ "[yellow]Tip: Try again with --empty-buckets or --force-delete-all flag[/yellow]"
442466 )
443467
444- sys .exit (1 )
468+ if not force_delete_all :
469+ sys .exit (1 )
470+ else :
471+ console .print ()
472+ console .print (
473+ "[yellow]Stack deletion failed, but continuing with force cleanup...[/yellow]"
474+ )
475+
476+ # Post-deletion cleanup if --force-delete-all
477+ cleanup_result = None
478+ if force_delete_all :
479+ console .print ()
480+ console .print ("[bold blue]━" * 60 + "[/bold blue]" )
481+ console .print (
482+ "[bold blue]Starting force cleanup of retained resources...[/bold blue]"
483+ )
484+ console .print ("[bold blue]━" * 60 + "[/bold blue]" )
485+
486+ try :
487+ # Use stack ID for deleted stacks (CloudFormation requires ID for deleted stacks)
488+ stack_identifier = result .get ("stack_id" , stack_name )
489+ cleanup_result = deployer .cleanup_retained_resources (stack_identifier )
490+
491+ # Show cleanup summary
492+ console .print ()
493+ console .print ("[bold green]✓ Cleanup phase complete![/bold green]" )
494+ console .print ()
495+
496+ total_deleted = (
497+ len (cleanup_result .get ("dynamodb_deleted" , []))
498+ + len (cleanup_result .get ("logs_deleted" , []))
499+ + len (cleanup_result .get ("buckets_deleted" , []))
500+ )
501+
502+ if total_deleted > 0 :
503+ console .print ("[bold]Resources deleted:[/bold]" )
504+
505+ if cleanup_result .get ("dynamodb_deleted" ):
506+ console .print (
507+ f" • DynamoDB Tables: { len (cleanup_result ['dynamodb_deleted' ])} "
508+ )
509+ for table in cleanup_result ["dynamodb_deleted" ]:
510+ console .print (f" - { table } " )
511+
512+ if cleanup_result .get ("logs_deleted" ):
513+ console .print (
514+ f" • CloudWatch Log Groups: { len (cleanup_result ['logs_deleted' ])} "
515+ )
516+ for log_group in cleanup_result ["logs_deleted" ]:
517+ console .print (f" - { log_group } " )
518+
519+ if cleanup_result .get ("buckets_deleted" ):
520+ console .print (
521+ f" • S3 Buckets: { len (cleanup_result ['buckets_deleted' ])} "
522+ )
523+ for bucket in cleanup_result ["buckets_deleted" ]:
524+ console .print (f" - { bucket } " )
525+
526+ if cleanup_result .get ("errors" ):
527+ console .print ()
528+ console .print (
529+ "[bold yellow]⚠️ Some resources could not be deleted:[/bold yellow]"
530+ )
531+ for error in cleanup_result ["errors" ]:
532+ console .print (f" • { error ['type' ]} : { error ['resource' ]} " )
533+ console .print (f" Error: { error ['error' ]} " )
534+
535+ console .print ()
536+
537+ except Exception as e :
538+ logger .error (f"Error during cleanup: { e } " , exc_info = True )
539+ console .print (f"\n [red]✗ Cleanup phase error: { e } [/red]" )
540+ console .print (
541+ "[yellow]Some resources may remain - check AWS Console[/yellow]"
542+ )
543+ else :
544+ # Standard deletion without force-delete-all
545+ if result .get ("success" ):
546+ console .print ()
547+ console .print (
548+ "[bold]Note:[/bold] LoggingBucket (if exists) is retained by design."
549+ )
550+ console .print ("Delete it manually if no longer needed:" )
551+ console .print (
552+ " [cyan]aws s3 rb s3://<logging-bucket-name> --force[/cyan]"
553+ )
554+ console .print ()
445555
446556 except Exception as e :
447557 logger .error (f"Error deleting stack: { e } " , exc_info = True )
0 commit comments