diff --git a/tf/deployment/prod/global/freshdesk/main.tf b/tf/deployment/prod/global/freshdesk/main.tf index ab7ce9e9..c306fea5 100644 --- a/tf/deployment/prod/global/freshdesk/main.tf +++ b/tf/deployment/prod/global/freshdesk/main.tf @@ -53,12 +53,31 @@ resource "onepassword_item" "webhook_path" { password = random_password.webhook_path.result } +data "freshdesk_agents" "all" { + count = local.enabled ? 1 : 0 +} + +locals { + bot_agent_ids = local.enabled ? [ + for a in data.freshdesk_agents.all[0].agents : a.id if a.email == var.yucca_freshdesk_bot_email + ] : [] +} + # Bot-created tickets land here; the id reaches the bot Secret through the -# 1P item below. +# 1P item below. The bot agent is group-scoped (ticket_scope 2), so group +# membership IS its authorization to touch the tickets it creates — without +# it every note/reply 403s. resource "freshdesk_group" "support" { count = local.enabled ? 1 : 0 + agent_ids = local.bot_agent_ids name = "FUTO Cloud" description = "FUTO Backups support tickets, managed by yucca tf" + lifecycle { + precondition { + condition = !local.enabled || length(local.bot_agent_ids) == 1 + error_message = "yucca_freshdesk_bot_email must match exactly one Freshdesk agent — a memberless group strands the group-scoped bot with 403s." + } + } } resource "onepassword_item" "group_id" { diff --git a/tf/deployment/prod/global/freshdesk/variables.tf b/tf/deployment/prod/global/freshdesk/variables.tf index 32be379d..2c807865 100644 --- a/tf/deployment/prod/global/freshdesk/variables.tf +++ b/tf/deployment/prod/global/freshdesk/variables.tf @@ -69,3 +69,9 @@ variable "yucca_app_domain" { type = string default = "backups.futo.cloud" } + +variable "yucca_freshdesk_bot_email" { + description = "Email of the bot agent whose API key the futo-backups-bot uses; bound into the support group so its group-scoped key may touch the tickets it creates." + type = string + default = "freshdesk-bot@futo.org" +} diff --git a/tf/deployment/staging/global/freshdesk/main.tf b/tf/deployment/staging/global/freshdesk/main.tf index 1b26f996..b6449f0b 100644 --- a/tf/deployment/staging/global/freshdesk/main.tf +++ b/tf/deployment/staging/global/freshdesk/main.tf @@ -53,12 +53,31 @@ resource "onepassword_item" "webhook_path" { password = random_password.webhook_path.result } +data "freshdesk_agents" "all" { + count = local.enabled ? 1 : 0 +} + +locals { + bot_agent_ids = local.enabled ? [ + for a in data.freshdesk_agents.all[0].agents : a.id if a.email == var.yucca_freshdesk_bot_email + ] : [] +} + # Bot-created tickets land here; the id reaches the bot Secret through the -# 1P item below. +# 1P item below. The bot agent is group-scoped (ticket_scope 2), so group +# membership IS its authorization to touch the tickets it creates — without +# it every note/reply 403s. resource "freshdesk_group" "support" { count = local.enabled ? 1 : 0 + agent_ids = local.bot_agent_ids name = "FUTO Cloud (Staging)" description = "FUTO Backups support tickets from staging, managed by yucca tf" + lifecycle { + precondition { + condition = !local.enabled || length(local.bot_agent_ids) == 1 + error_message = "yucca_freshdesk_bot_email must match exactly one Freshdesk agent — a memberless group strands the group-scoped bot with 403s." + } + } } resource "onepassword_item" "group_id" { diff --git a/tf/deployment/staging/global/freshdesk/variables.tf b/tf/deployment/staging/global/freshdesk/variables.tf index 0bdc14d9..d54f831f 100644 --- a/tf/deployment/staging/global/freshdesk/variables.tf +++ b/tf/deployment/staging/global/freshdesk/variables.tf @@ -69,3 +69,9 @@ variable "yucca_app_domain" { type = string default = "staging.backups.futo.cloud" } + +variable "yucca_freshdesk_bot_email" { + description = "Email of the bot agent whose API key the futo-backups-bot uses; bound into the support group so its group-scoped key may touch the tickets it creates." + type = string + default = "freshdesk-bot@futo.org" +}