refactor(bot): replace reminder polling with scheduled dispatches

This commit is contained in:
2026-03-24 20:51:54 +04:00
parent a1acec5e60
commit 7f836eeee2
48 changed files with 6425 additions and 1557 deletions

View File

@@ -12,21 +12,6 @@ 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,
@@ -37,6 +22,7 @@ locals {
api_services = toset([
"artifactregistry.googleapis.com",
"cloudtasks.googleapis.com",
"cloudscheduler.googleapis.com",
"iam.googleapis.com",
"iamcredentials.googleapis.com",

View File

@@ -58,10 +58,18 @@ resource "google_service_account" "mini_runtime" {
display_name = "${local.name_prefix} mini runtime"
}
resource "google_service_account" "scheduler_invoker" {
project = var.project_id
account_id = "${var.environment}-scheduler"
display_name = "${local.name_prefix} scheduler invoker"
resource "google_cloud_tasks_queue" "scheduled_dispatches" {
project = var.project_id
location = var.region
name = var.scheduled_dispatch_queue_name
depends_on = [google_project_service.enabled]
}
resource "google_project_iam_member" "bot_runtime_cloud_tasks_enqueuer" {
project = var.project_id
role = "roles/cloudtasks.enqueuer"
member = "serviceAccount:${google_service_account.bot_runtime.email}"
}
resource "google_secret_manager_secret" "runtime" {
@@ -169,8 +177,12 @@ module "bot_api_service" {
var.bot_mini_app_url == null ? {} : {
MINI_APP_URL = var.bot_mini_app_url
},
{
SCHEDULER_OIDC_ALLOWED_EMAILS = google_service_account.scheduler_invoker.email
var.scheduled_dispatch_public_base_url == null ? {} : {
SCHEDULED_DISPATCH_PROVIDER = "gcp-cloud-tasks"
SCHEDULED_DISPATCH_PUBLIC_BASE_URL = var.scheduled_dispatch_public_base_url
GCP_SCHEDULED_DISPATCH_PROJECT_ID = var.project_id
GCP_SCHEDULED_DISPATCH_LOCATION = var.region
GCP_SCHEDULED_DISPATCH_QUEUE = google_cloud_tasks_queue.scheduled_dispatches.name
}
)
@@ -192,6 +204,8 @@ module "bot_api_service" {
depends_on = [
google_project_service.enabled,
google_cloud_tasks_queue.scheduled_dispatches,
google_project_iam_member.bot_runtime_cloud_tasks_enqueuer,
google_secret_manager_secret.runtime,
google_secret_manager_secret_iam_member.bot_runtime_access
]
@@ -218,54 +232,6 @@ module "mini_app_service" {
depends_on = [google_project_service.enabled]
}
resource "google_cloud_run_v2_service_iam_member" "scheduler_invoker" {
project = var.project_id
location = var.region
name = module.bot_api_service.name
role = "roles/run.invoker"
member = "serviceAccount:${google_service_account.scheduler_invoker.email}"
}
resource "google_service_account_iam_member" "scheduler_token_creator" {
service_account_id = google_service_account.scheduler_invoker.name
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:service-${data.google_project.current.number}@gcp-sa-cloudscheduler.iam.gserviceaccount.com"
}
resource "google_cloud_scheduler_job" "reminders" {
for_each = local.reminder_jobs
project = var.project_id
region = var.region
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}${each.value.path}"
http_method = "POST"
headers = {
"Content-Type" = "application/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
audience = module.bot_api_service.uri
}
}
depends_on = [
module.bot_api_service,
google_service_account_iam_member.scheduler_token_creator
]
}
resource "google_service_account" "github_deployer" {
count = var.create_workload_identity ? 1 : 0

View File

@@ -23,11 +23,6 @@ output "mini_app_service_url" {
value = module.mini_app_service.uri
}
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" {
description = "Secret Manager IDs expected by runtime"
value = sort([for secret in google_secret_manager_secret.runtime : secret.secret_id])

View File

@@ -28,12 +28,8 @@ alert_notification_emails = [
"alerts@example.com"
]
scheduler_utilities_cron = "0 9 * * *"
scheduler_rent_warning_cron = "0 9 * * *"
scheduler_rent_due_cron = "0 9 * * *"
scheduler_timezone = "Asia/Tbilisi"
scheduler_paused = true
scheduler_dry_run = true
scheduled_dispatch_queue_name = "scheduled-dispatches"
scheduled_dispatch_public_base_url = "https://api.example.com"
create_workload_identity = true
github_repository = "whekin/household-bot"

View File

@@ -165,40 +165,17 @@ variable "openai_api_key_secret_id" {
nullable = true
}
variable "scheduler_timezone" {
description = "Scheduler timezone"
variable "scheduled_dispatch_queue_name" {
description = "Cloud Tasks queue name for one-shot reminder dispatches"
type = string
default = "Asia/Tbilisi"
default = "scheduled-dispatches"
}
variable "scheduler_utilities_cron" {
description = "Cron expression for the utilities reminder scheduler job. Daily cadence is recommended because the app filters per household."
variable "scheduled_dispatch_public_base_url" {
description = "Public bot base URL used by Cloud Tasks callbacks for scheduled dispatches"
type = string
default = "0 9 * * *"
}
variable "scheduler_rent_warning_cron" {
description = "Cron expression for the rent warning scheduler job. Daily cadence is recommended because the app filters per household."
type = string
default = "0 9 * * *"
}
variable "scheduler_rent_due_cron" {
description = "Cron expression for the rent due scheduler job. Daily cadence is recommended because the app filters per household."
type = string
default = "0 9 * * *"
}
variable "scheduler_dry_run" {
description = "Whether scheduler jobs should invoke the bot in dry-run mode"
type = bool
default = true
}
variable "scheduler_paused" {
description = "Whether scheduler should be paused initially"
type = bool
default = true
default = null
nullable = true
}
variable "bot_min_instances" {