From e4fc5d7907b0aca996e9606d90ccf81c4c9aa9a8 Mon Sep 17 00:00:00 2001 From: xnoto Date: Fri, 21 Aug 2026 16:49:58 -0600 Subject: [PATCH 1/2] feat: wire dependabot pr alerting via managed caller workflow - gh-dependabot.tf also manages .github/workflows/dependabot-notify.yml in every active repo, calling the shared-workflows reusable workflow only when the pull_request actor is dependabot[bot] - distribute CLOUDFLARE_AUTH_CLIENT_* to all active repos (new active_github_repositories local; archived repos cannot receive secrets) - add GRAFANA_ALERTS_TOKEN with a documented placeholder until the operator-generated token is copied from the cluster secret --- AGENTS.md | 23 +++++++++++++++++++++++ README.md | 1 + gh-dependabot.tf | 33 +++++++++++++++++++++++++++++++++ main.tf | 33 +++++++++++++++++++-------------- 4 files changed, 76 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index dd92037..90f6b2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,6 +59,29 @@ downstream repositories. Pre-commit hook revisions are not covered by Dependabot: they are owned by the canonical config in `images/tfroot-runner/pre-commit-config.yaml`. +## Dependabot PR Alerting + +When Dependabot opens a PR, the managed caller workflow +(`.github/workflows/dependabot-notify.yml`, from `gh-dependabot.tf`) invokes +the `dependabot-notify` reusable workflow in `shared-workflows`, which posts a +synthetic alert to the cluster Grafana's Alertmanager API. Delivery goes to +Discord via the `GrafanaContactPoint`/`GrafanaNotificationPolicy` CRs in +`kustomize-cluster/workloads/grafana`. + +Two secrets make this work, both distributed here to all active repositories: + +- `CLOUDFLARE_AUTH_CLIENT_ID` / `CLOUDFLARE_AUTH_CLIENT_SECRET` — the existing + "GitHub Actions" Cloudflare Access service token, allowed by the path-scoped + Access app managed in `tfroot-cloudflare/cf-access-grafana.tf`. +- `GRAFANA_ALERTS_TOKEN` — a Grafana service account token. The account and + token are managed as code by the `GrafanaServiceAccount` CR in + `kustomize-cluster/workloads/grafana/serviceaccount-alerts.yaml`; the + operator writes the generated token to the `grafana-alerts-token` cluster + Secret (key `token`). Until that value is copied here into + `secrets/secrets.yaml` as `grafana_alerts_token`, the distributed secret is + a placeholder and alert delivery fails with a 401 in the caller repo's + Actions log. + ## Related Repositories - `images` - Contains tfroot-runner image and canonical pre-commit config diff --git a/README.md b/README.md index d2447b7..68c4a63 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ No modules. | [github_membership.admin](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/membership) | resource | | [github_repository.repositories](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository) | resource | | [github_repository_file.dependabot](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_file) | resource | +| [github_repository_file.dependabot_notify](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_file) | resource | | [github_team.admins](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team) | resource | | [github_team.developers](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team) | resource | | [github_team_membership.admins_xnoto](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_membership) | resource | diff --git a/gh-dependabot.tf b/gh-dependabot.tf index b88b011..71f1198 100644 --- a/gh-dependabot.tf +++ b/gh-dependabot.tf @@ -69,3 +69,36 @@ resource "github_repository_file" "dependabot" { commit_message = "chore: sync managed dependabot configuration" overwrite_on_create = true } + +locals { + # Caller for the dependabot-notify reusable workflow in shared-workflows. + # Fires only when Dependabot itself opens the PR; posts a synthetic alert + # to the cluster Grafana (see AGENTS.md, "Dependabot PR Alerting"). + dependabot_notify_workflow = <<-EOT + --- + # Managed by tfroot-github (gh-dependabot.tf); local edits are overwritten. + name: dependabot-notify + + on: + pull_request: + types: [opened, reopened] + + permissions: {} + + jobs: + notify: + if: github.actor == 'dependabot[bot]' + uses: makeitworkcloud/shared-workflows/.github/workflows/dependabot-notify.yml@main + secrets: inherit + EOT +} + +resource "github_repository_file" "dependabot_notify" { + for_each = local.dependabot_configs + + repository = github_repository.repositories[each.key].name + file = ".github/workflows/dependabot-notify.yml" + content = local.dependabot_notify_workflow + commit_message = "chore: sync managed dependabot notification workflow" + overwrite_on_create = true +} diff --git a/main.tf b/main.tf index 48479bf..1b9d4c9 100644 --- a/main.tf +++ b/main.tf @@ -24,6 +24,12 @@ locals { "ansible-site-cluster", "ansible-role-crc" ]) + # Non-archived repositories. Secrets cannot be written to archived repos, + # so org-wide secrets must target this list rather than github_repositories. + active_github_repositories = toset([ + for repo in local.github_repositories : repo + if !contains(local.archived_github_repositories, repo) + ]) secrets = { "onion_s3_bucket" = { name = "ONION_AWS_S3_BUCKET" @@ -76,22 +82,21 @@ locals { repositories = ["www"] } "cloudflare_auth_client_id" = { - name = "CLOUDFLARE_AUTH_CLIENT_ID" - value = data.sops_file.secret_vars.data["cloudflare_auth_client_id"] - repositories = [ - "images", - "kustomize-cluster", - "tfroot-github" - ] + name = "CLOUDFLARE_AUTH_CLIENT_ID" + value = data.sops_file.secret_vars.data["cloudflare_auth_client_id"] + repositories = local.active_github_repositories } "cloudflare_auth_client_secret" = { - name = "CLOUDFLARE_AUTH_CLIENT_SECRET" - value = data.sops_file.secret_vars.data["cloudflare_auth_client_secret"] - repositories = [ - "images", - "kustomize-cluster", - "tfroot-github" - ] + name = "CLOUDFLARE_AUTH_CLIENT_SECRET" + value = data.sops_file.secret_vars.data["cloudflare_auth_client_secret"] + repositories = local.active_github_repositories + } + "grafana_alerts_token" = { + name = "GRAFANA_ALERTS_TOKEN" + # Placeholder until the operator-generated token is copied from the + # cluster (see AGENTS.md, "Dependabot PR Alerting"). + value = lookup(data.sops_file.secret_vars.data, "grafana_alerts_token", "pending-grafana-service-account-token") + repositories = local.active_github_repositories } "ssh_private_key" = { name = "SSH_PRIVATE_KEY" From 012263266e7b52ca0a0b6b7e149ee539d33a6597 Mon Sep 17 00:00:00 2001 From: xnoto Date: Mon, 24 Aug 2026 09:31:23 -0600 Subject: [PATCH 2/2] chore: add grafana alerts service account token Operator-generated token from the GrafanaServiceAccount CR in kustomize-cluster (grafana-alerts-token secret), copied via kubectl + sops set. Replaces the placeholder distributed by #15. --- secrets/secrets.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/secrets/secrets.yaml b/secrets/secrets.yaml index b471f3c..6c80586 100644 --- a/secrets/secrets.yaml +++ b/secrets/secrets.yaml @@ -19,13 +19,14 @@ www_aws_access_key_id: ENC[AES256_GCM,data:jb1vtp/sjpYE+9/ZxIhnpezUCzM=,iv:u5wB2 www_aws_secret_access_key: ENC[AES256_GCM,data:x7YarHj9pKPiYHM04xkaU+fACjoOmM7eaMj1rU+iIYq2jYgY11X74g==,iv:NEY6uHKvIWnw7m8ym0cYVXDMvbmCu9iAZ5N9WGyZgYM=,tag:xL1Pgyidj4Nw73vKFeCziQ==,type:str] cloudflare_zone_id: ENC[AES256_GCM,data:6RjS806r2iMX9dfWBJeLIG54jRu3DhylNP7QOmrOVWc=,iv:picCNDWPduEMzqcm3gh7oRaGEs+4n2E/P91EGC/3iDs=,tag:9G/KG68JLu/rxI+fLpQQ7Q==,type:str] cloudflare_api_token: ENC[AES256_GCM,data:z5WDjwxFZ7VaufG17WciwbbOVQlaZP+OSGOkRCTJQJAPxZCv8pHc6Q==,iv:jiUky+4sIka3Kkw4JcteY2eoj8uzSwsMAREamseJ/Vo=,tag:ChGagBsNZKUVka6rlcB/FQ==,type:str] +grafana_alerts_token: ENC[AES256_GCM,data:DHykbs4ifLhkmbrh9DdBO0oyjEGCjdxoaWa6juRyWQIWbtNkedYgWQ8LorZ6HQ==,iv:/dvVgkKmSp5sY/UlH+mPpF5v7HRDjgG1ok3tAW81niY=,tag:UaZyqVrWQW997/6jxqjD/A==,type:str] sops: kms: - arn: arn:aws:kms:us-west-2:332355796717:key/0a45c0f6-71dc-4d54-ab33-9df4de1a9e91 + aws_profile: "" created_at: "2026-06-19T04:16:52Z" enc: AQICAHj1IggLFhM4nJnKEvmbEpk5E9RxZZoxpZYUW0taoyrz1AF/kg94UKFDzajWL4wI8KwkAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMmVOJEF56prSE5mcxAgEQgDt27+5rh3R0yvgpohI7YEEeZqxAJQiRdIomE22ohFcv2WGRfPXvbh43PlSwUAekZwmkLMM440d0Pu8zcA== - aws_profile: "" - lastmodified: "2026-06-19T04:13:08Z" - mac: ENC[AES256_GCM,data:l0RC91HKiFmaYRNLv07KJXwjAXm9HMvUUFCZmFMrJ1SoKn1ICoP+Lj64bIUCcKdnB5nrNNcyYvjgyhWssu0/wn5qMUH+9ZyWVDPhYj8GBGT7ZGuwbjNef8WV+WwyO8Qw4FDg6kesJeemuEwOHhXyaKOtZNb+kdWllFvjfsasZXs=,iv:kxf3aqVIJeZbUvzOtY6Uq4YpCkkStctezgy+91PpTJc=,tag:ScaLQ9zqj+Xuc5eCu+hxRQ==,type:str] + lastmodified: "2026-08-24T15:30:58Z" + mac: ENC[AES256_GCM,data:ntytB4EW4Oo3YaKoqi0BsOy6Ij4HRE6PJ+bEKXiS3pHpIww1OVFPAYWRJPosPpkn7AYX1iSF7p+w16FJs5vKqek2hnP4wWzDzWqbbFhJAurBEjwWilLi/Phxc5SArJsWghA8UMl4g6FkRBlYC6S4pnfsmX/t0bGtIDQasVZPLU0=,iv:xTKf3t/71gvLP/ygJLbiTtOTN/9ahs55hv6wE5LYB0o=,tag:X6oDOiXdp77v3dgIQD2gVw==,type:str] unencrypted_suffix: _unencrypted version: 3.12.2