fix(api): retarget holidays per account and ship App Home changelog (PLAT-216)

This commit is contained in:
Adam Moussa 2026-09-21 14:55:47 -04:00
parent 5b272f9b4a
commit 2a3f30a9cb
No known key found for this signature in database
5 changed files with 55 additions and 12 deletions

View file

@ -6,6 +6,7 @@ terraform
tests
docs
*.md
!src/slack-bot/CHANGELOG.md
__pycache__
.pytest_cache
.mypy_cache

View file

@ -21,6 +21,7 @@ from botocore.exceptions import ClientError
PROD_ACCOUNT = "011934824531"
DEV_ACCOUNT = "710827005802"
HOLIDAY_SCHEDULER_ROLE = "afterhours-shift-manager-holiday-scheduler"
PREFIXES = ("holiday-activate-", "holiday-deactivate-")
_AT = re.compile(r"^at\((\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\)$")
_DATE = re.compile(r"(\d{4}-\d{2}-\d{2})")
@ -78,6 +79,10 @@ def action_and_date(name: str, existing_input: str) -> tuple[str, str]:
return action, date
def holiday_scheduler_role_arn(account: str) -> str:
return f"arn:aws:iam::{account}:role/tf-managed/{HOLIDAY_SCHEDULER_ROLE}"
def holiday_sqs_input(action: str, date: str) -> str:
return json.dumps({"event": "holiday", "action": action, "date": date})
@ -106,7 +111,8 @@ def main() -> int:
parser.add_argument("--queue-arn", required=True)
parser.add_argument(
"--role-arn",
default="arn:aws:iam::011934824531:role/tf-managed/afterhours-shift-manager-holiday-scheduler",
default="",
help="Scheduler execution role. Empty uses the tf-managed holiday role in the caller account.",
)
parser.add_argument("--execute", action="store_true")
args = parser.parse_args()
@ -115,6 +121,7 @@ def main() -> int:
if account not in {PROD_ACCOUNT, DEV_ACCOUNT}:
print("profile is not seahaven-prod or seahaven-dev", file=sys.stderr)
return 2
role_arn = args.role_arn.strip() or holiday_scheduler_role_arn(account)
client = _client(args.profile, args.region)
now = datetime.now(timezone.utc)
@ -154,7 +161,7 @@ def main() -> int:
ActionAfterCompletion=detail.get("ActionAfterCompletion", "DELETE"),
Target={
"Arn": args.queue_arn,
"RoleArn": args.role_arn,
"RoleArn": role_arn,
"Input": payload,
},
)

View file

@ -2168,21 +2168,33 @@ def _admin_holiday_list(respond, schedule):
@functools.lru_cache(maxsize=1)
def _changelog_text() -> str:
"""Read the CHANGELOG shipped in this function's package.
"""Read the CHANGELOG shipped next to this module.
Lazy (never at import) and tolerant of a missing file, so the App Home tab
degrades to "no What's New section" rather than erroring. The copy lives at
``$LAMBDA_TASK_ROOT/CHANGELOG.md`` (synced from the repo root).
degrades to "no What's New section" rather than erroring. Lambda zips and
the Fargate image both keep ``CHANGELOG.md`` beside ``app.py``.
``LAMBDA_TASK_ROOT`` remains a fallback for the zip layout.
"""
path = os.path.join(os.environ.get("LAMBDA_TASK_ROOT", "."), "CHANGELOG.md")
tried = []
for path in _changelog_paths():
tried.append(path)
try:
with open(path, encoding="utf-8") as fh:
return fh.read()
except OSError:
logger.warning("CHANGELOG.md not found at %s — App Home omits What's New", path)
continue
logger.warning("CHANGELOG.md not found at %s — App Home omits What's New", tried)
return ""
def _changelog_paths() -> list[str]:
paths = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "CHANGELOG.md")]
task_root = os.environ.get("LAMBDA_TASK_ROOT", "").strip()
if task_root:
paths.append(os.path.join(task_root, "CHANGELOG.md"))
return paths
_HOME_OVERVIEW_DAYS = 60

View file

@ -39,3 +39,14 @@ def test_action_and_date_from_schedule_name_when_input_empty():
def test_holiday_sqs_input_shape():
payload = mod.holiday_sqs_input("activate", "2026-07-04")
assert payload == '{"event": "holiday", "action": "activate", "date": "2026-07-04"}'
def test_holiday_scheduler_role_arn_follows_caller_account():
assert mod.holiday_scheduler_role_arn(mod.PROD_ACCOUNT) == (
"arn:aws:iam::011934824531:role/tf-managed/"
"afterhours-shift-manager-holiday-scheduler"
)
assert mod.holiday_scheduler_role_arn(mod.DEV_ACCOUNT) == (
"arn:aws:iam::710827005802:role/tf-managed/"
"afterhours-shift-manager-holiday-scheduler"
)

View file

@ -42,6 +42,18 @@ def test_publish_home_publishes_latest_entry(slackbot_app, client):
assert "v1.9.2" not in str(kwargs["view"]) # only the newest entry
def test_changelog_paths_include_package_copy(slackbot_app):
paths = slackbot_app._changelog_paths()
assert any(p.endswith("src/slack-bot/CHANGELOG.md") for p in paths)
def test_changelog_text_reads_package_copy(slackbot_app):
slackbot_app._changelog_text.cache_clear()
text = slackbot_app._changelog_text()
assert text
assert "## " in text
def test_publish_home_degrades_without_changelog(slackbot_app, client):
slackbot_app.publish_home(client, "U_BOB", "")
view = client.views_publish.call_args.kwargs["view"]