@@ -523,7 +523,7 @@ resource "github_branch_protection" "openstack_branch_protection_caracal" {
523523}
524524
525525resource "github_branch_protection" "openstack_branch_protection_epoxy" {
526- for_each = toset (var. repositories [" OpenStack" ])
526+ for_each = toset ([ for r in var . repositories [" OpenStack" ] : r if ! contains (var . repositories [ " ZuulOnly " ], r) ])
527527 repository_id = data. github_repository . repositories [each . key ]. node_id
528528
529529 pattern = " stackhpc/2025.1"
@@ -562,7 +562,7 @@ resource "github_branch_protection" "openstack_branch_protection_epoxy" {
562562}
563563
564564resource "github_branch_protection" "openstack_branch_protection_gazpacho" {
565- for_each = toset (var. repositories [" OpenStack" ])
565+ for_each = toset ([ for r in var . repositories [" OpenStack" ] : r if ! contains (var . repositories [ " ZuulOnly " ], r) ])
566566 repository_id = data. github_repository . repositories [each . key ]. node_id
567567
568568 pattern = " stackhpc/2026.1"
@@ -637,6 +637,75 @@ resource "github_branch_protection" "openstack_branch_protection_master" {
637637 }
638638}
639639
640+ # ZuulOnly: push_allowances contains only the Zuul GitHub App, so PRs can only be
641+ # merged by Zuul (typically after the "gate" label enqueues the change into its gate
642+ # pipeline). Neither the Developers team nor the owning team can merge directly.
643+ # Only the stackhpc/2025.1 and stackhpc/2026.1 branches are gated this way for now;
644+ # neutron's other branches keep normal OpenStack-group protection (see openstack_branch_protection_*).
645+ resource "github_branch_protection" "zuulonly_branch_protection_epoxy" {
646+ for_each = toset (var. repositories [" ZuulOnly" ])
647+ repository_id = data. github_repository . repositories [each . key ]. node_id
648+
649+ pattern = " stackhpc/2025.1"
650+ require_conversation_resolution = true
651+ allows_deletions = false
652+ allows_force_pushes = false
653+
654+ restrict_pushes {
655+ blocks_creations = false
656+ push_allowances = [
657+ local . zuul_app_node_id
658+ ]
659+ }
660+
661+ required_pull_request_reviews {
662+ dismiss_stale_reviews = true
663+ require_code_owner_reviews = true
664+ required_approving_review_count = 1
665+ }
666+
667+ required_status_checks {
668+ contexts = [" stackhpc/check" ]
669+ strict = false
670+ }
671+
672+ lifecycle {
673+ prevent_destroy = true
674+ }
675+ }
676+
677+ resource "github_branch_protection" "zuulonly_branch_protection_gazpacho" {
678+ for_each = toset (var. repositories [" ZuulOnly" ])
679+ repository_id = data. github_repository . repositories [each . key ]. node_id
680+
681+ pattern = " stackhpc/2026.1"
682+ require_conversation_resolution = true
683+ allows_deletions = false
684+ allows_force_pushes = false
685+
686+ restrict_pushes {
687+ blocks_creations = false
688+ push_allowances = [
689+ local . zuul_app_node_id
690+ ]
691+ }
692+
693+ required_pull_request_reviews {
694+ dismiss_stale_reviews = true
695+ require_code_owner_reviews = true
696+ required_approving_review_count = 1
697+ }
698+
699+ required_status_checks {
700+ contexts = [" stackhpc/check" ]
701+ strict = false
702+ }
703+
704+ lifecycle {
705+ prevent_destroy = true
706+ }
707+ }
708+
640709resource "github_branch_protection" "platform_branch_protection" {
641710 for_each = toset (var. repositories [" Platform" ])
642711 repository_id = data. github_repository . repositories [each . key ]. node_id
0 commit comments