From 4597dedf784b81d4f51127df726a4e400af80de8 Mon Sep 17 00:00:00 2001 From: Daniel Vieites Date: Wed, 6 May 2026 15:26:49 +0200 Subject: [PATCH 1/2] feat: add gh-provisioner IA machinery --- AGENTS.md | 45 +++++++++++ CONSTITUTION.md | 75 ++++++++++++++++++ RULES.md | 126 ++++++++++++++++++++++++++++++ package.json | 6 +- scripts/validate-ia-machinery.mjs | 36 +++++++++ 5 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 AGENTS.md create mode 100644 CONSTITUTION.md create mode 100644 RULES.md create mode 100644 scripts/validate-ia-machinery.mjs diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..34d1b7b8f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,45 @@ +# TFM Agent Instructions + +All AI agents working in this repository must read and follow `CONSTITUTION.md` before proposing or making changes. + +## Mandatory Reading + +Before any code or documentation change, read: + +- `CONSTITUTION.md` +- `CONTRIBUTING.md` +- `RULES.md` when the module is or may be consumed by Firestartr `gh_provisioner` +- Existing files in the target module, especially `variables.tf`, `main.tf`, `outputs.tf`, `versions.tf`, `docs/header.md`, `docs/footer.md`, `_examples/`, and `README.md` + +## Repository Purpose + +This repository contains Terraform modules. Firestartr now consumes these modules directly. Do not introduce CDKTF guidance for current Firestartr GitHub resource integrations unless a user explicitly asks about legacy history. + +## Working Rules + +- Strictly follow `CONTRIBUTING.md` for module layout, docs, examples, README generation, commit style, and PR process. +- Prefer small, focused modules with one responsibility. +- Use explicit Terraform object types and validations. +- Keep module inputs and outputs stable for consumers. +- Document import IDs, Terraform resource addresses, and destructive lifecycle behavior. +- Do not weaken existing validations or remove useful outputs without a migration reason. +- Do not bypass `terraform fmt`, validation, tests, docs generation, CI, or release requirements. + +## gh-provisioner Modules + +For modules consumed by `gh_provisioner`, load and follow `RULES.md`. + +The non-negotiable contract is a single top-level `config` input object consumed from `terraform.tfvars.json`. The module must be compatible with one Firestartr CR and one Terraform state. + +If a requested module cannot be modeled with that contract, stop and explain why it is not compatible before writing code. + +## Expected Agent Output + +When proposing or implementing a module change, include: + +- The module path. +- Whether it is `gh_provisioner` compatible. +- The `config` shape if applicable. +- Import behavior. +- Delete behavior and any safety concerns. +- Validation performed or intentionally skipped. diff --git a/CONSTITUTION.md b/CONSTITUTION.md new file mode 100644 index 000000000..f5545c3cf --- /dev/null +++ b/CONSTITUTION.md @@ -0,0 +1,75 @@ +# Constitution: Prefapp Terraform Modules + +This document is the supreme rule set for this repository. All contributors and AI agents must follow it when creating or modifying Terraform modules in `prefapp/tfm`. + +## 1. Purpose + +This repository contains reusable Terraform modules used directly by Firestartr and other Prefapp systems. Modules must be safe, documented, testable, and compatible with the consumers that call them. + +## 2. Rule Hierarchy + +When rules conflict, follow this precedence order: + +1. `CONSTITUTION.md` +2. `AGENTS.md` +3. `CONTRIBUTING.md` +4. `RULES.md` for gh-provisioner-compatible modules +5. Module-local documentation and examples + +`CONTRIBUTING.md` must be strictly observed for module structure, documentation, examples, README generation, commit messages, and pull request process. + +## 3. Module Standards + +Every module must have a single clear responsibility. Do not mix unrelated managed resources in one module unless the module is explicitly a composite module that exists to coordinate several child resources under one stable interface. + +Every module must follow the documented structure in `CONTRIBUTING.md`, including: + +- `.terraform-docs.yml` +- `README.md` generated by `terraform-docs` +- `main.tf` +- `variables.tf` +- `outputs.tf` +- `docs/header.md` +- `docs/footer.md` +- `_examples/` when applicable + +Modules must define explicit variable types, validations where practical, stable outputs, and provider requirements in `versions.tf` when the module uses providers. + +## 4. gh-provisioner Compatibility + +Modules intended to be consumed by Firestartr `gh_provisioner` must also follow `RULES.md`. + +The key compatibility contract is: + +- The module is fed by one `terraform.tfvars.json` document. +- The document has a single top-level key named `config`. +- The module exposes `variable "config"` as the single resource configuration object. +- The module maps to one Firestartr custom resource and one Terraform state. +- Import and delete behavior must be documented because Firestartr needs predictable lifecycle semantics. + +If a module does not satisfy this contract, AI agents must not claim it is compatible with `gh_provisioner`. + +## 5. Validation + +Changes must be validated with the commands and checks documented in `CONTRIBUTING.md` and any module-specific documentation. + +At minimum, module changes should be checked with: + +- `terraform fmt` +- `terraform init` for examples or test fixtures where applicable +- `terraform validate` for examples or test fixtures where applicable +- `terraform-docs .` from the module directory when documentation changes or module inputs/outputs change + +Do not bypass formatting, validation, tests, CI, release, or documentation requirements. + +## 6. Security And Lifecycle Safety + +Modules must not expose secrets in outputs unless the output is explicitly marked `sensitive = true` and is genuinely required. + +Modules must avoid surprising destructive behavior. If a Terraform provider's delete action resets settings, mutates existing resources, or affects more than the resource represented by the module, the module documentation must call this out and Firestartr integration must account for it. + +## 7. Release Compatibility + +This repository is managed by Release Please. Commits must follow Conventional Commits as documented in `CONTRIBUTING.md`. + +For modules consumed by Firestartr, release tags must be suitable for direct module references from `gitops-k8s`, usually following the existing pattern `-v`. diff --git a/RULES.md b/RULES.md new file mode 100644 index 000000000..4b0ce5ff0 --- /dev/null +++ b/RULES.md @@ -0,0 +1,126 @@ +# gh-provisioner Compatible Module Rules + +These rules apply to Terraform modules intended to be consumed by Firestartr `gh_provisioner` from `prefapp/gitops-k8s`. + +## 1. Compatibility Contract + +A compatible module must be fed by one generated `terraform.tfvars.json` document with exactly one root configuration key: + +```json +{ + "config": {} +} +``` + +The module must define one top-level Terraform variable: + +```hcl +variable "config" { + description = "..." + type = object({}) +} +``` + +The module may define other operational variables only when they are not part of Firestartr CR desired state and there is a documented reason. Prefer not to add them. + +## 2. State Ownership + +One `gh_provisioner`-compatible module maps to: + +- One Firestartr Kubernetes custom resource. +- One `gh_provisioner` entity. +- One Terraform state. +- One generated `terraform.tfvars.json` document. + +If several Terraform resources are needed to represent one Firestartr resource, compose them inside one module. + +If several independently managed artifacts are needed, use a composite module pattern. Existing examples include `github-files-set` and `github-org-rulesets`. + +## 3. Input Shape + +The `config` object must be explicit and typed. + +Required practices: + +- Use `object(...)`, `map(object(...))`, or `list(object(...))` with concrete attribute types. +- Use `optional(...)` defaults where Terraform should own a default. +- Add `validation` blocks for enum values, uniqueness, non-empty strings, and provider constraints. +- Use naming that matches the Firestartr CR or established module convention. +- Keep generated JSON friendly. Avoid requiring HCL-only constructs from the caller. + +Do not accept broad `any` unless the provider schema is genuinely unbounded and the reason is documented. + +## 4. Resource Addresses And Imports + +Every compatible module must document Terraform resource addresses that `gh_provisioner` may need for import. + +For each importable resource, document: + +- Terraform address, for example `github_repository.this`. +- Provider import ID format. +- How Firestartr can discover that ID, for example from a GitHub API lookup. +- Whether import is mandatory for adopting existing resources. + +If the Terraform provider does not support import, document that explicitly. + +## 5. Delete Semantics + +Every compatible module must document delete behavior. + +Safe delete means `terraform destroy` removes only the resource represented by the Firestartr CR. + +Unsafe delete includes behavior that resets settings, mutates a broader resource, archives instead of deletes, or affects unrelated objects. If delete is unsafe, the module documentation and `gitops-k8s` integration must choose a non-destructive strategy, such as state-only unmanagement, and tests must cover the decision. + +## 6. Outputs + +Outputs must be stable and useful for Firestartr. + +Include outputs for: + +- Provider IDs needed by dependent resources. +- Names, slugs, node IDs, or URLs used by other Firestartr CRs. +- Values that help tests and reconciliation verify what was created. + +Do not output secrets unless required, and mark them `sensitive = true`. + +## 7. Documentation And Examples + +Follow `CONTRIBUTING.md` for all modules. + +Additionally, compatible modules should include in `docs/header.md` or `docs/footer.md`: + +- A note that the module is `gh_provisioner` compatible. +- The expected `config` structure. +- A minimal example using `config`. +- Import behavior. +- Delete behavior. +- Any Firestartr-specific lifecycle notes. + +The generated `README.md` must be updated with `terraform-docs .` after input, output, or docs changes. + +## 8. Testing + +Compatible modules must be testable without Firestartr. + +At minimum, provide examples that can run: + +- `terraform init` +- `terraform validate` +- `terraform plan` when provider credentials and required IDs are available + +When the repository supports Terraform tests, add tests that cover valid input, invalid input validations, and important lifecycle behavior. + +## 9. Firestartr Integration Checklist + +Before marking a module ready for `gh_provisioner`, confirm: + +- `variables.tf` exposes `variable "config"`. +- `main.tf` consumes `var.config`. +- `outputs.tf` exposes stable outputs. +- `versions.tf` declares provider requirements. +- Examples use the `config` object. +- Docs explain import and delete behavior. +- The module can be referenced by `gitops-k8s` using a release tag. +- A matching `gh_provisioner` entity can map a CR spec to this `config` shape without special Terraform logic. + +If any item is missing, document the gap before opening or updating the Firestartr integration PR. diff --git a/package.json b/package.json index 7ae5e9740..bfbd5c68b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Terraform modules", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node scripts/validate-ia-machinery.mjs" }, "repository": { "type": "git", @@ -17,6 +17,6 @@ }, "homepage": "https://github.com/prefapp/tfm#readme", "workspaces": [ - "modules/*" - ] + "modules/*" + ] } diff --git a/scripts/validate-ia-machinery.mjs b/scripts/validate-ia-machinery.mjs new file mode 100644 index 000000000..e1d007653 --- /dev/null +++ b/scripts/validate-ia-machinery.mjs @@ -0,0 +1,36 @@ +import { readFileSync } from 'node:fs'; + +const requiredFiles = ['CONSTITUTION.md', 'AGENTS.md', 'RULES.md', 'CONTRIBUTING.md']; + +function read(path) { + return readFileSync(path, 'utf8'); +} + +function assertContains(path, content, expected) { + if (!content.includes(expected)) { + throw new Error(`${path} must contain: ${expected}`); + } +} + +for (const file of requiredFiles) { + read(file); +} + +const constitution = read('CONSTITUTION.md'); +assertContains('CONSTITUTION.md', constitution, 'CONTRIBUTING.md'); +assertContains('CONSTITUTION.md', constitution, 'gh-provisioner'); +assertContains('CONSTITUTION.md', constitution, 'variable "config"'); + +const agents = read('AGENTS.md'); +assertContains('AGENTS.md', agents, 'CONSTITUTION.md'); +assertContains('AGENTS.md', agents, 'CONTRIBUTING.md'); +assertContains('AGENTS.md', agents, 'RULES.md'); + +const rules = read('RULES.md'); +assertContains('RULES.md', rules, 'terraform.tfvars.json'); +assertContains('RULES.md', rules, '"config"'); +assertContains('RULES.md', rules, 'One Firestartr Kubernetes custom resource'); +assertContains('RULES.md', rules, 'Import'); +assertContains('RULES.md', rules, 'Delete'); + +console.log('IA machinery validation passed.'); From 5b62419b14850009ccce33c640114cc9c9a32ea0 Mon Sep 17 00:00:00 2001 From: Daniel Vieites Date: Thu, 7 May 2026 09:29:37 +0200 Subject: [PATCH 2/2] feat: new github-org-settings module --- .../github-org-settings/.terraform-docs.yml | 48 ++++++ modules/github-org-settings/README.md | 137 ++++++++++++++++++ .../_examples/basic/config.json | 28 ++++ .../_examples/basic/main.tf | 20 +++ modules/github-org-settings/docs/footer.md | 30 ++++ modules/github-org-settings/docs/header.md | 65 +++++++++ modules/github-org-settings/main.tf | 31 ++++ modules/github-org-settings/outputs.tf | 25 ++++ modules/github-org-settings/variables.tf | 49 +++++++ modules/github-org-settings/versions.tf | 10 ++ 10 files changed, 443 insertions(+) create mode 100644 modules/github-org-settings/.terraform-docs.yml create mode 100644 modules/github-org-settings/README.md create mode 100644 modules/github-org-settings/_examples/basic/config.json create mode 100644 modules/github-org-settings/_examples/basic/main.tf create mode 100644 modules/github-org-settings/docs/footer.md create mode 100644 modules/github-org-settings/docs/header.md create mode 100644 modules/github-org-settings/main.tf create mode 100644 modules/github-org-settings/outputs.tf create mode 100644 modules/github-org-settings/variables.tf create mode 100644 modules/github-org-settings/versions.tf diff --git a/modules/github-org-settings/.terraform-docs.yml b/modules/github-org-settings/.terraform-docs.yml new file mode 100644 index 000000000..49be8b854 --- /dev/null +++ b/modules/github-org-settings/.terraform-docs.yml @@ -0,0 +1,48 @@ +formatter: "markdown" # this is required + +version: "" + +header-from: docs/header.md +footer-from: docs/footer.md + +recursive: + enabled: false + path: modules + include-main: true + +sections: + hide: [] + show: [] + +content: "" + +output: + file: "README.md" + mode: inject + template: |- + + {{ .Content }} + + +output-values: + enabled: false + from: "" + +sort: + enabled: true + by: name + +settings: + anchor: true + color: true + default: true + description: false + escape: true + hide-empty: false + html: true + indent: 2 + lockfile: true + read-comments: true + required: true + sensitive: true + type: true diff --git a/modules/github-org-settings/README.md b/modules/github-org-settings/README.md new file mode 100644 index 000000000..f462c0b47 --- /dev/null +++ b/modules/github-org-settings/README.md @@ -0,0 +1,137 @@ + +# **GitHub Organization Settings Terraform Module** + +## Overview + +This module creates and manages the basic settings of a GitHub organization using the `github_organization_settings` Terraform resource. It is designed to be consumed by Firestartr `gh_provisioner` through a single strongly typed `config` object generated as `terraform.tfvars.json`. + +The module centralizes organization profile fields, repository creation policies, project settings, member permissions, and default security settings for new repositories. It intentionally manages one organization settings resource so it maps cleanly to one Firestartr custom resource and one Terraform state. + +This module does not create or delete a GitHub organization. It only updates settings for the organization configured in the GitHub provider. + +## Key Features + +- **gh-provisioner compatible**: Accepts one top-level `config` object suitable for generated `terraform.tfvars.json`. +- **Basic organization settings**: Manages billing email, company, blog, public email, location, display name, and description. +- **Repository creation policy**: Controls default repository permission and which repository types members can create. +- **Page and fork controls**: Manages Pages creation and private repository fork permissions. +- **Security defaults**: Configures Advanced Security, Dependabot, dependency graph, secret scanning, and push protection defaults for new repositories. + +## Firestartr Compatibility + +Expected generated input: + +```json +{ + "config": { + "billingEmail": "platform@example.com", + "defaultRepositoryPermission": "read", + "membersCanCreateRepositories": true + } +} +``` + +The matching `gh_provisioner` entity should map the Firestartr CR spec to this `config` shape and use the Terraform address `github_organization_settings.this` for imports. + +## Basic Usage + +### Using `terraform.tfvars.json` (recommended for Firestartr) + +```hcl +module "org_settings" { + source = "git::https://github.com/prefapp/tfm.git//modules/github-org-settings" + + config = var.config +} +``` + +### Inline example + +```hcl +module "org_settings" { + source = "git::https://github.com/prefapp/tfm.git//modules/github-org-settings" + + config = { + billingEmail = "platform@example.com" + name = "Example Organization" + description = "Managed by Firestartr" + defaultRepositoryPermission = "read" + membersCanCreateRepositories = true + membersCanCreatePublicRepositories = false + membersCanCreatePrivateRepositories = true + dependencyGraphEnabledForNewRepositories = true + secretScanningEnabledForNewRepositories = true + } +} +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5 | +| [github](#requirement\_github) | ~> 6.0 | + +## Providers + +| Name | Version | +|------|---------| +| [github](#provider\_github) | ~> 6.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [github_organization_settings.this](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/organization_settings) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [config](#input\_config) | GitHub organization settings configuration |
object({
billingEmail = string

company = optional(string)
blog = optional(string)
email = optional(string)
twitterUsername = optional(string)
location = optional(string)
name = optional(string)
description = optional(string)

hasOrganizationProjects = optional(bool, true)
hasRepositoryProjects = optional(bool, true)
defaultRepositoryPermission = optional(string, "read")
membersCanCreateRepositories = optional(bool, true)
membersCanCreatePublicRepositories = optional(bool, true)
membersCanCreatePrivateRepositories = optional(bool, true)
membersCanCreateInternalRepositories = optional(bool)
membersCanCreatePages = optional(bool, true)
membersCanCreatePublicPages = optional(bool, true)
membersCanCreatePrivatePages = optional(bool, true)
membersCanForkPrivateRepositories = optional(bool, false)
webCommitSignoffRequired = optional(bool, false)

advancedSecurityEnabledForNewRepositories = optional(bool, false)
dependabotAlertsEnabledForNewRepositories = optional(bool, false)
dependabotSecurityUpdatesEnabledForNewRepositories = optional(bool, false)
dependencyGraphEnabledForNewRepositories = optional(bool, false)
secretScanningEnabledForNewRepositories = optional(bool, false)
secretScanningPushProtectionEnabledForNewRepositories = optional(bool, false)
})
| n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [advanced\_security\_enabled\_for\_new\_repositories](#output\_advanced\_security\_enabled\_for\_new\_repositories) | Whether GitHub Advanced Security is enabled for new repositories. | +| [billing\_email](#output\_billing\_email) | Billing email configured for the organization. | +| [default\_repository\_permission](#output\_default\_repository\_permission) | Default repository permission configured for organization members. | +| [members\_can\_create\_repositories](#output\_members\_can\_create\_repositories) | Whether members can create repositories in the organization. | +| [organization\_settings\_id](#output\_organization\_settings\_id) | GitHub organization ID used by github\_organization\_settings. | + +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/github-org-settings/_examples): + +- [basic](https://github.com/prefapp/tfm/tree/main/modules/github-org-settings/_examples/basic) - Minimal organization settings managed through a `config.json` file. + +## Import Behavior + +This module manages one Terraform resource: + +- Terraform address: `github_organization_settings.this` +- Import ID: GitHub organization numeric ID + +The organization ID can be discovered with the GitHub REST API `GET /orgs/{org}`. Firestartr `gh_provisioner` should import using that numeric ID when adopting existing organization settings. + +## Delete Behavior + +`github_organization_settings` does not delete the GitHub organization. Terraform provider delete semantics reset organization settings to provider defaults. This is unsafe for normal Firestartr deletion because deleting the CR could unexpectedly mutate organization-wide settings. + +Firestartr integrations should treat deletion of the corresponding CR as non-destructive unmanagement, for example by removing Terraform state only instead of running `terraform destroy`. + +## Resources + +- **github\_organization\_settings**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/organization_settings) +- **GitHub REST API - Get an organization**: [Official Documentation](https://docs.github.com/en/rest/orgs/orgs#get-an-organization) +- **GitHub Terraform Provider**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs) + +## Support + +For issues, questions, or contributions related to this module, please visit the [repository's issue tracker](https://github.com/prefapp/tfm/issues). + diff --git a/modules/github-org-settings/_examples/basic/config.json b/modules/github-org-settings/_examples/basic/config.json new file mode 100644 index 000000000..ee2d6bead --- /dev/null +++ b/modules/github-org-settings/_examples/basic/config.json @@ -0,0 +1,28 @@ +{ + "config": { + "billingEmail": "platform@example.com", + "company": "Example Company", + "blog": "https://example.com", + "email": "opensource@example.com", + "location": "Remote", + "name": "Example Organization", + "description": "Managed by Firestartr", + "hasOrganizationProjects": true, + "hasRepositoryProjects": true, + "defaultRepositoryPermission": "read", + "membersCanCreateRepositories": true, + "membersCanCreatePublicRepositories": false, + "membersCanCreatePrivateRepositories": true, + "membersCanCreatePages": true, + "membersCanCreatePublicPages": true, + "membersCanCreatePrivatePages": true, + "membersCanForkPrivateRepositories": false, + "webCommitSignoffRequired": false, + "advancedSecurityEnabledForNewRepositories": false, + "dependabotAlertsEnabledForNewRepositories": true, + "dependabotSecurityUpdatesEnabledForNewRepositories": true, + "dependencyGraphEnabledForNewRepositories": true, + "secretScanningEnabledForNewRepositories": true, + "secretScanningPushProtectionEnabledForNewRepositories": true + } +} diff --git a/modules/github-org-settings/_examples/basic/main.tf b/modules/github-org-settings/_examples/basic/main.tf new file mode 100644 index 000000000..25c4b91fa --- /dev/null +++ b/modules/github-org-settings/_examples/basic/main.tf @@ -0,0 +1,20 @@ +terraform { + required_version = ">= 1.5" + + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +} + +module "org_settings" { + source = "../../" + + config = jsondecode(file("${path.module}/config.json")).config +} + +output "organization_settings_id" { + value = module.org_settings.organization_settings_id +} diff --git a/modules/github-org-settings/docs/footer.md b/modules/github-org-settings/docs/footer.md new file mode 100644 index 000000000..bb5634e70 --- /dev/null +++ b/modules/github-org-settings/docs/footer.md @@ -0,0 +1,30 @@ +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/github-org-settings/_examples): + +- [basic](https://github.com/prefapp/tfm/tree/main/modules/github-org-settings/_examples/basic) - Minimal organization settings managed through a `config.json` file. + +## Import Behavior + +This module manages one Terraform resource: + +- Terraform address: `github_organization_settings.this` +- Import ID: GitHub organization numeric ID + +The organization ID can be discovered with the GitHub REST API `GET /orgs/{org}`. Firestartr `gh_provisioner` should import using that numeric ID when adopting existing organization settings. + +## Delete Behavior + +`github_organization_settings` does not delete the GitHub organization. Terraform provider delete semantics reset organization settings to provider defaults. This is unsafe for normal Firestartr deletion because deleting the CR could unexpectedly mutate organization-wide settings. + +Firestartr integrations should treat deletion of the corresponding CR as non-destructive unmanagement, for example by removing Terraform state only instead of running `terraform destroy`. + +## Resources + +- **github_organization_settings**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/organization_settings) +- **GitHub REST API - Get an organization**: [Official Documentation](https://docs.github.com/en/rest/orgs/orgs#get-an-organization) +- **GitHub Terraform Provider**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs) + +## Support + +For issues, questions, or contributions related to this module, please visit the [repository's issue tracker](https://github.com/prefapp/tfm/issues). diff --git a/modules/github-org-settings/docs/header.md b/modules/github-org-settings/docs/header.md new file mode 100644 index 000000000..037bf1454 --- /dev/null +++ b/modules/github-org-settings/docs/header.md @@ -0,0 +1,65 @@ +# **GitHub Organization Settings Terraform Module** + +## Overview + +This module creates and manages the basic settings of a GitHub organization using the `github_organization_settings` Terraform resource. It is designed to be consumed by Firestartr `gh_provisioner` through a single strongly typed `config` object generated as `terraform.tfvars.json`. + +The module centralizes organization profile fields, repository creation policies, project settings, member permissions, and default security settings for new repositories. It intentionally manages one organization settings resource so it maps cleanly to one Firestartr custom resource and one Terraform state. + +This module does not create or delete a GitHub organization. It only updates settings for the organization configured in the GitHub provider. + +## Key Features + +- **gh-provisioner compatible**: Accepts one top-level `config` object suitable for generated `terraform.tfvars.json`. +- **Basic organization settings**: Manages billing email, company, blog, public email, location, display name, and description. +- **Repository creation policy**: Controls default repository permission and which repository types members can create. +- **Page and fork controls**: Manages Pages creation and private repository fork permissions. +- **Security defaults**: Configures Advanced Security, Dependabot, dependency graph, secret scanning, and push protection defaults for new repositories. + +## Firestartr Compatibility + +Expected generated input: + +```json +{ + "config": { + "billingEmail": "platform@example.com", + "defaultRepositoryPermission": "read", + "membersCanCreateRepositories": true + } +} +``` + +The matching `gh_provisioner` entity should map the Firestartr CR spec to this `config` shape and use the Terraform address `github_organization_settings.this` for imports. + +## Basic Usage + +### Using `terraform.tfvars.json` (recommended for Firestartr) + +```hcl +module "org_settings" { + source = "git::https://github.com/prefapp/tfm.git//modules/github-org-settings" + + config = var.config +} +``` + +### Inline example + +```hcl +module "org_settings" { + source = "git::https://github.com/prefapp/tfm.git//modules/github-org-settings" + + config = { + billingEmail = "platform@example.com" + name = "Example Organization" + description = "Managed by Firestartr" + defaultRepositoryPermission = "read" + membersCanCreateRepositories = true + membersCanCreatePublicRepositories = false + membersCanCreatePrivateRepositories = true + dependencyGraphEnabledForNewRepositories = true + secretScanningEnabledForNewRepositories = true + } +} +``` diff --git a/modules/github-org-settings/main.tf b/modules/github-org-settings/main.tf new file mode 100644 index 000000000..9200ed1b7 --- /dev/null +++ b/modules/github-org-settings/main.tf @@ -0,0 +1,31 @@ +resource "github_organization_settings" "this" { + billing_email = var.config.billingEmail + company = var.config.company + blog = var.config.blog + email = var.config.email + twitter_username = var.config.twitterUsername + location = var.config.location + name = var.config.name + description = var.config.description + + has_organization_projects = var.config.hasOrganizationProjects + has_repository_projects = var.config.hasRepositoryProjects + default_repository_permission = var.config.defaultRepositoryPermission + + members_can_create_repositories = var.config.membersCanCreateRepositories + members_can_create_public_repositories = var.config.membersCanCreatePublicRepositories + members_can_create_private_repositories = var.config.membersCanCreatePrivateRepositories + members_can_create_internal_repositories = var.config.membersCanCreateInternalRepositories + members_can_create_pages = var.config.membersCanCreatePages + members_can_create_public_pages = var.config.membersCanCreatePublicPages + members_can_create_private_pages = var.config.membersCanCreatePrivatePages + members_can_fork_private_repositories = var.config.membersCanForkPrivateRepositories + web_commit_signoff_required = var.config.webCommitSignoffRequired + + advanced_security_enabled_for_new_repositories = var.config.advancedSecurityEnabledForNewRepositories + dependabot_alerts_enabled_for_new_repositories = var.config.dependabotAlertsEnabledForNewRepositories + dependabot_security_updates_enabled_for_new_repositories = var.config.dependabotSecurityUpdatesEnabledForNewRepositories + dependency_graph_enabled_for_new_repositories = var.config.dependencyGraphEnabledForNewRepositories + secret_scanning_enabled_for_new_repositories = var.config.secretScanningEnabledForNewRepositories + secret_scanning_push_protection_enabled_for_new_repositories = var.config.secretScanningPushProtectionEnabledForNewRepositories +} diff --git a/modules/github-org-settings/outputs.tf b/modules/github-org-settings/outputs.tf new file mode 100644 index 000000000..445bb1123 --- /dev/null +++ b/modules/github-org-settings/outputs.tf @@ -0,0 +1,25 @@ +output "organization_settings_id" { + description = "GitHub organization ID used by github_organization_settings." + value = github_organization_settings.this.id +} + +output "billing_email" { + description = "Billing email configured for the organization." + value = github_organization_settings.this.billing_email + sensitive = true +} + +output "default_repository_permission" { + description = "Default repository permission configured for organization members." + value = github_organization_settings.this.default_repository_permission +} + +output "members_can_create_repositories" { + description = "Whether members can create repositories in the organization." + value = github_organization_settings.this.members_can_create_repositories +} + +output "advanced_security_enabled_for_new_repositories" { + description = "Whether GitHub Advanced Security is enabled for new repositories." + value = github_organization_settings.this.advanced_security_enabled_for_new_repositories +} diff --git a/modules/github-org-settings/variables.tf b/modules/github-org-settings/variables.tf new file mode 100644 index 000000000..d0dde70eb --- /dev/null +++ b/modules/github-org-settings/variables.tf @@ -0,0 +1,49 @@ +variable "config" { + description = "GitHub organization settings configuration" + type = object({ + billingEmail = string + + company = optional(string) + blog = optional(string) + email = optional(string) + twitterUsername = optional(string) + location = optional(string) + name = optional(string) + description = optional(string) + + hasOrganizationProjects = optional(bool, true) + hasRepositoryProjects = optional(bool, true) + defaultRepositoryPermission = optional(string, "read") + membersCanCreateRepositories = optional(bool, true) + membersCanCreatePublicRepositories = optional(bool, true) + membersCanCreatePrivateRepositories = optional(bool, true) + membersCanCreateInternalRepositories = optional(bool) + membersCanCreatePages = optional(bool, true) + membersCanCreatePublicPages = optional(bool, true) + membersCanCreatePrivatePages = optional(bool, true) + membersCanForkPrivateRepositories = optional(bool, false) + webCommitSignoffRequired = optional(bool, false) + + advancedSecurityEnabledForNewRepositories = optional(bool, false) + dependabotAlertsEnabledForNewRepositories = optional(bool, false) + dependabotSecurityUpdatesEnabledForNewRepositories = optional(bool, false) + dependencyGraphEnabledForNewRepositories = optional(bool, false) + secretScanningEnabledForNewRepositories = optional(bool, false) + secretScanningPushProtectionEnabledForNewRepositories = optional(bool, false) + }) + + validation { + condition = length(trimspace(var.config.billingEmail)) > 0 + error_message = "config.billingEmail must be a non-empty email address." + } + + validation { + condition = can(regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", var.config.billingEmail)) + error_message = "config.billingEmail must look like a valid email address." + } + + validation { + condition = contains(["read", "write", "admin", "none"], var.config.defaultRepositoryPermission) + error_message = "config.defaultRepositoryPermission must be one of: read, write, admin, none." + } +} diff --git a/modules/github-org-settings/versions.tf b/modules/github-org-settings/versions.tf new file mode 100644 index 000000000..9afbac820 --- /dev/null +++ b/modules/github-org-settings/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5" + + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +}