Skip to content

microsoftexpert/tf-mod-github-enterprise-actions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🐙 GitHub Actions — Enterprise Terraform Module

One deeply-typed boundary for enterprise-wide GitHub Actions governance — the allowed-actions policy, which-organizations-may-run scope, the default GITHUB_TOKEN workflow permissions, and enterprise self-hosted runner groups, all secure-by-default. The Enterprise-tier mirror of tf-mod-github-actions-organization. Built for integrations/github v6.x.

Terraform GitHub provider module type resources scope

⚠️ ENTERPRISE CLOUD ONLY. Every resource here targets a GitHub Enterprise account and requires an enterprise-owner identity (admin:enterprise) — strictly higher than org admin. apply fails with 403/404 on Free/Team plans and on standalone organizations. See GitHub Prerequisites.


🧩 Overview

This composite module governs GitHub Actions at enterprise scope behind a single Terraform boundary:

  • 🛡️ Allowed-actions policy — restrict which actions/reusable workflows may run across the whole enterprise (all / local_only / selected), with a tunable allow-list for GitHub-owned, Marketplace-verified, and explicit owner/repo@ref patterns.
  • 🏢 Which-organizations scope — choose whether all, none, or only selected organizations in the enterprise may run Actions.
  • 🔑 Default workflow token permissions — set the enterprise-wide default GITHUB_TOKEN scope (read baseline / write) and whether Actions may approve pull-request reviews.
  • 🏃 Enterprise runner groups — a for_each map of self-hosted runner groups with all / selected organization visibility, optional public-repo access, and workflow restriction.

💡 Why it matters: an enterprise Actions policy is the highest-altitude CI/CD control in the org chart — it can force-disable choices every downstream organization makes. Centralizing the allowed-actions policy, the read-only token baseline, and runner-group scope in one reviewable module turns enterprise supply-chain governance into code review instead of a click-ops checklist in the enterprise admin console.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


🗺️ Where this fits in the family

This is the enterprise-scope apex of the Actions family — the org-tier counterpart is tf-mod-github-actions-organization, and it sits alongside the sibling tf-mod-github-enterprise (enterprise org / IP-allow-list / security governance). It consumes the enterprise slug and numeric org ids and emits the resolved policy plus a runner-group id map.

flowchart LR
 ent["tf-mod-github-enterprise<br/>(sibling · enterprise org / IP allow-list / security)"]
 entact["tf-mod-github-enterprise-actions<br/>(THIS · enterprise-scope Actions)"]
 actorg["tf-mod-github-actions-organization<br/>(org-tier counterpart)"]
 rg["tf-mod-github-actions-runner-group<br/>(org-scope runners)"]
 repo["tf-mod-github-repository<br/>(keystone)"]

 ent -- "enterprise_slug + numeric org ids" --> entact
 entact -. "policy precedence<br/>(enterprise can force-disable org choices)".-> actorg
 entact -- "runner_group_ids" --> rg
 repo -- "repo_id (numeric)" --> actorg

 style entact fill:#8957E5,color:#fff
 style repo fill:#24292F,color:#fff
Loading

🔌 Consumes the enterprise_slug and numeric org ids (from tf-mod-github-enterprise / the org listing); emits the resolved policy plus runner_group_ids. See the 🔌 Cross-Module Contract.


🧬 What this module builds

The primary resource is github_enterprise_actions_permissions.this; a singleton workflow-permissions resource and a for_each runner-group collection share the same enterprise boundary.

flowchart TD
 this["github_enterprise_actions_permissions.this<br/>(primary policy · allowed_actions + enabled_organizations)"]
 workflow["github_enterprise_actions_workflow_permissions.workflow<br/>(singleton · default GITHUB_TOKEN scope = read)"]
 rg["github_enterprise_actions_runner_group.runner_groups<br/>(for_each = var.runner_groups)"]

 this -- "same enterprise boundary" --> workflow
 this -- "same enterprise boundary" --> rg

 style this fill:#8957E5,color:#fff
Loading

Resource inventory

  • 🛡️ github_enterprise_actions_permissions.this — the primary policy: allowed-actions mode, enabled-organizations mode, the optional allow-list, and the optional selected-organization list.
  • 🔑 github_enterprise_actions_workflow_permissions.workflow — a singleton setting the enterprise default GITHUB_TOKEN scope (read by default) and whether Actions may approve PR reviews (false by default). Always managed, to enforce the read-only baseline.
  • 🏃 github_enterprise_actions_runner_group.runner_groups — a for_each map of enterprise self-hosted runner groups keyed by runner-group name.

✅ Provider / Versions

Requirement Value
Terraform >= 1.12.0
Provider integrations/github ~> 6.0 (current 6.12.1)
Plan/edition GitHub Enterprise Cloud (a real Enterprise account)

⚠️ Provider source is integrations/github, never the deprecated hashicorp/github. Migrate old state with terraform state replace-provider. ℹ️ Auth (PAT / GitHub CLI / OAuth / GitHub App) and the target enterprise identity are provider concerns — this module declares no owner / token / app_auth variables. The provider identity must be an enterprise owner.


📁 Module Structure

tf-mod-github-enterprise-actions/
├── providers.tf # terraform{} + required_providers (integrations/github ~> 6.0); no provider{} block
├── variables.tf # enterprise_slug → policy → workflow token → runner_groups; validations, secure defaults
├── main.tf # this policy + singleton workflow permissions + for_each runner groups
├── outputs.tf # id, resolved policy, default token scope, runner_group_ids map
├── SCOPE.md # design contract: in/out-of-scope, consumes/emits, token scopes, prerequisites
└── README.md # this file

⚙️ Quick Start

module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  # Secure defaults applied automatically:
  # allowed_actions = "selected" (GitHub-owned + verified only)
  # enabled_organizations = "all"
  # default_workflow_permissions = "read" (read-only GITHUB_TOKEN)
}

🔒 The empty call produces the secure baseline: only GitHub-owned and Marketplace-verified actions may run enterprise-wide, and every workflow's GITHUB_TOKEN is read-only.


🔌 Cross-Module Contract

Sourced from this repo's SCOPE.md.

Consumes

Input Type Source
enterprise_slug string caller-supplied enterprise identifier (the enterprise the provider identity administers)
enabled_organizations_config.organization_ids set(number) tf-mod-github-enterprise / the org listing — numeric org database ids; required only when enabled_organizations = "selected"
runner_groups[*].selected_organization_ids set(number) tf-mod-github-enterprise / the org listing — honored only when a group's visibility = "selected"

Emits

Output Description Consumed by
id Enterprise Actions permissions policy id (the enterprise) Composition / reporting
allowed_actions Resolved enterprise allowed-actions policy Audit / compliance
enabled_organizations Resolved which-orgs-can-run policy Audit
default_workflow_permissions Resolved enterprise default token scope (read-only baseline) Compliance reporting
can_approve_pull_request_reviews Resolved: whether Actions may approve PRs enterprise-wide Compliance reporting
runner_group_ids Map: runner-group name → id tf-mod-github-actions-runner-group / org runners, reporting

🔒 This module manages no secret material — nothing it emits is sensitive.


📚 Example Library

1 · Minimal — secure default policy + read-only token
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  allowed_actions       = "selected" # GitHub-owned + verified only
  enabled_organizations = "all"
  # default_workflow_permissions = "read" (secure default — kept implicit)
}

🔒 Even minimal, this blocks arbitrary third-party actions and pins every workflow token to read-only.

2 · Lock the enterprise down — local actions only
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  # Only actions/workflows defined inside the enterprise's own orgs may run.
  allowed_actions       = "local_only"
  enabled_organizations = "all"
}

🔒 local_only is the strictest non-none posture — no Marketplace actions at all, not even actions/checkout. Expect to vendor common actions internally.

3 · Selected actions allow-list with verified creators + explicit patterns
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  allowed_actions       = "selected"
  enabled_organizations = "all"

  allowed_actions_config = {
    github_owned_allowed = true
    verified_allowed     = true
    patterns_allowed = [
      "aws-actions/configure-aws-credentials@*",
      "hashicorp/setup-terraform@*",
      "azure/login@v2",
    ]
  }
}

💡 allowed_actions_config is honored only when allowed_actions = "selected". github_owned_allowed is required by the provider when the block renders — keep it true so GitHub's own actions don't break.

4 · Restrict which organizations may run Actions
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  allowed_actions       = "selected"
  enabled_organizations = "selected"

  enabled_organizations_config = {
    organization_ids = [
      12345678, # lending-platform org (numeric database id)
      87654321, # data-platform org
    ]
  }
}

💡 organization_ids are numeric org database ids, not org login names. enabled_organizations_config is required when enabled_organizations = "selected" (enforced at plan time).

5 · Kill switch — disable Actions for every organization
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug       = "financial-partners"
  enabled_organizations = "none" # no org in the enterprise may run Actions
}

⚠️ none is an enterprise-wide CI/CD off-switch — every organization's Actions stop. Use deliberately (incident response, migration freeze).

6 · Default workflow token = write (explicit opt-in)
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  # ⚠️ Grants every workflow in the enterprise a write-scoped GITHUB_TOKEN.
  default_workflow_permissions     = "write"
  can_approve_pull_request_reviews = false # keep humans in the review loop
}

⚠️ write hands broad repository-write to every workflow token enterprise-wide. Prefer the read default and elevate per-job with explicit permissions: in the workflow YAML. Leaving can_approve_pull_request_reviews = false stops a workflow from satisfying required-review gates without a human.

7 · One enterprise runner group — selected-org visibility
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  runner_groups = {
    "secure-linux" = {
      visibility                = "selected"
      selected_organization_ids = [12345678, 87654321]
    }
  }
}

🔒 visibility = "selected" (the default) scopes the runner group to named orgs — self-hosted runners can execute untrusted workflow code, so this is least-privilege by default.

8 · Runner group available to all organizations
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  runner_groups = {
    "shared-pool" = {
      visibility = "all" # every org in the enterprise may target this group
    }
  }
}

⚠️ visibility = "all" exposes the runners to every org. Note: GitHub documents a "private" value, but the API does not support it for runner groups — this module rejects it at plan time. Use "selected" to scope narrowly.

9 · Runner group restricted to specific workflows
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  runner_groups = {
    "prod-deploy" = {
      visibility                = "selected"
      selected_organization_ids = [12345678]
      restricted_to_workflows   = true
      selected_workflows = [
        "lending-platform/deploy/.github/workflows/release.yml@refs/heads/main",
      ]
    }
  }
}

🔒 With restricted_to_workflows = true, only the listed fully-qualified workflow refs may use the group. An empty selected_workflows with the flag on permits no workflows to run.

10 · Runner groups for_each at scale from a map(object)
locals {
  runner_groups = {
    "linux-general" = { visibility = "all" }
    "linux-secure"  = { visibility = "selected", selected_organization_ids = [12345678, 87654321] }
    "gpu-ml"        = { visibility = "selected", selected_organization_ids = [11112222], allows_public_repositories = false }
    "windows-build" = { visibility = "selected", selected_organization_ids = [12345678] }
  }
}

module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"
  runner_groups   = local.runner_groups
}

⚠️ Bulk for_each over many runner groups is one API write each — watch GitHub secondary rate limits on large applies (see Troubleshooting). Keys are stable runner-group names, so adding one group never churns the others.

11 · Wiring numeric org ids from tf-mod-github-enterprise
module "enterprise" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise?ref=v1.0.0"
  #... manages the enterprise account and its organizations...
}

module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug       = module.enterprise.slug
  allowed_actions       = "selected"
  enabled_organizations = "selected"

  enabled_organizations_config = {
    # numeric org database ids emitted by the sibling enterprise module
    organization_ids = values(module.enterprise.organization_database_ids)
  }
}

💡 Org-scoped selections use numeric org database ids, not slugs. The exact output name (organization_database_ids here) comes from tf-mod-github-enterprise.

12 · Policy + workflow token + runner groups together
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  allowed_actions       = "selected"
  enabled_organizations = "all"

  allowed_actions_config = {
    github_owned_allowed = true
    verified_allowed     = true
    patterns_allowed     = ["hashicorp/setup-terraform@v3"]
  }

  default_workflow_permissions     = "read"
  can_approve_pull_request_reviews = false

  runner_groups = {
    "linux-secure" = { visibility = "selected", selected_organization_ids = [12345678] }
  }
}
13 · 🔒 Secure / hardened enterprise baseline (recommended)
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = "financial-partners"

  # Tightest practical posture short of local_only.
  allowed_actions       = "selected"
  enabled_organizations = "selected"

  allowed_actions_config = {
    github_owned_allowed = true
    verified_allowed     = false # block non-GitHub Marketplace creators entirely
    patterns_allowed = [
      "hashicorp/setup-terraform@v3",
      "aws-actions/configure-aws-credentials@v4",
    ]
  }

  enabled_organizations_config = {
    organization_ids = [12345678, 87654321]
  }

  # Read-only token, no Actions PR approvals.
  default_workflow_permissions     = "read"
  can_approve_pull_request_reviews = false

  runner_groups = {
    "prod-secure" = {
      visibility                 = "selected"
      selected_organization_ids  = [12345678]
      allows_public_repositories = false
      restricted_to_workflows    = true
      selected_workflows = [
        "lending-platform/deploy/.github/workflows/release.yml@refs/tags/v*",
      ]
    }
  }
}

🔒 verified_allowed = false + pinned @vN patterns + selected org scope + read-only token + workflow-restricted runners is the hardened enterprise baseline.

🏗️ 14 · End-to-end composition — tf-mod-github-enterprisetf-mod-github-enterprise-actions (outputs → inputs)
# ── 1) Enterprise account + organizations (sibling module) ────────────
module "enterprise" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise?ref=v1.0.0"

  slug = "financial-partners"
  #... org / IP-allow-list / security-analysis governance...
}

# ── 2) Enterprise-scope Actions governance (THIS module) ──────────────
module "enterprise_actions" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-enterprise-actions?ref=v1.0.0"

  enterprise_slug = module.enterprise.slug

  allowed_actions       = "selected"
  enabled_organizations = "selected"

  allowed_actions_config = {
    github_owned_allowed = true
    verified_allowed     = true
    patterns_allowed     = ["hashicorp/setup-terraform@v3"]
  }

  # numeric org database ids wired from the sibling enterprise module
  enabled_organizations_config = {
    organization_ids = values(module.enterprise.organization_database_ids)
  }

  default_workflow_permissions     = "read"
  can_approve_pull_request_reviews = false

  runner_groups = {
    "enterprise-linux" = {
      visibility                = "selected"
      selected_organization_ids = values(module.enterprise.organization_database_ids)
    }
  }
}

# ── 3) Org-tier Actions config can consume the enterprise policy / runner-group ids ──
output "enterprise_runner_groups" {
  value = module.enterprise_actions.runner_group_ids
}

🔒 The enterprise policy is the apex: it can force-disable choices that tf-mod-github-actions-organization makes at org scope. Wire enterprise_slug and numeric org ids from tf-mod-github-enterprise, never hardcode them.


📥 Inputs

Identifying parent reference

  • enterprise_slug (required) — slug of the GitHub Enterprise to govern. Every resource is scoped to it.

Policy (primary this resource)

  • allowed_actionsselected (secure default) / all / local_only.
  • enabled_organizationsall (default) / none / selected.
  • allowed_actions_configgithub_owned_allowed, verified_allowed, patterns_allowed (honored only when allowed_actions = "selected").
  • enabled_organizations_configorganization_ids (required when enabled_organizations = "selected").

Workflow token permissions (singleton)

  • default_workflow_permissionsread (secure default) / write.
  • can_approve_pull_request_reviewsbool, default false.

Runner groups (for_each)

  • runner_groupsmap(object) keyed by runner-group name; visibility, selected org ids, public-repo access, workflow restriction.
Full object schemas for nested inputs
# allowed_actions_config (honored only when allowed_actions = "selected")
object({
 github_owned_allowed = optional(bool, true) # required by the provider when the block renders
 verified_allowed = optional(bool, true) # allow Marketplace-verified creators
 patterns_allowed = optional(set(string), []) # "owner/repo@ref" / wildcard patterns
})

# enabled_organizations_config (required when enabled_organizations = "selected")
object({
 organization_ids = set(number) # numeric enterprise org database ids
})

# runner_groups (keyed by runner-group name)
map(object({
 visibility = optional(string, "selected") # "all" | "selected"
 selected_organization_ids = optional(set(number), []) # honored when visibility = "selected"
 allows_public_repositories = optional(bool, false)
 restricted_to_workflows = optional(bool, false)
 selected_workflows = optional(list(string), []) # honored when restricted_to_workflows = true
}))

🧾 Outputs

Output Description
id Enterprise Actions permissions policy id (the enterprise)
allowed_actions Resolved allowed-actions policy (all / local_only / selected)
enabled_organizations Resolved which-orgs-can-run policy (all / none / selected)
default_workflow_permissions Resolved enterprise default GITHUB_TOKEN scope (read baseline / write)
can_approve_pull_request_reviews Whether Actions may approve PR reviews enterprise-wide (false baseline)
runner_group_ids Map of runner-group name → runner group id (empty when none)

ℹ️ No arn, no node_id/slug (these enterprise policy resources expose none), no tags_all. No outputs are sensitive — this module manages no secrets.


🧠 Architecture Notes

  • Enterprise vs org scope interaction. Enterprise and organization Actions policies are separate layers. The enterprise policy is the apex: it can force-disable choices the org-tier (tf-mod-github-actions-organization) makes — e.g. if the enterprise sets enabled_organizations = "selected" and omits an org, no org-level toggle re-enables Actions there. Model the enterprise policy as the ceiling and the org policy as the per-org floor beneath it.
  • Read-only token baseline and why. default_workflow_permissions defaults to "read" — a read-only GITHUB_TOKEN for every workflow in the enterprise. Write access is a deliberate, enterprise-wide grant; prefer the read default and elevate per-job with explicit permissions: in workflow YAML. The singleton is always managed so the baseline is enforced, not merely suggested; on destroy the provider resets to read / no-PR-approval.
  • allowed_actions = "selected" + config block. The allow-list lives in allowed_actions_config and is rendered via a dynamic block only when allowed_actions = "selected"; for all / local_only the block emits nothing and any leftover config is ignored. github_owned_allowed is required by the provider when the block renders (kept true by default). The same pattern governs enabled_organizations_config under enabled_organizations = "selected".
  • id semantics. github_enterprise_actions_permissions.this.id is the enterprise itself (the policy is a singleton per enterprise) — there is no node_id/slug for the policy. The workflow-permissions resource's id is the enterprise slug. Runner groups expose id only.
  • Authoritative singletons. Both the permissions policy and the workflow-permissions resource are authoritative singletons — applying overwrites the enterprise's policy wholesale. Only one instance should manage each across your whole Terraform estate, or the last apply wins and the others churn.
  • Runner-group visibility. Only all and selected are supported; GitHub documents "private" but the API rejects it, so this module rejects it at plan time. selected_organization_ids are numeric org database ids, honored only when visibility = "selected".
  • ForceNew / import. enterprise_slug is the identity of every resource — changing it moves the resources. Import ids differ: the permissions/workflow resources import by enterprise slug; a runner group imports by enterprise-slug/<numeric-id> (see Runbook).
  • Eventual consistency & secondary rate limits. The GitHub REST API is eventually consistent and enforces secondary rate limits on rapid writes — bulk runner-group for_each can trip them. Terraform's graph normally orders dependencies correctly; an occasional re-apply clears transient races.
  • No tags, no timeouts. GitHub has neither concept on these resources — do not look for a tags/timeouts tail.

🧱 Design Principles

  • 🔒 Secure by default. allowed_actions = "selected" blocks arbitrary third-party actions out of the box; default_workflow_permissions = "read" keeps the token read-only.
  • 🪪 Least privilege. Runner groups default to selected visibility and no public-repo access; Actions cannot approve PRs by default.
  • 🏛️ Apex governance. One authoritative enterprise policy is the ceiling every organization sits beneath — supply-chain control as code, not console clicks.
  • 🤫 No secret leakage. This module owns no secret material; nothing emitted is sensitive.
  • 🚫 No provider concerns leak in. No owner / token / app_auth variables — auth and the target enterprise are the caller's provider block.
  • 📦 No GitHub-absent concepts. No tags, no tags_all, no timeouts, no ARNs.

🚀 Runbook

terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan # requires an enterprise-owner github provider (admin:enterprise)
terraform apply
terraform output

Importing existing enterprise configuration

# permissions policy + workflow permissions import by enterprise slug
terraform import github_enterprise_actions_permissions.this financial-partners
terraform import github_enterprise_actions_workflow_permissions.workflow financial-partners

# a runner group imports by enterprise-slug/<numeric-id>
terraform import 'github_enterprise_actions_runner_group.runner_groups["secure-linux"]' financial-partners/42

⚠️ Always pin the module source to a tag — ?ref=v1.0.0, never a branch. Branches move; tags are immutable.


🧪 Testing

Offline proof gate (no enterprise credentials required):

terraform fmt -check # zero formatting differences
terraform validate # zero errors
tflint # core rules — no dedicated GitHub ruleset exists

ℹ️ terraform plan/apply require a live github provider authenticated as an enterprise owner; init/validate/fmt run fully offline.


💬 Example Output

id = "financial-partners"
allowed_actions = "selected"
enabled_organizations = "selected"
default_workflow_permissions = "read"
can_approve_pull_request_reviews = false
runner_group_ids = {
 "enterprise-linux" = "7"
 "prod-secure" = "9"
}

🔍 Troubleshooting

Symptom Cause Resolution
404 Not Found / 403 on every resource Not a real Enterprise account, or identity is not an enterprise owner Confirm a GitHub Enterprise Cloud account and an admin:enterprise identity — see Prerequisites
404 resolving the enterprise enterprise_slug typo / identity doesn't administer it Use the exact enterprise slug the identity owns
enabled_organizations_config is required when enabled_organizations = 'selected' Variable validation tripped Supply enabled_organizations_config.organization_ids, or set enabled_organizations to all/none
visibility must be 'all' or 'selected' on a runner group "private" requested The GitHub API does not support private runner-group visibility — use selected
Runner group runs no workflows restricted_to_workflows = true with an empty selected_workflows Populate selected_workflows, or set restricted_to_workflows = false
Org-level Actions toggle has no effect Enterprise policy is force-disabling it The enterprise policy is the ceiling — adjust enabled_organizations / allowed_actions here, not just at org scope
default_workflow_permissions reverted to read after a destroy By design — the provider resets to safe defaults on destroy Re-apply to restore write if it was intentional
You have exceeded a secondary rate limit on bulk for_each Too many runner-group writes too fast Reduce parallelism (terraform apply -parallelism=2); retry after backoff

🔑 Required token scopes / GitHub App permissions

Classic PAT scopes

  • admin:enterprise — enterprise administration (allowed-actions policy, workflow permissions)
  • manage_runners:enterprise — enterprise self-hosted runner groups

Fine-grained PAT / GitHub App permissions

  • Enterprise permissions → Actions / Enterprise administration: read/write
  • Enterprise self-hosted runners: read/write
  • Metadata: read

⚠️ Managing enterprise resources requires an ENTERPRISE OWNER identity — a strictly higher privilege than org admin. Auth and the target enterprise are provider concerns, never module variables.


🧷 GitHub Prerequisites

  • ⚠️ ENTERPRISE CLOUD ONLY. These resources require a GitHub Enterprise account (not merely an org on an Enterprise plan). apply fails with 404 / 403 on Free/Team and on standalone organizations.
  • The provider identity must be an enterprise owner.
  • enterprise_slug must identify an enterprise the identity administers.
  • organization_ids / selected_organization_ids are numeric org database ids belonging to the enterprise — wire from tf-mod-github-enterprise / the org listing.
  • Rate limits: runner-group for_each is one API write each — watch secondary rate limits on large applies.

🔗 Related Docs

  • This module's SCOPE.md — the authoritative design contract
  • tf-mod-github-enterprise — sibling: enterprise org / IP-allow-list / security-analysis governance (emits the enterprise slug + org ids consumed here)
  • tf-mod-github-actions-organization — the org-tier counterpart in the Actions family
  • tf-mod-github-actions-runner-group — org-scope runner groups (consume runner_group_ids for reporting)
  • tf-mod-github-repository — keystone module of the GitHub library
  • integrations/github provider — github_enterprise_actions_permissions, github_enterprise_actions_workflow_permissions, github_enterprise_actions_runner_group
  • GitHub Docs — enforcing GitHub Actions policies for your enterprise

Packages

 
 
 

Contributors

Languages