diff --git a/modules/gh-membership/.terraform-docs.yml b/modules/gh-membership/.terraform-docs.yml new file mode 100644 index 000000000..3a69365ff --- /dev/null +++ b/modules/gh-membership/.terraform-docs.yml @@ -0,0 +1,48 @@ +formatter: "markdown" + +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/gh-membership/README.md b/modules/gh-membership/README.md new file mode 100644 index 000000000..f2d33cea9 --- /dev/null +++ b/modules/gh-membership/README.md @@ -0,0 +1,106 @@ + +# **GitHub Membership Terraform Module** + +## Overview + +This module manages GitHub organization-level membership (admin/member) and team memberships using a single strongly-typed `config` object. + +It is designed for Prefapp’s Internal Developer Platform and automated user/team provisioning pipelines. The module accepts input directly from external programs via JSON. + +## Key Features + +- **Organization role**: Assign `member` or `admin` at organization level +- **Team relationships**: Add users to teams with `member` or `maintainer` roles +- **Single config object**: Everything in one `config` variable +- **Full validation**: Role enforcement and required fields +- **JSON-native**: Perfect for programmatic generation + +## Basic Usage + +### Using `terraform.tfvars.json` (recommended) + +```hcl +module "membership" { + source = "git::https://github.com/prefapp/tfm.git//modules/gh-membership" + + config = var.config +} + +#### Inline example +```hcl +module "membership" { + source = "git::https://github.com/prefapp/tfm.git//modules/gh-membership" + + config = { + user = { + username = "johndoe" + role = "admin" + } + relationships = [ + { + username = "johndoe" + teamId = "foo-all" + role = "member" + } + ] + } +} +``` +``` + +## Requirements + +| Name | Version | +|------|---------| +| [github](#requirement\_github) | ~> 6.0 | + +## Providers + +| Name | Version | +|------|---------| +| [github](#provider\_github) | ~> 6.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [github_membership.this](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/membership) | resource | +| [github_team_membership.relationships](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_membership) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [config](#input\_config) | GitHub membership configuration (organization role + team relationships) |
object({
relationships = optional(list(object({
username = string
teamId = string # team slug (e.g. "jvazquez-prefapp-all")
role = optional(string, "member") # member | maintainer
})), [])

user = optional(object({
username = string
role = optional(string, "member") # member | admin
}))
})
| n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [organization\_membership](#output\_organization\_membership) | Organization-level membership | +| [team\_memberships](#output\_team\_memberships) | Team memberships created | +| [user](#output\_user) | User details | + +### 3. `docs/footer.md` +```markdown +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/gh-membership/_examples): + +- [basic](https://github.com/prefapp/tfm/tree/main/modules/gh-membership/_examples/basic) - Organization membership + team relationship + +## Resources + +- **github_membership**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/membership) +- **github_team_membership**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_membership) +- **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). +``` + \ No newline at end of file diff --git a/modules/gh-membership/_examples/basic/config.json b/modules/gh-membership/_examples/basic/config.json new file mode 100644 index 000000000..e9ac242bd --- /dev/null +++ b/modules/gh-membership/_examples/basic/config.json @@ -0,0 +1,15 @@ +{ + "config": { + "relationships": [ + { + "username": "johndoe", + "teamId": "foo-all", + "role": "member" + } + ], + "user": { + "username": "johndoe", + "role": "admin" + } + } +} diff --git a/modules/gh-membership/_examples/basic/main.tf b/modules/gh-membership/_examples/basic/main.tf new file mode 100644 index 000000000..6dfdb96e0 --- /dev/null +++ b/modules/gh-membership/_examples/basic/main.tf @@ -0,0 +1,22 @@ +terraform { + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +} + +module "membership" { + source = "../../" + + config = jsondecode(file("${path.module}/config.json")).config +} + +output "user_managed" { + value = module.membership.organization_membership +} + +output "team_memberships" { + value = module.membership.team_memberships +} diff --git a/modules/gh-membership/docs/footer.md b/modules/gh-membership/docs/footer.md new file mode 100644 index 000000000..0d7facfaa --- /dev/null +++ b/modules/gh-membership/docs/footer.md @@ -0,0 +1,20 @@ + +### 3. `docs/footer.md` +```markdown +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/gh-membership/_examples): + +- [basic](https://github.com/prefapp/tfm/tree/main/modules/gh-membership/_examples/basic) - Organization membership + team relationship + +## Resources + +- **github_membership**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/membership) +- **github_team_membership**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_membership) +- **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/gh-membership/docs/header.md b/modules/gh-membership/docs/header.md new file mode 100644 index 000000000..c830ce6a0 --- /dev/null +++ b/modules/gh-membership/docs/header.md @@ -0,0 +1,47 @@ +# **GitHub Membership Terraform Module** + +## Overview + +This module manages GitHub organization-level membership (admin/member) and team memberships using a single strongly-typed `config` object. + +It is designed for Prefapp’s Internal Developer Platform and automated user/team provisioning pipelines. The module accepts input directly from external programs via JSON. + +## Key Features + +- **Organization role**: Assign `member` or `admin` at organization level +- **Team relationships**: Add users to teams with `member` or `maintainer` roles +- **Single config object**: Everything in one `config` variable +- **Full validation**: Role enforcement and required fields +- **JSON-native**: Perfect for programmatic generation + +## Basic Usage + +### Using `terraform.tfvars.json` (recommended) + +```hcl +module "membership" { + source = "git::https://github.com/prefapp/tfm.git//modules/gh-membership" + + config = var.config +} + +#### Inline example +```hcl +module "membership" { + source = "git::https://github.com/prefapp/tfm.git//modules/gh-membership" + + config = { + user = { + username = "johndoe" + role = "admin" + } + relationships = [ + { + username = "johndoe" + teamId = "foo-all" + role = "member" + } + ] + } +} +``` diff --git a/modules/gh-membership/main.tf b/modules/gh-membership/main.tf new file mode 100644 index 000000000..2b5923daa --- /dev/null +++ b/modules/gh-membership/main.tf @@ -0,0 +1,18 @@ +# Organization membership (admin / member) +resource "github_membership" "this" { + count = var.config.user != null ? 1 : 0 + + username = var.config.user.username + role = var.config.user.role +} + +# Team memberships +resource "github_team_membership" "relationships" { + for_each = { + for r in var.config.relationships : "${r.username}-${r.teamId}" => r + } + + team_id = each.value.teamId # accepts slug directly + username = each.value.username + role = each.value.role +} diff --git a/modules/gh-membership/outputs.tf b/modules/gh-membership/outputs.tf new file mode 100644 index 000000000..213676058 --- /dev/null +++ b/modules/gh-membership/outputs.tf @@ -0,0 +1,23 @@ +output "organization_membership" { + description = "Organization-level membership" + value = var.config.user != null ? { + username = github_membership.this[0].username + role = github_membership.this[0].role + } : null +} + +output "team_memberships" { + description = "Team memberships created" + value = [ + for r in var.config.relationships : { + username = r.username + teamId = r.teamId + role = r.role + } + ] +} + +output "user" { + description = "User details" + value = var.config.user +} diff --git a/modules/gh-membership/variables.tf b/modules/gh-membership/variables.tf new file mode 100644 index 000000000..56e64b9d4 --- /dev/null +++ b/modules/gh-membership/variables.tf @@ -0,0 +1,27 @@ +variable "config" { + description = "GitHub membership configuration (organization role + team relationships)" + type = object({ + relationships = optional(list(object({ + username = string + teamId = string # team slug (e.g. "jvazquez-prefapp-all") + role = optional(string, "member") # member | maintainer + })), []) + + user = optional(object({ + username = string + role = optional(string, "member") # member | admin + })) + }) + + validation { + condition = alltrue([ + for r in var.config.relationships : contains(["member", "maintainer"], r.role) + ]) + error_message = "relationship.role must be 'member' or 'maintainer'." + } + + validation { + condition = var.config.user == null || contains(["member", "admin"], var.config.user.role) + error_message = "user.role must be 'member' or 'admin'." + } +} diff --git a/modules/gh-membership/versions.tf b/modules/gh-membership/versions.tf new file mode 100644 index 000000000..30369f8ed --- /dev/null +++ b/modules/gh-membership/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +} + diff --git a/release-please-config.json b/release-please-config.json index b59f07b71..c751c4df4 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -151,5 +151,8 @@ "modules/aws-secretsmanager-replication": { "package-name": "aws-secretsmanager-replication" } + "modules/gh-membership": { + "package-name": "gh-membership" + } } }