From 4cf83805a9cacbf14cad0e990e48e34f8a726420 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Thu, 23 Jul 2026 12:33:19 -0400 Subject: [PATCH 1/2] feat: discord webhooks to o11y vaults for alerting Signed-off-by: Devin Buhl --- .../scoped/discord/community/channels.tf | 10 +-- .../scoped/discord/community/providers.tf | 5 ++ .../scoped/discord/community/variables.tf | 5 ++ .../scoped/discord/community/webhooks.tf | 62 +++++++++++++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/tf/deployment/modules/scoped/discord/community/channels.tf b/tf/deployment/modules/scoped/discord/community/channels.tf index 3800fa436..d2aa5ab07 100644 --- a/tf/deployment/modules/scoped/discord/community/channels.tf +++ b/tf/deployment/modules/scoped/discord/community/channels.tf @@ -55,7 +55,7 @@ locals { "yucca_off_topic", "yucca_focus_topic", "yucca_alerts", - "yucca_alerts_testing", + "yucca_alerts_staging", "yucca_alerts_dev", ] # Leadership @@ -333,7 +333,7 @@ module "team_channels_write" { yucca_off_topic = discord_text_channel.yucca_off_topic.id yucca_focus_topic = discord_forum_channel.yucca_focus_topic.id yucca_alerts = discord_text_channel.yucca_alerts.id - yucca_alerts_testing = discord_text_channel.yucca_alerts_testing.id + yucca_alerts_staging = discord_text_channel.yucca_alerts_staging.id yucca_alerts_dev = discord_text_channel.yucca_alerts_dev.id yucca_voice = discord_voice_channel.yucca_voice.id } @@ -796,9 +796,9 @@ resource "discord_text_channel" "yucca_alerts" { } } -resource "discord_text_channel" "yucca_alerts_testing" { - name = "yucca-alerts-testing" - position = index(local.channel_order.yucca, "yucca_alerts_testing") +resource "discord_text_channel" "yucca_alerts_staging" { + name = "yucca-alerts-staging" + position = index(local.channel_order.yucca, "yucca_alerts_staging") server_id = discord_server.server.id category = discord_category_channel.yucca.id sync_perms_with_category = false diff --git a/tf/deployment/modules/scoped/discord/community/providers.tf b/tf/deployment/modules/scoped/discord/community/providers.tf index 1cb9da429..9042472c3 100644 --- a/tf/deployment/modules/scoped/discord/community/providers.tf +++ b/tf/deployment/modules/scoped/discord/community/providers.tf @@ -6,3 +6,8 @@ provider "onepassword" { url = var.op_connect_url token = var.op_connect_token } + +provider "onepassword" { + alias = "futo" + service_account_token = var.futo_op_service_account_token +} diff --git a/tf/deployment/modules/scoped/discord/community/variables.tf b/tf/deployment/modules/scoped/discord/community/variables.tf index 3b80fbc5c..efa380dda 100644 --- a/tf/deployment/modules/scoped/discord/community/variables.tf +++ b/tf/deployment/modules/scoped/discord/community/variables.tf @@ -7,6 +7,11 @@ variable "op_connect_token" { variable "discord_token" {} +variable "futo_op_service_account_token" { + description = "1Password service-account token for the FUTO account, used to write the webhook URLs into the yucca/o11y vaults" + sensitive = true +} + variable "discord_server_id" {} variable "env" {} diff --git a/tf/deployment/modules/scoped/discord/community/webhooks.tf b/tf/deployment/modules/scoped/discord/community/webhooks.tf index 5bb91ace7..58960398b 100644 --- a/tf/deployment/modules/scoped/discord/community/webhooks.tf +++ b/tf/deployment/modules/scoped/discord/community/webhooks.tf @@ -20,6 +20,68 @@ output "team_alerts_grafana_webhook_url" { sensitive = true } +resource "discord_webhook" "yucca_alerts_grafana" { + name = "Grafana" + channel_id = discord_text_channel.yucca_alerts.id + avatar_data_uri = local.image_data_uris["grafana_128_128.webp"] +} + +output "yucca_alerts_grafana_webhook_url" { + value = discord_webhook.yucca_alerts_grafana.url + sensitive = true +} + +resource "discord_webhook" "yucca_alerts_staging_grafana" { + name = "Grafana Staging" + channel_id = discord_text_channel.yucca_alerts_staging.id + avatar_data_uri = local.image_data_uris["grafana_128_128.webp"] +} + +output "yucca_alerts_staging_grafana_webhook_url" { + value = discord_webhook.yucca_alerts_staging_grafana.url + sensitive = true +} + +# Publish the yucca-alerts Grafana webhook URLs into the FUTO o11y vaults so the o11y +# stack can consume them. The o11y_tf_* vaults live in the FUTO 1Password account, which +# the default (immich Connect) provider cannot reach — hence provider = onepassword.futo. +# +# Gated to the prod apply only: this module also runs in dev against the test Discord +# server, and an ungated write would clobber the real webhook URLs with dev-server ones +# on every dev apply (the dev-env service account may not have o11y vault access either, +# which is also why the data sources are gated — they execute during every plan). +# yucca-alerts-staging lives on the same prod community server, so both vault items are +# written from the single prod apply. +data "onepassword_vault" "o11y_tf_prod" { + count = var.env == "prod" ? 1 : 0 + provider = onepassword.futo + name = "o11y_tf_prod" +} + +data "onepassword_vault" "o11y_tf_staging" { + count = var.env == "prod" ? 1 : 0 + provider = onepassword.futo + name = "o11y_tf_staging" +} + +resource "onepassword_item" "o11y_prod_grafana_discord_webhook" { + count = var.env == "prod" ? 1 : 0 + provider = onepassword.futo + vault = data.onepassword_vault.o11y_tf_prod[0].uuid + title = "GRAFANA_DISCORD_WEBHOOK" + category = "password" + password = discord_webhook.yucca_alerts_grafana.url +} + +resource "onepassword_item" "o11y_staging_grafana_discord_webhook" { + count = var.env == "prod" ? 1 : 0 + provider = onepassword.futo + vault = data.onepassword_vault.o11y_tf_staging[0].uuid + title = "GRAFANA_DISCORD_WEBHOOK" + category = "password" + password = discord_webhook.yucca_alerts_staging_grafana.url +} + resource "discord_webhook" "yucca_alerts_dev_grafana" { name = "Grafana Dev" channel_id = discord_text_channel.yucca_alerts_dev.id From 1a9f4e273c4e524d7806f636d17939c0aa000078 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Thu, 23 Jul 2026 13:01:11 -0400 Subject: [PATCH 2/2] fix: publish o11y grafana discord webhooks per env with for_each Signed-off-by: Devin Buhl --- .../scoped/discord/community/webhooks.tf | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/tf/deployment/modules/scoped/discord/community/webhooks.tf b/tf/deployment/modules/scoped/discord/community/webhooks.tf index 58960398b..43c3eb003 100644 --- a/tf/deployment/modules/scoped/discord/community/webhooks.tf +++ b/tf/deployment/modules/scoped/discord/community/webhooks.tf @@ -42,44 +42,37 @@ output "yucca_alerts_staging_grafana_webhook_url" { sensitive = true } -# Publish the yucca-alerts Grafana webhook URLs into the FUTO o11y vaults so the o11y -# stack can consume them. The o11y_tf_* vaults live in the FUTO 1Password account, which -# the default (immich Connect) provider cannot reach — hence provider = onepassword.futo. +# Publish each cluster's yucca-alerts Grafana webhook URL into its FUTO o11y vault +# (o11y_tf_) so the o11y stack can consume it. The o11y_tf_* vaults live in the +# FUTO 1Password account, which the default (immich Connect) provider cannot reach — +# hence provider = onepassword.futo. # -# Gated to the prod apply only: this module also runs in dev against the test Discord -# server, and an ungated write would clobber the real webhook URLs with dev-server ones -# on every dev apply (the dev-env service account may not have o11y vault access either, -# which is also why the data sources are gated — they execute during every plan). -# yucca-alerts-staging lives on the same prod community server, so both vault items are -# written from the single prod apply. -data "onepassword_vault" "o11y_tf_prod" { - count = var.env == "prod" ? 1 : 0 - provider = onepassword.futo - name = "o11y_tf_prod" -} - -data "onepassword_vault" "o11y_tf_staging" { - count = var.env == "prod" ? 1 : 0 - provider = onepassword.futo - name = "o11y_tf_staging" +# All three yucca-alerts channels live on the community server, which only the prod +# discord apply manages — the dev apply targets the test server and would clobber the +# vault items with test-server URLs on every run (its service account may not have o11y +# vault access either, and data sources execute during every plan). Hence the for_each +# collapses to an empty map outside prod. +locals { + o11y_grafana_discord_webhooks = { + prod = discord_webhook.yucca_alerts_grafana.url + staging = discord_webhook.yucca_alerts_staging_grafana.url + dev = discord_webhook.yucca_alerts_dev_grafana.url + } } -resource "onepassword_item" "o11y_prod_grafana_discord_webhook" { - count = var.env == "prod" ? 1 : 0 +data "onepassword_vault" "o11y_tf" { + for_each = { for env, url in local.o11y_grafana_discord_webhooks : env => url if var.env == "prod" } provider = onepassword.futo - vault = data.onepassword_vault.o11y_tf_prod[0].uuid - title = "GRAFANA_DISCORD_WEBHOOK" - category = "password" - password = discord_webhook.yucca_alerts_grafana.url + name = "o11y_tf_${each.key}" } -resource "onepassword_item" "o11y_staging_grafana_discord_webhook" { - count = var.env == "prod" ? 1 : 0 +resource "onepassword_item" "o11y_grafana_discord_webhook" { + for_each = { for env, url in local.o11y_grafana_discord_webhooks : env => url if var.env == "prod" } provider = onepassword.futo - vault = data.onepassword_vault.o11y_tf_staging[0].uuid + vault = data.onepassword_vault.o11y_tf[each.key].uuid title = "GRAFANA_DISCORD_WEBHOOK" category = "password" - password = discord_webhook.yucca_alerts_staging_grafana.url + password = each.value } resource "discord_webhook" "yucca_alerts_dev_grafana" {