"""CLI entry point for interacting with the DynamoDB snapshot store.""" import json import logging from pathlib import Path from typing import Annotated import typer from config import DynamoConfig from dynamo_client.dynamo_client import DynamoClient from shared.schemas.dynamo import Snapshot LOGGER = logging.getLogger('dynamo_cli.cli') app: typer.Typer = typer.Typer(help='DynamoDB snapshot CLI for offboarding automation.') @app.callback() def callback( debug: Annotated[ bool, typer.Option('--debug/--no-debug', help='Show debug logs.') ] = False, ) -> None: """Configure logging for the DynamoDB CLI.""" level = logging.DEBUG if debug else logging.INFO logging.basicConfig( level=level, format='%(asctime)s %(name)s %(levelname)s: %(message)s' ) @app.command() def put_snapshot( ticket_id: Annotated[str, typer.Option('--ticket-id', help='Jira ticket key.')], payload_file: Annotated[ Path, typer.Option( '--payload-file', help='Path to a JSON file containing the snapshot payload.', exists=True, readable=True, ), ], dry_run: Annotated[bool, typer.Option('--dry-run/--no-dry-run')] = False, ) -> None: """Store a Phase 1 snapshot from a JSON payload file. The JSON file should contain the fields: email, full_name, last_working_day, and optionally auth0_matches, terraform_hits. """ try: cfg = DynamoConfig() except Exception as exc: LOGGER.error('Configuration error: %s', exc) raise typer.Exit(code=1) data = json.loads(payload_file.read_text()) data['ticket_id'] = ticket_id if dry_run: LOGGER.info('[Dry run] Would store snapshot for ticket %s.', ticket_id) print( json.dumps( {'ticket_id': ticket_id, 'stored': False, 'dry_run': True}, indent=2 ) ) return try: snapshot = Snapshot.model_validate(data) except Exception as exc: LOGGER.error('Invalid payload: %s', exc) raise typer.Exit(code=1) client = DynamoClient(table_name=cfg.dynamodb_table_name, region=cfg.aws_region) try: client.put_snapshot(snapshot, ttl_days=cfg.dynamodb_ttl_days) except Exception as exc: LOGGER.error('Failed to store snapshot: %s', exc) raise typer.Exit(code=1) print( json.dumps({'ticket_id': ticket_id, 'stored': True, 'dry_run': False}, indent=2) ) @app.command() def get_snapshot( ticket_id: Annotated[str, typer.Option('--ticket-id', help='Jira ticket key.')], ) -> None: """Retrieve a stored snapshot by ticket ID and print it as JSON.""" try: cfg = DynamoConfig() except Exception as exc: LOGGER.error('Configuration error: %s', exc) raise typer.Exit(code=1) client = DynamoClient(table_name=cfg.dynamodb_table_name, region=cfg.aws_region) try: snapshot = client.get_snapshot(ticket_id) except Exception as exc: LOGGER.error('Failed to retrieve snapshot: %s', exc) raise typer.Exit(code=1) if snapshot is None: print( json.dumps( {'ticket_id': ticket_id, 'found': False, 'snapshot': None}, indent=2 ) ) return print( json.dumps( {'ticket_id': ticket_id, 'found': True, 'snapshot': snapshot.model_dump()}, indent=2, ) ) @app.command() def delete_snapshot( ticket_id: Annotated[str, typer.Option('--ticket-id', help='Jira ticket key.')], dry_run: Annotated[bool, typer.Option('--dry-run/--no-dry-run')] = False, ) -> None: """Delete a snapshot by ticket ID.""" try: cfg = DynamoConfig() except Exception as exc: LOGGER.error('Configuration error: %s', exc) raise typer.Exit(code=1) if dry_run: LOGGER.info('[Dry run] Would delete snapshot for ticket %s.', ticket_id) print( json.dumps( {'ticket_id': ticket_id, 'deleted': False, 'dry_run': True}, indent=2 ) ) return client = DynamoClient(table_name=cfg.dynamodb_table_name, region=cfg.aws_region) try: client.delete_snapshot(ticket_id) except Exception as exc: LOGGER.error('Failed to delete snapshot: %s', exc) raise typer.Exit(code=1) print( json.dumps( {'ticket_id': ticket_id, 'deleted': True, 'dry_run': False}, indent=2 ) ) if __name__ == '__main__': app()