Files
household-bot/infra/terraform/main.tf
whekin fcdd0f2aaf feat(bot): add /dashboard command and MINI_APP_URL config
- Add /dashboard command to BotFather registration alongside /app
- Add new MINI_APP_URL env var for dashboard URL (separate from CORS origins)
- Pass MINI_APP_URL and BOT_API_URL in CD workflow
- Update Terraform with new variable for future infrastructure deployments
2026-03-17 02:17:11 +04:00

322 lines
10 KiB
HCL

data "google_project" "current" {
project_id = var.project_id
}
resource "google_project_service" "enabled" {
for_each = local.api_services
project = var.project_id
service = each.value
disable_on_destroy = false
disable_dependent_services = false
}
resource "google_artifact_registry_repository" "containers" {
location = local.artifact_location
project = var.project_id
repository_id = var.artifact_repository_id
description = "Container images for household bot"
format = "DOCKER"
labels = local.common_labels
cleanup_policies {
id = "keep-last-10"
action = "KEEP"
most_recent_versions {
keep_count = 10
}
}
cleanup_policies {
id = "delete-stale"
action = "DELETE"
condition {
older_than = "1209600s" # 14 days
}
}
lifecycle {
ignore_changes = [
labels,
effective_labels,
terraform_labels,
]
}
depends_on = [google_project_service.enabled]
}
resource "google_service_account" "bot_runtime" {
project = var.project_id
account_id = "${var.environment}-bot-runtime"
display_name = "${local.name_prefix} bot runtime"
}
resource "google_service_account" "mini_runtime" {
project = var.project_id
account_id = "${var.environment}-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_secret_manager_secret" "runtime" {
for_each = var.manage_runtime_secrets ? local.runtime_secret_ids : toset([])
project = var.project_id
secret_id = each.value
replication {
auto {}
}
labels = local.common_labels
depends_on = [google_project_service.enabled]
}
resource "google_secret_manager_secret_iam_member" "bot_runtime_access" {
for_each = local.runtime_secret_ids
project = var.project_id
secret_id = each.value
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.bot_runtime.email}"
}
resource "google_secret_manager_secret_iam_member" "github_deployer_bot_token_access" {
count = var.create_workload_identity ? 1 : 0
project = var.project_id
secret_id = var.telegram_bot_token_secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.github_deployer[0].email}"
}
resource "google_secret_manager_secret_iam_member" "github_deployer_webhook_secret_access" {
count = var.create_workload_identity ? 1 : 0
project = var.project_id
secret_id = var.telegram_webhook_secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.github_deployer[0].email}"
}
resource "google_secret_manager_secret_iam_member" "github_deployer_webhook_secret_test_access" {
count = var.create_workload_identity ? 1 : 0
project = var.project_id
secret_id = "${var.telegram_webhook_secret_id}-test"
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.github_deployer[0].email}"
}
module "bot_api_service" {
source = "./modules/cloud_run_service"
project_id = var.project_id
region = var.region
name = "${local.name_prefix}-bot-api"
service_account_email = google_service_account.bot_runtime.email
image = var.bot_api_image
allow_unauthenticated = true
min_instance_count = var.bot_min_instances
max_instance_count = var.bot_max_instances
labels = local.common_labels
env = merge(
{
NODE_ENV = var.environment
DB_SCHEMA = var.db_schema
},
var.bot_purchase_parser_model == null ? {} : {
PURCHASE_PARSER_MODEL = var.bot_purchase_parser_model
},
var.bot_assistant_model == null ? {} : {
ASSISTANT_MODEL = var.bot_assistant_model
},
var.bot_topic_processor_model == null ? {} : {
TOPIC_PROCESSOR_MODEL = var.bot_topic_processor_model
},
var.bot_topic_processor_timeout_ms == null ? {} : {
TOPIC_PROCESSOR_TIMEOUT_MS = tostring(var.bot_topic_processor_timeout_ms)
},
var.bot_assistant_timeout_ms == null ? {} : {
ASSISTANT_TIMEOUT_MS = tostring(var.bot_assistant_timeout_ms)
},
var.bot_assistant_memory_max_turns == null ? {} : {
ASSISTANT_MEMORY_MAX_TURNS = tostring(var.bot_assistant_memory_max_turns)
},
var.bot_assistant_rate_limit_burst == null ? {} : {
ASSISTANT_RATE_LIMIT_BURST = tostring(var.bot_assistant_rate_limit_burst)
},
var.bot_assistant_rate_limit_burst_window_ms == null ? {} : {
ASSISTANT_RATE_LIMIT_BURST_WINDOW_MS = tostring(var.bot_assistant_rate_limit_burst_window_ms)
},
var.bot_assistant_rate_limit_rolling == null ? {} : {
ASSISTANT_RATE_LIMIT_ROLLING = tostring(var.bot_assistant_rate_limit_rolling)
},
var.bot_assistant_rate_limit_rolling_window_ms == null ? {} : {
ASSISTANT_RATE_LIMIT_ROLLING_WINDOW_MS = tostring(var.bot_assistant_rate_limit_rolling_window_ms)
},
length(var.bot_mini_app_allowed_origins) == 0 ? {} : {
MINI_APP_ALLOWED_ORIGINS = join(",", var.bot_mini_app_allowed_origins)
},
var.bot_mini_app_url == null ? {} : {
MINI_APP_URL = var.bot_mini_app_url
},
{
SCHEDULER_OIDC_ALLOWED_EMAILS = google_service_account.scheduler_invoker.email
}
)
secret_env = merge(
{
TELEGRAM_WEBHOOK_SECRET = var.telegram_webhook_secret_id
SCHEDULER_SHARED_SECRET = var.scheduler_shared_secret_id
},
var.database_url_secret_id == null ? {} : {
DATABASE_URL = var.database_url_secret_id
},
var.telegram_bot_token_secret_id == null ? {} : {
TELEGRAM_BOT_TOKEN = var.telegram_bot_token_secret_id
},
var.openai_api_key_secret_id == null ? {} : {
OPENAI_API_KEY = var.openai_api_key_secret_id
}
)
depends_on = [
google_project_service.enabled,
google_secret_manager_secret.runtime,
google_secret_manager_secret_iam_member.bot_runtime_access
]
}
module "mini_app_service" {
source = "./modules/cloud_run_service"
project_id = var.project_id
region = var.region
name = "${local.name_prefix}-mini-app"
service_account_email = google_service_account.mini_runtime.email
image = var.mini_app_image
allow_unauthenticated = true
min_instance_count = var.mini_min_instances
max_instance_count = var.mini_max_instances
labels = local.common_labels
env = {
NODE_ENV = var.environment
BOT_API_URL = module.bot_api_service.uri
}
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
project = var.project_id
account_id = "${var.environment}-${var.github_deploy_service_account_id}"
display_name = "${local.name_prefix} GitHub deployer"
}
resource "google_iam_workload_identity_pool" "github" {
count = var.create_workload_identity ? 1 : 0
project = var.project_id
workload_identity_pool_id = var.workload_identity_pool_id
display_name = "GitHub Actions Pool"
}
resource "google_iam_workload_identity_pool_provider" "github" {
count = var.create_workload_identity ? 1 : 0
project = var.project_id
workload_identity_pool_id = google_iam_workload_identity_pool.github[0].workload_identity_pool_id
workload_identity_pool_provider_id = var.workload_identity_provider_id
display_name = "GitHub Actions Provider"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.repository" = "assertion.repository"
}
attribute_condition = "assertion.repository == \"${var.github_repository}\""
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
resource "google_service_account_iam_member" "github_oidc" {
count = var.create_workload_identity ? 1 : 0
service_account_id = google_service_account.github_deployer[0].name
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github[0].name}/attribute.repository/${var.github_repository}"
}
resource "google_project_iam_member" "github_deployer_roles" {
for_each = var.create_workload_identity ? local.github_deploy_roles : toset([])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.github_deployer[0].email}"
}