""" Local runner for secrets_rotator. Replicates what AWS Secrets Manager does before invoking the rotation Lambda: copies the current secret value into a new AWSPENDING version, then calls lambda_handler for each rotation step with that version's token. Usage: python run_local.py [--step createSecret|setSecret|testSecret|finishSecret] [--token UUID] If --step is omitted, all four steps are run in sequence. AWS credentials are resolved from the environment (AWS_PROFILE, AWS_ACCESS_KEY_ID, etc.). """ import argparse import logging import uuid from time import sleep logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s") from secrets_rotator import lambda_handler STEPS = ["createSecret", "setSecret", "testSecret", "finishSecret"] class LocalContext: aws_request_id = "local-run" def run_step(secret_arn: str, token: str, step: str) -> None: print(f"\n>>> {step} token={token}") event = {"SecretId": secret_arn, "ClientRequestToken": token, "Step": step} print(event) lambda_handler(event, LocalContext()) print(f"<<< done: {step}") def main() -> None: parser = argparse.ArgumentParser(description="Run secrets rotation locally against AWS.") parser.add_argument("secret_arn", help="Full ARN of the secret to rotate") parser.add_argument("--step", choices=STEPS, default=None, help="Single step to run (default: all four in sequence)") parser.add_argument("--token", default=None, help="ClientRequestToken (version ID). Auto-generated if omitted.") args = parser.parse_args() token = args.token or str(uuid.uuid4()) steps = [args.step] if args.step else STEPS for step in steps: sleep(3) run_step(args.secret_arn, token, step) if __name__ == "__main__": main()