From 6dc812e3e41f9f2830ea8e2e1208457fee3e847e Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Sat, 5 Sep 2026 11:22:13 -0600 Subject: [PATCH 1/6] refactor: relocate root catalog and secret definitions --- main.tf | 185 -------------------------------------------------------- 1 file changed, 185 deletions(-) delete mode 100644 main.tf diff --git a/main.tf b/main.tf deleted file mode 100644 index a322677..0000000 --- a/main.tf +++ /dev/null @@ -1,185 +0,0 @@ -data "sops_file" "secret_vars" { - source_file = "${path.module}/secrets/secrets.yaml" -} - -locals { - github_repositories = toset([ - ".github", - "ansible-project-libvirt", - "ansible-site-cluster", - "ansible-role-crc", - "cflan", - "hero-host-config", - "charts", - "agent-knowledge", - "channel-project", - "kustomize-cluster", - "images", - "shared-workflows", - "terraform-libvirt-domain", - "tfroot-aws", - "tfroot-cloudflare", - "tfroot-gcp", - "tfroot-github", - "tfroot-libvirt", - "tfroot-namecheap", - "tfroot-twilio", - "www" - ]) - archived_github_repositories = toset([ - "ansible-project-libvirt", - "ansible-site-cluster", - "ansible-role-crc" - ]) - private_github_repositories = toset([ - "agent-knowledge", - "channel-project", - "hero-host-config" - ]) - topics_by_repository = { - ".github" = ["community-health", "github", "org-profile"] - "agent-knowledge" = ["agents", "documentation", "knowledge-base", "opencode"] - "cflan" = ["cloudflare", "dns", "networking", "networkmanager", "python"] - "hero-host-config" = ["ansible", "cloudflare-zero-trust", "configuration-management", "node-exporter", "rhel", "systemd"] - "channel-project" = ["cloudflare", "dns", "opentofu", "video-streaming"] - "charts" = ["ghcr", "gitops", "helm", "helm-charts", "oci"] - "images" = ["buildah", "containerfiles", "custom-runners", "github-actions", "opentofu"] - "kustomize-cluster" = ["app-of-apps", "argocd", "gitops", "k3s", "ksops", "kustomize", "sops"] - "shared-workflows" = ["github-actions", "github-workflows", "opentofu", "reusable-workflows", "shared-workflows"] - "terraform-libvirt-domain" = ["cloud-init", "libvirt", "libvirt-provider", "terraform-module"] - "tfroot-aws" = ["aws-provider", "kms", "opentofu", "s3-backend", "sops", "tfstate"] - "tfroot-cloudflare" = ["cloudflare", "cloudflare-access", "cloudflare-tunnel", "opentofu", "s3-backend", "sops", "tfstate"] - "tfroot-gcp" = ["gcp", "gcs-backend", "kms", "opentofu", "sops", "tfstate", "workload-identity-federation"] - "tfroot-github" = ["github-actions", "opentofu", "s3-backend", "sops", "tfstate", "terraform-provider-github"] - "tfroot-libvirt" = ["cloud-init", "k3s", "libvirt", "libvirt-provider", "opentofu", "s3-backend", "sops", "tfstate"] - "tfroot-namecheap" = ["cloudflare", "dns", "domains", "namecheap", "opentofu"] - "tfroot-twilio" = ["opentofu", "s3-backend", "sms", "sops", "twilio"] - "www" = ["css", "html", "pwa", "s3", "static-site"] - } - # Public repositories remain on the relaxed profile only until their initial - # pull request has installed the required workflow and centrally managed files. - relaxed_branch_protection_github_repositories = toset([ - "agent-knowledge" - ]) - # Repositories where automation-created pull requests merge themselves once - # required checks pass. GitHub auto-merge is enabled only for these - # repositories (see gh-repositories.tf). kustomize-cluster receives the - # charts post-publish opencode-server pin pull request, which merges after - # its required `test` check passes. - auto_merge_github_repositories = toset([ - "kustomize-cluster" - ]) - active_github_repositories = toset([ - for repo in local.github_repositories : repo - if !contains(local.archived_github_repositories, repo) - ]) - required_status_checks_by_repository = { - ".github" = ["pre-commit"] - "cflan" = ["lint-and-test (3.10)", "lint-and-test (3.11)", "lint-and-test (3.12)", "lint-and-test (3.13)", "type-check"] - "charts" = ["test"] - "images" = ["checks"] - "kustomize-cluster" = ["test"] - "shared-workflows" = ["lint"] - "terraform-libvirt-domain" = ["test"] - "tfroot-aws" = ["opentofu / test", "opentofu / plan"] - "tfroot-cloudflare" = ["opentofu / test", "opentofu / plan"] - "tfroot-gcp" = ["opentofu / test", "opentofu / plan"] - "tfroot-github" = ["opentofu / test", "opentofu / plan"] - "tfroot-libvirt" = ["opentofu / test", "opentofu / plan"] - "tfroot-namecheap" = ["opentofu / test", "opentofu / plan"] - "tfroot-twilio" = ["opentofu / test", "opentofu / plan"] - "www" = ["static-checks"] - } - secrets = { - "onion_s3_bucket" = { - name = "ONION_AWS_S3_BUCKET" - value = data.sops_file.secret_vars.data["onion_s3_bucket"] - repositories = ["www"] - } - "onion_aws_region" = { - name = "ONION_AWS_REGION" - value = data.sops_file.secret_vars.data["onion_aws_region"] - repositories = ["www"] - } - "onion_access_key_id" = { - name = "ONION_AWS_ACCESS_KEY_ID" - value = data.sops_file.secret_vars.data["onion_aws_access_key_id"] - repositories = ["www"] - } - "onion_secret_access_key" = { - name = "ONION_AWS_SECRET_ACCESS_KEY" - value = data.sops_file.secret_vars.data["onion_aws_secret_access_key"] - repositories = ["www"] - } - "www_s3_bucket" = { - name = "AWS_S3_BUCKET" - value = data.sops_file.secret_vars.data["www_s3_bucket"] - repositories = ["www"] - } - "www_aws_region" = { - name = "AWS_REGION" - value = data.sops_file.secret_vars.data["www_aws_region"] - repositories = ["www"] - } - "www_access_key_id" = { - name = "AWS_ACCESS_KEY_ID" - value = data.sops_file.secret_vars.data["www_aws_access_key_id"] - repositories = ["www"] - } - "www_secret_access_key" = { - name = "AWS_SECRET_ACCESS_KEY" - value = data.sops_file.secret_vars.data["www_aws_secret_access_key"] - repositories = ["www"] - } - "cloudflare_zone_id" = { - name = "CLOUDFLARE_ZONE_ID" - value = data.sops_file.secret_vars.data["cloudflare_zone_id"] - repositories = ["www"] - } - "cloudflare_api_token" = { - name = "CLOUDFLARE_API_TOKEN" - value = data.sops_file.secret_vars.data["cloudflare_api_token"] - repositories = ["www", "tfroot-namecheap", "channel-project"] - } - "namecheap_api_key" = { - name = "NAMECHEAP_API_KEY" - value = data.sops_file.secret_vars.data["namecheap_api_key"] - repositories = ["tfroot-namecheap", "channel-project"] - } - "cloudflare_auth_client_id" = { - name = "CLOUDFLARE_AUTH_CLIENT_ID" - value = data.sops_file.secret_vars.data["cloudflare_auth_client_id"] - repositories = setsubtract(local.active_github_repositories, toset(["channel-project", "hero-host-config"])) - } - "cloudflare_auth_client_secret" = { - name = "CLOUDFLARE_AUTH_CLIENT_SECRET" - value = data.sops_file.secret_vars.data["cloudflare_auth_client_secret"] - repositories = setsubtract(local.active_github_repositories, toset(["channel-project", "hero-host-config"])) - } - "chart_updater_github_app_private_key" = { - name = "CHART_UPDATER_GITHUB_APP_PRIVATE_KEY" - value = data.sops_file.secret_vars.data["chart_updater_github_app_private_key"] - repositories = [ - "charts", - "kustomize-cluster", - "tfroot-aws", - "tfroot-cloudflare", - "tfroot-gcp", - "tfroot-github", - "tfroot-libvirt", - "tfroot-namecheap", - "tfroot-twilio", - ] - } - "ssh_private_key" = { - name = "SSH_PRIVATE_KEY" - value = data.sops_file.secret_vars.data["ssh_private_key"] - repositories = ["tfroot-libvirt"] - } - "ssh_known_hosts" = { - name = "SSH_KNOWN_HOSTS" - value = data.sops_file.secret_vars.data["ssh_known_hosts"] - repositories = ["tfroot-libvirt"] - } - } -} From 4cd5f707ee2505900664cd5aef66dca64112d77b Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Sat, 5 Sep 2026 11:24:27 -0600 Subject: [PATCH 2/6] refactor: centralize repository metadata for agent edits --- .terraform-docs.yml | 10 ++ README.md | 10 ++ gh-dependabot.tf | 48 +----- gh-protections.tf | 35 ++--- gh-repositories.tf | 10 +- imports.tf | 12 +- repositories.tf | 201 ++++++++++++++++++++++++++ secrets.tf | 98 +++++++++++++ templates/dependabot-notify.yml.tftpl | 15 ++ 9 files changed, 364 insertions(+), 75 deletions(-) create mode 100644 repositories.tf create mode 100644 secrets.tf create mode 100644 templates/dependabot-notify.yml.tftpl diff --git a/.terraform-docs.yml b/.terraform-docs.yml index 6b249bd..eabf352 100644 --- a/.terraform-docs.yml +++ b/.terraform-docs.yml @@ -5,6 +5,16 @@ output: mode: replace template: | + # tfroot-github + + OpenTofu root for Make IT Work Cloud GitHub organization infrastructure. + + ## Agent and operator contract + + - This root owns centralized repository policy, protections, Dependabot callers, and distributed Actions configuration. + - Read [`AGENTS.md`](AGENTS.md) before changing managed paths or workflow callers; downstream edits to generated files are overwritten. + - Pull-request CI plans are the validation authority. `main` is an environment-gated apply path; do not run local OpenTofu or secret-decryption operations from an agent session. + {{ .Content }} ## Operator guides diff --git a/README.md b/README.md index cbc2e9f..d6c70cb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,14 @@ +# tfroot-github + +OpenTofu root for Make IT Work Cloud GitHub organization infrastructure. + +## Agent and operator contract + +- This root owns centralized repository policy, protections, Dependabot callers, and distributed Actions configuration. +- Read [`AGENTS.md`](AGENTS.md) before changing managed paths or workflow callers; downstream edits to generated files are overwritten. +- Pull-request CI plans are the validation authority. `main` is an environment-gated apply path; do not run local OpenTofu or secret-decryption operations from an agent session. + ## Requirements | Name | Version | diff --git a/gh-dependabot.tf b/gh-dependabot.tf index 3c40848..78559b7 100644 --- a/gh-dependabot.tf +++ b/gh-dependabot.tf @@ -12,29 +12,13 @@ # - Kubernetes image tags in kustomize-cluster: no Dependabot ecosystem exists locals { - dependabot_ecosystems = { - ".github" = ["github-actions"] - "cflan" = ["github-actions", "pip"] - "hero-host-config" = ["github-actions"] - "images" = ["github-actions", "docker"] - "kustomize-cluster" = ["github-actions"] - "shared-workflows" = ["github-actions"] - "terraform-libvirt-domain" = ["github-actions", "opentofu"] - "tfroot-aws" = ["github-actions", "opentofu"] - "tfroot-cloudflare" = ["github-actions", "opentofu"] - "tfroot-github" = ["github-actions", "opentofu"] - "tfroot-libvirt" = ["github-actions", "opentofu"] - "tfroot-namecheap" = ["github-actions", "opentofu"] - "tfroot-twilio" = ["github-actions", "opentofu"] - "www" = ["github-actions"] - } dependabot_docker_directories = ["gh-cli", "tfroot-runner"] dependabot_configs = { - for repo, ecosystems in local.dependabot_ecosystems : repo => { + for name, repository in local.repositories : name => { version = 2 updates = concat( - [for ecosystem in ecosystems : { + [for ecosystem in repository.dependabot_ecosystems : { package-ecosystem = ecosystem directory = "/" schedule = { @@ -57,9 +41,10 @@ locals { patterns = ["*"] } } - } if contains(ecosystems, "docker")] + } if contains(repository.dependabot_ecosystems, "docker")] ) } + if length(repository.dependabot_ecosystems) > 0 } } @@ -85,28 +70,9 @@ resource "github_repository_file" "dependabot" { locals { # Caller for the dependabot-notify reusable workflow in shared-workflows. - # The reusable lives at _dependabot-notify.yml because this file's target - # path is managed in every repo, shared-workflows included; a reusable at - # the caller's path would be overwritten by this resource on every apply. - # Fires only when Dependabot itself opens the PR; posts a synthetic alert - # to the cluster Alertmanager (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 + # It is named _dependabot-notify.yml there because this root owns the caller + # path in every repository, including shared-workflows itself. + dependabot_notify_workflow = templatefile("${path.module}/templates/dependabot-notify.yml.tftpl", {}) } resource "github_repository_file" "dependabot_notify" { diff --git a/gh-protections.tf b/gh-protections.tf index a3bb78d..ba93072 100644 --- a/gh-protections.tf +++ b/gh-protections.tf @@ -4,18 +4,14 @@ data "github_app" "chart_updater" { slug = "makeitworkbot" } -# Every active public repository requires a pull request with its configured CI -# checks passing before merge, except repositories explicitly assigned the -# relaxed protection profile below. Private repositories are deliberately -# excluded: GitHub Free cannot enforce these protections there, and personal -# private repositories are outside the organization's review policy. The check -# map lives in main.tf because check-run names differ by repository and GitHub -# treats an unknown required check as pending. +# Strict protection requires pull requests and configured CI checks. Private +# repositories remain excluded because their GitHub plan does not enforce this +# organization policy through this resource. resource "github_branch_protection" "protections" { - for_each = toset([ - for repo in local.github_repositories : repo - if !contains(local.archived_github_repositories, repo) && !contains(local.private_github_repositories, repo) && !contains(local.relaxed_branch_protection_github_repositories, repo) - ]) + for_each = { + for name, repository in local.repositories : name => repository + if !repository.archived && !repository.private && repository.protection_profile == "strict" + } repository_id = github_repository.repositories[each.key].node_id pattern = "main" @@ -25,7 +21,7 @@ resource "github_branch_protection" "protections" { require_conversation_resolution = true required_status_checks { strict = true - contexts = local.required_status_checks_by_repository[each.key] + contexts = each.value.required_status_checks } # This block requires a pull request while retaining the solo-maintainer # workflow: zero approvals, no code-owner gate, and no bypass actors. @@ -55,15 +51,14 @@ resource "github_branch_protection" "protections" { ] } -# The relaxed profile is for public personal repositories: pull-request-only -# writes and basic branch integrity, while allowing any pull request to merge -# without a CI, approval, code-owner, or conversation-resolution gate. Private -# repositories are deliberately excluded from all branch-protection resources. +# The relaxed profile retains pull-request-only writes and basic branch +# integrity, but does not require CI, approvals, code owners, or resolved +# conversations. Private repositories remain excluded from this resource. resource "github_branch_protection" "relaxed_protections" { - for_each = toset([ - for repo in local.relaxed_branch_protection_github_repositories : repo - if !contains(local.archived_github_repositories, repo) && !contains(local.private_github_repositories, repo) - ]) + for_each = { + for name, repository in local.repositories : name => repository + if !repository.archived && !repository.private && repository.protection_profile == "relaxed" + } repository_id = github_repository.repositories[each.key].node_id pattern = "main" diff --git a/gh-repositories.tf b/gh-repositories.tf index fe74749..0eec9e5 100644 --- a/gh-repositories.tf +++ b/gh-repositories.tf @@ -1,19 +1,19 @@ resource "github_repository" "repositories" { - for_each = local.github_repositories + for_each = local.repositories name = each.key - archived = contains(local.archived_github_repositories, each.key) - visibility = contains(local.private_github_repositories, each.key) ? "private" : var.github_visibility + archived = each.value.archived + visibility = each.value.private ? "private" : var.github_visibility auto_init = true allow_squash_merge = true allow_merge_commit = true allow_rebase_merge = false # Auto-merge stays off except for repositories whose automation-created pull # requests are expected to merge themselves once required checks pass. - allow_auto_merge = contains(local.auto_merge_github_repositories, each.key) + allow_auto_merge = each.value.allow_auto_merge delete_branch_on_merge = true squash_merge_commit_title = "PR_TITLE" squash_merge_commit_message = "PR_BODY" - topics = contains(local.active_github_repositories, each.key) ? local.topics_by_repository[each.key] : null + topics = each.value.archived ? null : each.value.topics lifecycle { ignore_changes = [ description, diff --git a/imports.tf b/imports.tf index e6c40df..d38f3b7 100644 --- a/imports.tf +++ b/imports.tf @@ -8,9 +8,9 @@ import { id = "channel-project" } -# These files were seeded through tfroot-twilio PR #1 because its temporary -# protection rejects direct file creation. Import them before central management -# so the first reconciled apply does not attempt another protected direct write. +# These files were seeded through pull requests because protected branches +# reject direct creation. Keep the imports until a reviewed no-op plan confirms +# state ownership and central management of the matching live file. import { to = github_repository_file.dependabot["tfroot-twilio"] id = "tfroot-twilio:.github/dependabot.yml:" @@ -21,12 +21,6 @@ import { id = "tfroot-twilio:.github/workflows/dependabot-notify.yml:" } -# The kustomize-cluster dependabot-notify workflow was seeded through the -# pull-request path before this resource instance existed in state; the -# direct write in apply run 189 was rejected by the destination's required -# pull-request protection. The live file content matches the managed -# template exactly, so import it before central management, mirroring the -# tfroot-twilio adoption above. import { to = github_repository_file.dependabot_notify["kustomize-cluster"] id = "kustomize-cluster:.github/workflows/dependabot-notify.yml:" diff --git a/repositories.tf b/repositories.tf new file mode 100644 index 0000000..35018fd --- /dev/null +++ b/repositories.tf @@ -0,0 +1,201 @@ +locals { + # This catalog is the single source of truth for centrally managed repository + # metadata. Resource files derive their collections from it; do not add a + # repository-specific policy list elsewhere. + repositories = { + ".github" = { + archived = false + private = false + topics = ["community-health", "github", "org-profile"] + protection_profile = "strict" + required_status_checks = ["pre-commit"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false + } + "agent-knowledge" = { + archived = false + private = true + topics = ["agents", "documentation", "knowledge-base", "opencode"] + protection_profile = "relaxed" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false + } + "ansible-project-libvirt" = { + archived = true + private = false + topics = [] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false + } + "ansible-role-crc" = { + archived = true + private = false + topics = [] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false + } + "ansible-site-cluster" = { + archived = true + private = false + topics = [] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false + } + "cflan" = { + archived = false + private = false + topics = ["cloudflare", "dns", "networking", "networkmanager", "python"] + protection_profile = "strict" + required_status_checks = ["lint-and-test (3.10)", "lint-and-test (3.11)", "lint-and-test (3.12)", "lint-and-test (3.13)", "type-check"] + dependabot_ecosystems = ["github-actions", "pip"] + allow_auto_merge = false + } + "channel-project" = { + archived = false + private = true + topics = ["cloudflare", "dns", "opentofu", "video-streaming"] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false + } + "charts" = { + archived = false + private = false + topics = ["ghcr", "gitops", "helm", "helm-charts", "oci"] + protection_profile = "strict" + required_status_checks = ["test"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false + } + "hero-host-config" = { + archived = false + private = true + topics = ["ansible", "cloudflare-zero-trust", "configuration-management", "node-exporter", "rhel", "systemd"] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false + } + "images" = { + archived = false + private = false + topics = ["buildah", "containerfiles", "custom-runners", "github-actions", "opentofu"] + protection_profile = "strict" + required_status_checks = ["checks"] + dependabot_ecosystems = ["github-actions", "docker"] + allow_auto_merge = false + } + "kustomize-cluster" = { + archived = false + private = false + topics = ["app-of-apps", "argocd", "gitops", "k3s", "ksops", "kustomize", "sops"] + protection_profile = "strict" + required_status_checks = ["test"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = true + } + "shared-workflows" = { + archived = false + private = false + topics = ["github-actions", "github-workflows", "opentofu", "reusable-workflows", "shared-workflows"] + protection_profile = "strict" + required_status_checks = ["lint"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false + } + "terraform-libvirt-domain" = { + archived = false + private = false + topics = ["cloud-init", "libvirt", "libvirt-provider", "terraform-module"] + protection_profile = "strict" + required_status_checks = ["test"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false + } + "tfroot-aws" = { + archived = false + private = false + topics = ["aws-provider", "kms", "opentofu", "s3-backend", "sops", "tfstate"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false + } + "tfroot-cloudflare" = { + archived = false + private = false + topics = ["cloudflare", "cloudflare-access", "cloudflare-tunnel", "opentofu", "s3-backend", "sops", "tfstate"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false + } + "tfroot-gcp" = { + archived = false + private = false + topics = ["gcp", "gcs-backend", "kms", "opentofu", "s3-backend", "sops", "tfstate", "workload-identity-federation"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false + } + "tfroot-github" = { + archived = false + private = false + topics = ["github-actions", "opentofu", "s3-backend", "sops", "tfstate", "terraform-provider-github"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false + } + "tfroot-libvirt" = { + archived = false + private = false + topics = ["cloud-init", "k3s", "libvirt", "libvirt-provider", "opentofu", "s3-backend", "sops", "tfstate"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false + } + "tfroot-namecheap" = { + archived = false + private = false + topics = ["cloudflare", "dns", "domains", "namecheap", "opentofu"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false + } + "tfroot-twilio" = { + archived = false + private = false + topics = ["opentofu", "s3-backend", "sms", "sops", "twilio"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false + } + "www" = { + archived = false + private = false + topics = ["css", "html", "pwa", "s3", "static-site"] + protection_profile = "strict" + required_status_checks = ["static-checks"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false + } + } + + active_github_repositories = toset([ + for name, repository in local.repositories : name + if !repository.archived + ]) +} diff --git a/secrets.tf b/secrets.tf new file mode 100644 index 0000000..8b10f82 --- /dev/null +++ b/secrets.tf @@ -0,0 +1,98 @@ +data "sops_file" "secret_vars" { + source_file = "${path.module}/secrets/secrets.yaml" +} + +locals { + secrets = { + "onion_s3_bucket" = { + name = "ONION_AWS_S3_BUCKET" + value = data.sops_file.secret_vars.data["onion_s3_bucket"] + repositories = ["www"] + } + "onion_aws_region" = { + name = "ONION_AWS_REGION" + value = data.sops_file.secret_vars.data["onion_aws_region"] + repositories = ["www"] + } + "onion_access_key_id" = { + name = "ONION_AWS_ACCESS_KEY_ID" + value = data.sops_file.secret_vars.data["onion_aws_access_key_id"] + repositories = ["www"] + } + "onion_secret_access_key" = { + name = "ONION_AWS_SECRET_ACCESS_KEY" + value = data.sops_file.secret_vars.data["onion_aws_secret_access_key"] + repositories = ["www"] + } + "www_s3_bucket" = { + name = "AWS_S3_BUCKET" + value = data.sops_file.secret_vars.data["www_s3_bucket"] + repositories = ["www"] + } + "www_aws_region" = { + name = "AWS_REGION" + value = data.sops_file.secret_vars.data["www_aws_region"] + repositories = ["www"] + } + "www_access_key_id" = { + name = "AWS_ACCESS_KEY_ID" + value = data.sops_file.secret_vars.data["www_aws_access_key_id"] + repositories = ["www"] + } + "www_secret_access_key" = { + name = "AWS_SECRET_ACCESS_KEY" + value = data.sops_file.secret_vars.data["www_secret_access_key"] + repositories = ["www"] + } + "cloudflare_zone_id" = { + name = "CLOUDFLARE_ZONE_ID" + value = data.sops_file.secret_vars.data["cloudflare_zone_id"] + repositories = ["www"] + } + "cloudflare_api_token" = { + name = "CLOUDFLARE_API_TOKEN" + value = data.sops_file.secret_vars.data["cloudflare_api_token"] + repositories = ["www", "tfroot-namecheap", "channel-project"] + } + "namecheap_api_key" = { + name = "NAMECHEAP_API_KEY" + value = data.sops_file.secret_vars.data["namecheap_api_key"] + repositories = ["tfroot-namecheap", "channel-project"] + } + "cloudflare_auth_client_id" = { + name = "CLOUDFLARE_AUTH_CLIENT_ID" + value = data.sops_file.secret_vars.data["cloudflare_auth_client_id"] + repositories = setsubtract(local.active_github_repositories, toset(["channel-project", "hero-host-config"])) + } + "cloudflare_auth_client_secret" = { + name = "CLOUDFLARE_AUTH_CLIENT_SECRET" + value = data.sops_file.secret_vars.data["cloudflare_auth_client_secret"] + repositories = setsubtract(local.active_github_repositories, toset(["channel-project", "hero-host-config"])) + } + "chart_updater_github_app_private_key" = { + name = "CHART_UPDATER_GITHUB_APP_PRIVATE_KEY" + value = data.sops_file.secret_vars.data["chart_updater_github_app_private_key"] + repositories = [ + "charts", + "kustomize-cluster", + "tfroot-aws", + "tfroot-cloudflare", + "tfroot-gcp", + "tfroot-github", + "tfroot-libvirt", + "tfroot-namecheap", + "tfroot-twilio", + ] + } + "ssh_private_key" = { + name = "SSH_PRIVATE_KEY" + value = data.sops_file.secret_vars.data["ssh_private_key"] + repositories = ["tfroot-libvirt"] + } + "ssh_known_hosts" = { + name = "SSH_KNOWN_HOSTS" + value = data.sops_file.secret_vars.data["ssh_known_hosts"] + repositories = ["tfroot-libvirt"] + } + } +} diff --git a/templates/dependabot-notify.yml.tftpl b/templates/dependabot-notify.yml.tftpl new file mode 100644 index 0000000..de27250 --- /dev/null +++ b/templates/dependabot-notify.yml.tftpl @@ -0,0 +1,15 @@ +--- +# 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 From 4fd90b1ad5f8d681577cde00ee5f6c3f66c7870c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:26:35 +0000 Subject: [PATCH 3/6] chore: apply pre-commit fixes --- repositories.tf | 294 ++++++++++++++++++++++++------------------------ 1 file changed, 147 insertions(+), 147 deletions(-) diff --git a/repositories.tf b/repositories.tf index 35018fd..8f401ed 100644 --- a/repositories.tf +++ b/repositories.tf @@ -4,193 +4,193 @@ locals { # repository-specific policy list elsewhere. repositories = { ".github" = { - archived = false - private = false - topics = ["community-health", "github", "org-profile"] - protection_profile = "strict" - required_status_checks = ["pre-commit"] - dependabot_ecosystems = ["github-actions"] - allow_auto_merge = false + archived = false + private = false + topics = ["community-health", "github", "org-profile"] + protection_profile = "strict" + required_status_checks = ["pre-commit"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false } "agent-knowledge" = { - archived = false - private = true - topics = ["agents", "documentation", "knowledge-base", "opencode"] - protection_profile = "relaxed" - required_status_checks = [] - dependabot_ecosystems = [] - allow_auto_merge = false + archived = false + private = true + topics = ["agents", "documentation", "knowledge-base", "opencode"] + protection_profile = "relaxed" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false } "ansible-project-libvirt" = { - archived = true - private = false - topics = [] - protection_profile = "none" - required_status_checks = [] - dependabot_ecosystems = [] - allow_auto_merge = false + archived = true + private = false + topics = [] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false } "ansible-role-crc" = { - archived = true - private = false - topics = [] - protection_profile = "none" - required_status_checks = [] - dependabot_ecosystems = [] - allow_auto_merge = false + archived = true + private = false + topics = [] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false } "ansible-site-cluster" = { - archived = true - private = false - topics = [] - protection_profile = "none" - required_status_checks = [] - dependabot_ecosystems = [] - allow_auto_merge = false + archived = true + private = false + topics = [] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false } "cflan" = { - archived = false - private = false - topics = ["cloudflare", "dns", "networking", "networkmanager", "python"] - protection_profile = "strict" - required_status_checks = ["lint-and-test (3.10)", "lint-and-test (3.11)", "lint-and-test (3.12)", "lint-and-test (3.13)", "type-check"] - dependabot_ecosystems = ["github-actions", "pip"] - allow_auto_merge = false + archived = false + private = false + topics = ["cloudflare", "dns", "networking", "networkmanager", "python"] + protection_profile = "strict" + required_status_checks = ["lint-and-test (3.10)", "lint-and-test (3.11)", "lint-and-test (3.12)", "lint-and-test (3.13)", "type-check"] + dependabot_ecosystems = ["github-actions", "pip"] + allow_auto_merge = false } "channel-project" = { - archived = false - private = true - topics = ["cloudflare", "dns", "opentofu", "video-streaming"] - protection_profile = "none" - required_status_checks = [] - dependabot_ecosystems = [] - allow_auto_merge = false + archived = false + private = true + topics = ["cloudflare", "dns", "opentofu", "video-streaming"] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = [] + allow_auto_merge = false } "charts" = { - archived = false - private = false - topics = ["ghcr", "gitops", "helm", "helm-charts", "oci"] - protection_profile = "strict" - required_status_checks = ["test"] - dependabot_ecosystems = ["github-actions"] - allow_auto_merge = false + archived = false + private = false + topics = ["ghcr", "gitops", "helm", "helm-charts", "oci"] + protection_profile = "strict" + required_status_checks = ["test"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false } "hero-host-config" = { - archived = false - private = true - topics = ["ansible", "cloudflare-zero-trust", "configuration-management", "node-exporter", "rhel", "systemd"] - protection_profile = "none" - required_status_checks = [] - dependabot_ecosystems = ["github-actions"] - allow_auto_merge = false + archived = false + private = true + topics = ["ansible", "cloudflare-zero-trust", "configuration-management", "node-exporter", "rhel", "systemd"] + protection_profile = "none" + required_status_checks = [] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false } "images" = { - archived = false - private = false - topics = ["buildah", "containerfiles", "custom-runners", "github-actions", "opentofu"] - protection_profile = "strict" - required_status_checks = ["checks"] - dependabot_ecosystems = ["github-actions", "docker"] - allow_auto_merge = false + archived = false + private = false + topics = ["buildah", "containerfiles", "custom-runners", "github-actions", "opentofu"] + protection_profile = "strict" + required_status_checks = ["checks"] + dependabot_ecosystems = ["github-actions", "docker"] + allow_auto_merge = false } "kustomize-cluster" = { - archived = false - private = false - topics = ["app-of-apps", "argocd", "gitops", "k3s", "ksops", "kustomize", "sops"] - protection_profile = "strict" - required_status_checks = ["test"] - dependabot_ecosystems = ["github-actions"] - allow_auto_merge = true + archived = false + private = false + topics = ["app-of-apps", "argocd", "gitops", "k3s", "ksops", "kustomize", "sops"] + protection_profile = "strict" + required_status_checks = ["test"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = true } "shared-workflows" = { - archived = false - private = false - topics = ["github-actions", "github-workflows", "opentofu", "reusable-workflows", "shared-workflows"] - protection_profile = "strict" - required_status_checks = ["lint"] - dependabot_ecosystems = ["github-actions"] - allow_auto_merge = false + archived = false + private = false + topics = ["github-actions", "github-workflows", "opentofu", "reusable-workflows", "shared-workflows"] + protection_profile = "strict" + required_status_checks = ["lint"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false } "terraform-libvirt-domain" = { - archived = false - private = false - topics = ["cloud-init", "libvirt", "libvirt-provider", "terraform-module"] - protection_profile = "strict" - required_status_checks = ["test"] - dependabot_ecosystems = ["github-actions", "opentofu"] - allow_auto_merge = false + archived = false + private = false + topics = ["cloud-init", "libvirt", "libvirt-provider", "terraform-module"] + protection_profile = "strict" + required_status_checks = ["test"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false } "tfroot-aws" = { - archived = false - private = false - topics = ["aws-provider", "kms", "opentofu", "s3-backend", "sops", "tfstate"] - protection_profile = "strict" - required_status_checks = ["opentofu / test", "opentofu / plan"] - dependabot_ecosystems = ["github-actions", "opentofu"] - allow_auto_merge = false + archived = false + private = false + topics = ["aws-provider", "kms", "opentofu", "s3-backend", "sops", "tfstate"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false } "tfroot-cloudflare" = { - archived = false - private = false - topics = ["cloudflare", "cloudflare-access", "cloudflare-tunnel", "opentofu", "s3-backend", "sops", "tfstate"] - protection_profile = "strict" - required_status_checks = ["opentofu / test", "opentofu / plan"] - dependabot_ecosystems = ["github-actions", "opentofu"] - allow_auto_merge = false + archived = false + private = false + topics = ["cloudflare", "cloudflare-access", "cloudflare-tunnel", "opentofu", "s3-backend", "sops", "tfstate"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false } "tfroot-gcp" = { - archived = false - private = false - topics = ["gcp", "gcs-backend", "kms", "opentofu", "s3-backend", "sops", "tfstate", "workload-identity-federation"] - protection_profile = "strict" - required_status_checks = ["opentofu / test", "opentofu / plan"] - dependabot_ecosystems = ["github-actions", "opentofu"] - allow_auto_merge = false + archived = false + private = false + topics = ["gcp", "gcs-backend", "kms", "opentofu", "s3-backend", "sops", "tfstate", "workload-identity-federation"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false } "tfroot-github" = { - archived = false - private = false - topics = ["github-actions", "opentofu", "s3-backend", "sops", "tfstate", "terraform-provider-github"] - protection_profile = "strict" - required_status_checks = ["opentofu / test", "opentofu / plan"] - dependabot_ecosystems = ["github-actions", "opentofu"] - allow_auto_merge = false + archived = false + private = false + topics = ["github-actions", "opentofu", "s3-backend", "sops", "tfstate", "terraform-provider-github"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false } "tfroot-libvirt" = { - archived = false - private = false - topics = ["cloud-init", "k3s", "libvirt", "libvirt-provider", "opentofu", "s3-backend", "sops", "tfstate"] - protection_profile = "strict" - required_status_checks = ["opentofu / test", "opentofu / plan"] - dependabot_ecosystems = ["github-actions", "opentofu"] - allow_auto_merge = false + archived = false + private = false + topics = ["cloud-init", "k3s", "libvirt", "libvirt-provider", "opentofu", "s3-backend", "sops", "tfstate"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false } "tfroot-namecheap" = { - archived = false - private = false - topics = ["cloudflare", "dns", "domains", "namecheap", "opentofu"] - protection_profile = "strict" - required_status_checks = ["opentofu / test", "opentofu / plan"] - dependabot_ecosystems = ["github-actions", "opentofu"] - allow_auto_merge = false + archived = false + private = false + topics = ["cloudflare", "dns", "domains", "namecheap", "opentofu"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false } "tfroot-twilio" = { - archived = false - private = false - topics = ["opentofu", "s3-backend", "sms", "sops", "twilio"] - protection_profile = "strict" - required_status_checks = ["opentofu / test", "opentofu / plan"] - dependabot_ecosystems = ["github-actions", "opentofu"] - allow_auto_merge = false + archived = false + private = false + topics = ["opentofu", "s3-backend", "sms", "sops", "twilio"] + protection_profile = "strict" + required_status_checks = ["opentofu / test", "opentofu / plan"] + dependabot_ecosystems = ["github-actions", "opentofu"] + allow_auto_merge = false } "www" = { - archived = false - private = false - topics = ["css", "html", "pwa", "s3", "static-site"] - protection_profile = "strict" - required_status_checks = ["static-checks"] - dependabot_ecosystems = ["github-actions"] - allow_auto_merge = false + archived = false + private = false + topics = ["css", "html", "pwa", "s3", "static-site"] + protection_profile = "strict" + required_status_checks = ["static-checks"] + dependabot_ecosystems = ["github-actions"] + allow_auto_merge = false } } From 37f30edef31a1448d8d1e4cef3ad734dbe066535 Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Sat, 5 Sep 2026 11:28:27 -0600 Subject: [PATCH 4/6] fix: preserve current GitHub topic metadata --- repositories.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repositories.tf b/repositories.tf index 8f401ed..8562b64 100644 --- a/repositories.tf +++ b/repositories.tf @@ -141,7 +141,7 @@ locals { "tfroot-gcp" = { archived = false private = false - topics = ["gcp", "gcs-backend", "kms", "opentofu", "s3-backend", "sops", "tfstate", "workload-identity-federation"] + topics = ["gcp", "gcs-backend", "kms", "opentofu", "sops", "tfstate", "workload-identity-federation"] protection_profile = "strict" required_status_checks = ["opentofu / test", "opentofu / plan"] dependabot_ecosystems = ["github-actions", "opentofu"] From 6f23b7c5f0941e7a3c8af8fa3f1181c46275b4c2 Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Sun, 6 Sep 2026 12:47:45 -0600 Subject: [PATCH 5/6] fix: preserve existing SOPS data lookup --- secrets.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secrets.tf b/secrets.tf index 8b10f82..aa0af60 100644 --- a/secrets.tf +++ b/secrets.tf @@ -41,7 +41,7 @@ locals { } "www_secret_access_key" = { name = "AWS_SECRET_ACCESS_KEY" - value = data.sops_file.secret_vars.data["www_secret_access_key"] + value = data.sops_file.secret_vars.data["www_aws_secret_access_key"] repositories = ["www"] } "cloudflare_zone_id" = { From 20872227946ac68bd9eecca3743363eeeec67bd9 Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Sun, 6 Sep 2026 12:48:33 -0600 Subject: [PATCH 6/6] fix: preserve existing Dependabot enrollment --- repositories.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repositories.tf b/repositories.tf index 8562b64..1042056 100644 --- a/repositories.tf +++ b/repositories.tf @@ -72,7 +72,7 @@ locals { topics = ["ghcr", "gitops", "helm", "helm-charts", "oci"] protection_profile = "strict" required_status_checks = ["test"] - dependabot_ecosystems = ["github-actions"] + dependabot_ecosystems = [] allow_auto_merge = false } "hero-host-config" = { @@ -144,7 +144,7 @@ locals { topics = ["gcp", "gcs-backend", "kms", "opentofu", "sops", "tfstate", "workload-identity-federation"] protection_profile = "strict" required_status_checks = ["opentofu / test", "opentofu / plan"] - dependabot_ecosystems = ["github-actions", "opentofu"] + dependabot_ecosystems = [] allow_auto_merge = false } "tfroot-github" = {