feat(infra): add reminder scheduler jobs

This commit is contained in:
2026-03-08 22:23:19 +04:00
parent 1b08da4591
commit fd0680c8ef
18 changed files with 474 additions and 59 deletions

View File

@@ -7,7 +7,7 @@ This directory contains baseline IaC for deploying the household bot platform on
- Artifact Registry Docker repository
- Cloud Run service: bot API (public webhook endpoint)
- Cloud Run service: mini app (public web UI)
- Cloud Scheduler job for reminder triggers
- Cloud Scheduler jobs for reminder triggers
- Runtime and scheduler service accounts with least-privilege bindings
- Secret Manager secrets (IDs only, secret values are added separately)
- Optional GitHub OIDC Workload Identity setup for deploy automation
@@ -16,7 +16,7 @@ This directory contains baseline IaC for deploying the household bot platform on
- `bot-api`: Telegram webhook + app API endpoints
- `mini-app`: front-end delivery
- `scheduler`: triggers `bot-api` internal reminder endpoint using OIDC token
- `scheduler`: triggers `bot-api` reminder endpoints using OIDC tokens
## Prerequisites
@@ -84,5 +84,5 @@ CI runs:
## Notes
- Scheduler job defaults to `paused = true` to prevent accidental sends before app logic is ready.
- Scheduler jobs default to `paused = true` and `dry_run = true` to prevent accidental sends before live reminder delivery is ready.
- Bot API is public to accept Telegram webhooks; scheduler endpoint should still verify app-level auth.

View File

@@ -12,6 +12,21 @@ locals {
artifact_location = coalesce(var.artifact_repository_location, var.region)
reminder_jobs = {
utilities = {
schedule = var.scheduler_utilities_cron
path = "/jobs/reminder/utilities"
}
rent-warning = {
schedule = var.scheduler_rent_warning_cron
path = "/jobs/reminder/rent-warning"
}
rent-due = {
schedule = var.scheduler_rent_due_cron
path = "/jobs/reminder/rent-due"
}
}
runtime_secret_ids = toset(compact([
var.telegram_webhook_secret_id,
var.scheduler_shared_secret_id,

View File

@@ -92,6 +92,9 @@ module "bot_api_service" {
},
var.bot_parser_model == null ? {} : {
PARSER_MODEL = var.bot_parser_model
},
{
SCHEDULER_OIDC_ALLOWED_EMAILS = google_service_account.scheduler_invoker.email
}
)
@@ -158,22 +161,27 @@ resource "google_service_account_iam_member" "scheduler_token_creator" {
}
resource "google_cloud_scheduler_job" "reminders" {
for_each = local.reminder_jobs
project = var.project_id
region = var.region
name = "${local.name_prefix}-reminders"
schedule = var.scheduler_cron
name = "${local.name_prefix}-${each.key}"
schedule = each.value.schedule
time_zone = var.scheduler_timezone
paused = var.scheduler_paused
http_target {
uri = "${module.bot_api_service.uri}${var.scheduler_path}"
http_method = var.scheduler_http_method
uri = "${module.bot_api_service.uri}${each.value.path}"
http_method = "POST"
headers = {
"Content-Type" = "application/json"
}
body = base64encode(var.scheduler_body_json)
body = base64encode(jsonencode({
dryRun = var.scheduler_dry_run
jobId = "${local.name_prefix}-${each.key}"
}))
oidc_token {
service_account_email = google_service_account.scheduler_invoker.email

View File

@@ -23,9 +23,9 @@ output "mini_app_service_url" {
value = module.mini_app_service.uri
}
output "scheduler_job_name" {
description = "Cloud Scheduler job for reminders"
value = google_cloud_scheduler_job.reminders.name
output "scheduler_job_names" {
description = "Cloud Scheduler jobs for reminders"
value = { for name, job in google_cloud_scheduler_job.reminders : name => job.name }
}
output "runtime_secret_ids" {

View File

@@ -13,9 +13,12 @@ bot_household_chat_id = "-1001234567890"
bot_purchase_topic_id = 777
bot_parser_model = "gpt-4.1-mini"
scheduler_cron = "0 9 * * *"
scheduler_timezone = "Asia/Tbilisi"
scheduler_paused = true
scheduler_utilities_cron = "0 9 4 * *"
scheduler_rent_warning_cron = "0 9 17 * *"
scheduler_rent_due_cron = "0 9 20 * *"
scheduler_timezone = "Asia/Tbilisi"
scheduler_paused = true
scheduler_dry_run = true
create_workload_identity = true
github_repository = "whekin/household-bot"

View File

@@ -118,35 +118,34 @@ variable "openai_api_key_secret_id" {
nullable = true
}
variable "scheduler_path" {
description = "Reminder endpoint path on bot API"
type = string
default = "/internal/scheduler/reminders"
}
variable "scheduler_http_method" {
description = "Scheduler HTTP method"
type = string
default = "POST"
}
variable "scheduler_cron" {
description = "Cron expression for reminder scheduler"
type = string
default = "0 9 * * *"
}
variable "scheduler_timezone" {
description = "Scheduler timezone"
type = string
default = "Asia/Tbilisi"
}
variable "scheduler_body_json" {
description = "JSON payload for scheduler requests"
variable "scheduler_utilities_cron" {
description = "Cron expression for the utilities reminder scheduler job"
type = string
default = "{\"kind\":\"monthly-reminder\"}"
default = "0 9 4 * *"
}
variable "scheduler_rent_warning_cron" {
description = "Cron expression for the rent warning scheduler job"
type = string
default = "0 9 17 * *"
}
variable "scheduler_rent_due_cron" {
description = "Cron expression for the rent due scheduler job"
type = string
default = "0 9 20 * *"
}
variable "scheduler_dry_run" {
description = "Whether scheduler jobs should invoke the bot in dry-run mode"
type = bool
default = true
}
variable "scheduler_paused" {