#!/usr/bin/env python3 """Recreate future holiday-* EventBridge Scheduler schedules in prod. Reads outstanding holiday-activate-* / holiday-deactivate-* from mgmt and creates the same names in prod targeting the prod router ARN and scheduler role. Dry-run unless --execute. """ from __future__ import annotations import argparse import sys from datetime import datetime, timezone import boto3 from botocore.exceptions import ClientError SRC_ACCOUNT = "328440206208" DST_ACCOUNT = "011934824531" PROD_ROUTER_ARN = "arn:aws:lambda:us-east-1:011934824531:function:afterhours-holiday-router" PROD_ROLE_ARN = "arn:aws:iam::011934824531:role/tf-managed/afterhours-shift-manager-holiday-scheduler" PREFIXES = ("holiday-activate-", "holiday-deactivate-") def _client(profile: str, region: str): return boto3.Session(profile_name=profile, region_name=region).client("scheduler") def _account(profile: str) -> str: return boto3.Session(profile_name=profile).client("sts").get_caller_identity()["Account"] def _list_holiday(client): names = [] token = None while True: kwargs = {"GroupName": "default"} if token: kwargs["NextToken"] = token resp = client.list_schedules(**kwargs) for item in resp.get("Schedules", []): name = item.get("Name", "") if name.startswith(PREFIXES): names.append(name) token = resp.get("NextToken") if not token: return names def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--src-profile", required=True) parser.add_argument("--dst-profile", required=True) parser.add_argument("--region", default="us-east-1") parser.add_argument("--execute", action="store_true") args = parser.parse_args() if _account(args.src_profile) != SRC_ACCOUNT: print("src profile is not mgmt", file=sys.stderr) return 2 if _account(args.dst_profile) != DST_ACCOUNT: print("dst profile is not prod", file=sys.stderr) return 2 src = _client(args.src_profile, args.region) dst = _client(args.dst_profile, args.region) now = datetime.now(timezone.utc) created = 0 skipped = 0 for name in _list_holiday(src): detail = src.get_schedule(Name=name, GroupName="default") expr = detail.get("ScheduleExpression", "") tzname = detail.get("ScheduleExpressionTimezone", "America/New_York") at = detail.get("EndDate") or detail.get("StartDate") if at is not None and at < now: print(f"skip past {name}") skipped += 1 continue payload = { "Name": name, "GroupName": "default", "ScheduleExpression": expr, "ScheduleExpressionTimezone": tzname, "FlexibleTimeWindow": {"Mode": "OFF"}, "Target": { "Arn": PROD_ROUTER_ARN, "RoleArn": PROD_ROLE_ARN, "Input": detail.get("Target", {}).get("Input", ""), }, "ActionAfterCompletion": detail.get("ActionAfterCompletion", "DELETE"), } if detail.get("EndDate"): payload["EndDate"] = detail["EndDate"] print(f"would create {name} expr={expr} tz={tzname}") if not args.execute: continue try: dst.create_schedule(**payload) created += 1 except ClientError as exc: if exc.response["Error"]["Code"] == "ConflictException": print(f"exists {name}") else: raise print(f"created={created} skipped_past={skipped} execute={args.execute}") if not args.execute: print("dry-run; pass --execute to CreateSchedule") return 0 if __name__ == "__main__": raise SystemExit(main())