Skip to content

feat: initial version #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

gberenice
Copy link
Member

@gberenice gberenice commented Apr 18, 2025

what

  • This introduces a new GitHub Teams Terraform Module child module for managing GitHub teams, memberships, and repository collaborators.
  • Key Features:
    • Team Management: name and description, privacy settings, parent team relationships, LDAP integration, default maintainer settings.
    • Membership Control: team administrators and regular members; membership invitations.
    • Repository Collaboration: granular permission levels.
    • Review Request Delegation: algorithm selection for reviewer assignment, member count control.

why

  • We want to treat our teams as IaC and open-source this.

references

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive management of GitHub teams, memberships, and repository collaborators, including support for team privacy, membership roles, and review request delegation.
    • Added enforcement to ensure organization owners are always maintainers in their teams.
    • Provided detailed outputs for teams, memberships, collaborators, and team settings.
  • Bug Fixes

    • Improved input validation for team, membership, and collaborator configurations to prevent misconfiguration.
  • Documentation

    • Rewrote the README with detailed usage instructions, input descriptions, and validation rules.
  • Chores

    • Updated dependencies and tool versions for Terraform, providers, and related tooling.
    • Added automated tests and a new CI workflow for validating Terraform configurations.
  • Refactor

    • Replaced previous example and template files with new, relevant examples and removed obsolete context and output files.

Copy link

coderabbitai bot commented Apr 18, 2025

Walkthrough

This update introduces a comprehensive overhaul of the Terraform module, shifting its focus to managing GitHub teams, memberships, and repository collaborators. The changes include new and updated input variables with validation, expanded resource definitions for teams, memberships, collaborators, and team settings, and detailed outputs. The module now enforces organization owner roles and supports review request delegation. Supporting files such as documentation, examples, tests, and CI/CD workflows are also updated or added to match the new functionality. Legacy context and random resource logic are removed to streamline the module’s purpose.

Changes

File(s) Summary of Changes
.github/workflows/test.yaml Added a new GitHub Actions workflow to run Terraform tests for both "tofu" and "terraform" targets using matrix strategy, caching, and fine-grained permissions.
.gitignore Removed ignore rules related to "build-harness" directories and files.
.trunk/trunk.yaml Updated Trunk CLI and plugin versions; added and upgraded linters.
README.md Rewrote documentation to reflect the new GitHub teams module, including usage, input descriptions, validation rules, and updated badges/URLs.
aqua.yaml Upgraded aqua registry and package versions for terraform-docs, hashicorp terraform, and opentofu.
checks.tf Added logic and checks to enforce that GitHub organization owners always have the "maintainer" role in teams and to validate existence of all referenced users.
context.tf Deleted file. Removed all context, labeling, and tagging variable definitions and module instantiation.
examples/complete/main.tf Replaced with a full example using the new module interface, specifying organization, teams, members, review delegation, and repository collaborators.
examples/complete/outputs.tf, examples/complete/variables.tf Deleted files. Previously contained only comments, no functional code.
examples/complete/versions.tf Added file specifying required Terraform version (exact 1.9.0) and the GitHub provider (approx 6.0).
main.tf Removed random resource; implemented dynamic creation of GitHub teams, memberships, collaborators, and team settings using input variables and computed maps.
outputs.tf Replaced single output with multiple structured outputs for teams, memberships, collaborators, and team settings.
tests/main.tftest.hcl Added test file with mock provider, input variables, and multiple test runs validating teams, memberships, privacy, collaborator permissions, organization membership roles, downgrade behavior, and membership consistency.
variables.tf Removed simple numeric variable; added structured variables for organization, repository collaborators, and teams with detailed validation logic including membership consistency checks.
versions.tf Updated required Terraform version to 1.7+ and switched required provider to "github" (integrations/github, 6.0+).
.github/CODEOWNERS Changed default code owners team from @masterpointio/masterpoint-internal to @masterpointio/masterpoint-open-source.
.terraform-docs.yaml Upgraded terraform-docs version to 0.20.0; disabled recursive scanning; added explicit path setting.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Module
    participant GitHub

    User->>Module: Provide organization, teams, collaborators inputs
    Module->>GitHub: Create teams
    Module->>GitHub: Add team members with roles
    Module->>GitHub: Set team settings (review delegation)
    Module->>GitHub: Add repository collaborators
    Module->>GitHub: Accept collaborator invitations
    Module->>Module: Enforce org owner as maintainer (check)
    Module->>User: Output teams, memberships, collaborators, settings
Loading

Poem

🎉
A module reborn, for teams and for code,
GitHub connections in Terraform mode!
Members and roles, with checks standing tall,
Reviewers delegated, permissions for all.
Outputs now structured, examples anew—
Collaboration empowered, the module breaks through!
🚀

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🔭 Outside diff range comments (1)
tests/main.tftest.hcl (1)

1-137: 🛠️ Refactor suggestion

Add review request delegation tests
The new github_team_settings resource for delegation isn’t covered here. Please add a run block asserting github_team_settings.default["platform-engineers"].review_request_delegation.* attributes (e.g., algorithm, member_count, notify).

🧹 Nitpick comments (11)
.trunk/trunk.yaml (1)

23-23: Add Renovate linter v39.245.3
Including Renovate will automate dependency updates—consider verifying any custom Renovate configuration for compatibility.

aqua.yaml (1)

4-8: Optional: Enforce package checksums
We currently have the checksum block commented out. For stronger supply‑chain security, consider enabling Aqua’s checksum validation (uncomment and adjust as needed) to ensure the integrity of downloaded packages.

variables.tf (2)

1-4: Consider adding non‑empty validation for github_organization
Right now any string (including "") is allowed. To catch misconfigurations early, you could add:

validation {
  condition     = length(var.github_organization) > 0
  error_message = "github_organization must not be empty"
}

9-10: Clarify naming of permission_diff_suppression
The field name is a bit verbose and could be confusing. Consider renaming to something like suppress_permission_diff for better readability and consistency.

examples/complete/versions.tf (1)

2-2: Align version constraint style (optional)
Root uses >= 1.7 while the example uses ~> 1.7. For consistency, consider matching the root constraint or noting the deliberate difference.

.github/workflows/test.yaml (3)

1-8: Consider adding a manual trigger
Including workflow_dispatch under on: enables maintainers to run the workflow on demand.


24-31: Pin action versions via tags
Instead of SHA commits, consider using the latest semver tags (e.g., actions/checkout@v4) for readability and maintainability while still ensuring stability.


44-45: Add explicit step names
Naming these steps (e.g., name: terraform init, name: terraform test) improves log readability.

examples/complete/main.tf (1)

6-31: Demonstrate optional attributes in example
Consider illustrating optional fields (e.g., parent_team_id, ldap_dn, create_default_maintainer) in the example to help users explore full module capabilities.

checks.tf (1)

23-39: Enhance check metadata
You might consider adding a description and explicit severity argument to the check block for better clarity in CI failures.

main.tf (1)

1-28: Flattening locals is effective
The merge approach works, though you could also use a single nested for comprehension for brevity.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91f9b90 and fee3afa.

📒 Files selected for processing (16)
  • .github/workflows/test.yaml (1 hunks)
  • .gitignore (0 hunks)
  • .trunk/trunk.yaml (2 hunks)
  • README.md (2 hunks)
  • aqua.yaml (1 hunks)
  • checks.tf (1 hunks)
  • context.tf (0 hunks)
  • examples/complete/main.tf (1 hunks)
  • examples/complete/outputs.tf (0 hunks)
  • examples/complete/variables.tf (0 hunks)
  • examples/complete/versions.tf (1 hunks)
  • main.tf (1 hunks)
  • outputs.tf (1 hunks)
  • tests/main.tftest.hcl (1 hunks)
  • variables.tf (1 hunks)
  • versions.tf (1 hunks)
💤 Files with no reviewable changes (4)
  • .gitignore
  • examples/complete/outputs.tf
  • examples/complete/variables.tf
  • context.tf
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.tf`: You're a Terraform expert who has thoroughly studied all the documentation from Hashicorp https://developer.hashicorp.com/terraform/docs and OpenTofu https://opentofu.or...

**/*.tf: You're a Terraform expert who has thoroughly studied all the documentation from Hashicorp https://developer.hashicorp.com/terraform/docs and OpenTofu https://opentofu.org/docs/.
You have a strong grasp of Terraform syntax and prioritize providing accurate and insightful code suggestions.
As a fan of the Cloud Posse / SweetOps ecosystem, you incorporate many of their best practices https://docs.cloudposse.com/best-practices/terraform/ while balancing them with general Terraform guidelines.

  • examples/complete/main.tf
  • versions.tf
  • examples/complete/versions.tf
  • checks.tf
  • outputs.tf
  • main.tf
  • variables.tf
🧠 Learnings (1)
versions.tf (1)
Learnt from: gberenice
PR: masterpointio/terraform-spacelift-automation#3
File: modules/spacelift-automation/variables.tf:0-0
Timestamp: 2024-10-30T16:38:33.362Z
Learning: Spacelift requires a specific Terraform version number; it does not support version constraints like "~> 1.7.0".
🔇 Additional comments (31)
.trunk/trunk.yaml (4)

5-5: Bump Trunk CLI version to 1.22.12
Updating the CLI version ensures access to the latest features and fixes.


10-10: Upgrade trunk plugin source to v1.6.8
Aligns the plugin reference with the latest release for improved stability.


26-26: Upgrade Checkov to v3.2.405
This patch version includes critical policy checks. Please confirm that all existing Terraform policies still pass in CI.


32-32: Upgrade TruffleHog to v3.88.23
The new release improves secret-detection accuracy. Ensure no new findings are introduced in your pipelines.

aqua.yaml (1)

11-15: Approve version bumps
The Aqua packages have been correctly updated to v4.346.0, terraform‑docs v0.20.0, Terraform v1.11.4, and OpenTofu v1.9.0, which align with the module’s new requirements.

versions.tf (2)

2-2: Update Terraform version constraint
Raising the required_version to >= 1.7 correctly aligns with the usage of new object defaults and validation features.


5-7: Switch to GitHub provider
Declaring the github provider from integrations/github at >= 6.0 is appropriate for the new resources.

README.md (3)

3-3: Approve updated module title
The new # terraform-github-teams header clearly reflects the module’s purpose.


29-61: Verify example source address
The example uses source = "masterpointio/teams/github". Please confirm this matches the published module on the Terraform Registry or update it to the correct path (e.g., "masterpointio/terraform-github-teams/github").


144-149: Approve updated badge links
The release, contributor, and issue links now point to terraform-github-teams, matching the new repository name.

.github/workflows/test.yaml (2)

20-23: Matrix strategy is well-defined
Using tf: [tofu, terraform] cleanly tests both CLI variants. Nicely done!


35-42: Aqua installation and tagging look good
Pinning both the installer and version ensures reproducible CLI environments.

examples/complete/main.tf (2)

1-5: Example module block is correctly configured
The source, github_organization, and module naming align well with usage patterns.


33-40: Repository collaborator block is clear
Your example for repository_collaborators shows realistic permission usage. Nice work!

checks.tf (3)

1-7: Organization owners set computed correctly
Using toset on the comprehension ensures uniqueness for the owner logins.


8-17: Invalid memberships collection is straightforward
The comprehension cleanly builds the error list for owners not in maintainer role.


19-21: GitHub organization data source is used appropriately
Fetching the current org users enables dynamic enforcement.

tests/main.tftest.hcl (6)

1-4: Mock provider alias is set correctly
This sets up the mock GitHub provider for isolated testing.


5-54: Variable inputs are comprehensive
Defining github_organization, teams, and repository_collaborators aligns well with module inputs.


56-72: Developer team validation is solid
Checks for default privacy and name ensure core team settings behave as expected.


74-90: Platform engineers test covers privacy override
Good to see overrides validated for non-default configurations.


92-113: Team membership assertions look correct
Validating both maintainer and member roles helps catch misconfigurations early.


115-136: Repository collaborator assertions are on point
Testing multiple repos and permission levels provides strong coverage.

main.tf (5)

30-39: Dynamic team creation is well-structured
Iterating with for_each = var.teams and mapping attributes aligns with best practices.


41-47: Team memberships are correctly provisioned
Using local.memberships_map ensures unique keys and clean iteration.


49-56: Repository collaborator provisioning is solid
Including permission_diff_suppression covers drift scenarios nicely.


58-62: Invitation accepter ensures idempotence
Automatically accepting invites completes the collaboration flow.


64-73: Team settings delegation is implemented correctly
The conditional for_each on teams_with_delegation cleanly scopes this resource.

outputs.tf (3)

1-11: Approve the structured teams output

The teams block cleanly maps each GitHub team name to its core properties (id, slug, name, privacy). This provides a clear, typed map that's easy to consume in downstream modules.


13-22: Approve the team_memberships output mapping

Using the resource instance key (e.g. team:username) as the map key clearly identifies each membership. Exposing team_id, username, and role aligns well with expected IaC patterns.


24-33: Approve the repository_collaborators output mapping

This for‑expression cleanly surfaces collaborator permissions keyed by repo:username. It’s consistent with the other outputs and makes it straightforward to verify or reference collaborator settings.

Comment on lines +95 to +98
for team_key, team in var.teams :
lower(replace(replace(team.name, " ", "-"), "/[^a-zA-Z0-9-]/", "")) == team_key
])
error_message = "Team map key must match the slugified team name (lowercase, spaces replaced with hyphens, special characters removed)."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix slugification regex
The replace(..., "/[^a-zA-Z0-9-]/", "") pattern will not strip special characters because Terraform’s replace expects the raw regex (no / delimiters). This will break the slug check.

Apply this diff:

- lower(replace(replace(team.name, " ", "-"), "/[^a-zA-Z0-9-]/", "")) == team_key
+ lower(replace(replace(team.name, " ", "-"), "[^a-zA-Z0-9-]", ""))      == team_key
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for team_key, team in var.teams :
lower(replace(replace(team.name, " ", "-"), "/[^a-zA-Z0-9-]/", "")) == team_key
])
error_message = "Team map key must match the slugified team name (lowercase, spaces replaced with hyphens, special characters removed)."
for team_key, team in var.teams :
lower(replace(replace(team.name, " ", "-"), "[^a-zA-Z0-9-]", "")) == team_key
])
error_message = "Team map key must match the slugified team name (lowercase, spaces replaced with hyphens, special characters removed)."

Comment on lines +9 to +15
permissions:
actions: read
checks: write
contents: read
id-token: write
pull-requests: read

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Minimize id-token permission
If you’re not leveraging OIDC, you can restrict id-token to read or remove it to adhere to the principle of least privilege.

@gberenice gberenice requested a review from a team April 18, 2025 13:16
Copy link
Member

@Gowiem Gowiem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gberenice this is looking awesome. My comments are mostly questions + small nits.

Let's roll this out in mp-infra so we can see what this looks like for real.

create_default_maintainer = each.value.create_default_maintainer
}

resource "github_team_membership" "default" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so I'm clear: Will this invite a new team member to an organization if they're not currently in the org? I think we should make sure that is clear in the README as well -- important info.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is a team membership. I added a new resource to manage org membership and updated the doc. Le mw know your thoughts!

output "teams" {
description = "Map of team names to their properties including ID and slug"
value = {
for name, team in github_team.default : name => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this different from value = github_team.default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is that value = github_team.default would output ALL attributes of the resource, while the current format provides a cleaner output by explicitly selecting the most important (IMO) attributes. Does that make sense?

Comment on lines +95 to +97
providers = {
github = github.mock
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha this makes it so the GH provider is not initialized at all? So we're just testing the logic and not the underlying provider's logic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we're using a completely mocked version of the GitHub provider, not the real one: no actual GitHub API calls are made, so no real resources are created/modified/deleted. I feel like this is safer, because we're not actually making any changes to GitHub org membership and team structure.
This still allows us to test the module's logic and transformations, and expected resource configurations. How do you feel about this approach?

for team_key, team in var.teams :
lower(replace(replace(team.name, " ", "-"), "/[^a-zA-Z0-9-]/", "")) == team_key
])
error_message = "Team map key must match the slugified team name (lowercase, spaces replaced with hyphens, special characters removed)."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this validation? The key doesn't seem to be used AFAICT so why do they need to match + follow a specific pattern.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this validation to ensure that the team's map key matches GitHub's slug format for team URLs and API endpoints. As GitHub automatically generates a slug for each team (used in URLs and API calls), I'd like to keep the consistency between our module and GitHub's API.

name = "Developers"
members = [
{
username = "rachel"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the Friends reference

default = 2
variable "github_organization" {
type = string
description = "The GitHub organization name"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given integrations/terraform-provider-github#241, I wonder if we should add that github_organization is case insensitive?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think this is fixed. I don't see drifts in mp-infra root module, and we have there various cases.
See https://github.com/masterpointio/mp-infra/pull/60


variable "repository_collaborators" {
type = map(list(object({
username = string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given integrations/terraform-provider-github#241, I wonder if we should add that username is case insensitive?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
tests/main.tftest.hcl (1)

1-223: 🛠️ Refactor suggestion

Add tests for review request delegation
There are currently no assertions for the github_team_settings resource with delegation. It’s valuable to validate that the delegation settings are applied correctly. For example:

run "validate_review_request_delegation_settings" {
  command   = plan
  providers = { github = github.mock }

  assert {
    condition     = github_team_settings.default["platform-engineers"].review_request_delegation.algorithm == "ROUND_ROBIN"
    error_message = "Delegation algorithm should be ROUND_ROBIN"
  }
  assert {
    condition     = github_team_settings.default["platform-engineers"].review_request_delegation.member_count == 2
    error_message = "Delegation member_count should be 2"
  }
  assert {
    condition     = github_team_settings.default["platform-engineers"].review_request_delegation.notify == true
    error_message = "Delegation notify flag should be true"
  }
}
♻️ Duplicate comments (1)
variables.tf (1)

115-120: Fix slugification regex
Terraform’s replace expects raw regex without / delimiters, so the current pattern "/[^a-zA-Z0-9-]/" won’t strip special characters. Apply:

- lower(replace(replace(team.name, " ", "-"), "/[^a-zA-Z0-9-]/", "")) == team_key
+ lower(replace(replace(team.name, " ", "-"), "[^a-zA-Z0-9-]", ""))      == team_key
🧹 Nitpick comments (6)
examples/complete/versions.tf (1)

1-3: Loosen Terraform version constraint
Pinning required_version = "1.9.0" forces exactly that version. Consider allowing patch upgrades (or future minors) by using a range or pessimistic constraint, for example:

required_version = "~> 1.9.0"            # allows 1.9.x
# or
required_version = ">= 1.9.0, < 2.0.0"  # allows any 1.x
variables.tf (2)

1-4: Add non-empty validation for github_organization
To catch empty or unintended values early, you can enforce that the organization name is not blank:

variable "github_organization" {
  type        = string
  description = "The GitHub organization name"

  validation {
    condition     = var.github_organization != ""
    error_message = "github_organization must not be empty."
  }
}

73-81: Simplify delegation validation with coalesce
You can make the review request delegation checks more concise by leveraging coalesce:

validation {
  condition = alltrue([
    for team in values(var.teams) :
    contains(
      ["ROUND_ROBIN", "LOAD_BALANCE"],
      coalesce(team.review_request_delegation.algorithm, "ROUND_ROBIN")
    )
  ])
  error_message = "Review request delegation algorithm must be either 'ROUND_ROBIN' or 'LOAD_BALANCE'."
}

Likewise, use coalesce(team.review_request_delegation.member_count, 1) >= 1 for the member_count check.

examples/complete/main.tf (1)

6-23: Explicitly show membership toggle
Since organization_memberships_enabled defaults to true, consider adding it in the example to clarify that Terraform will manage org memberships:

organization_memberships_enabled = true
main.tf (1)

53-61: Optional: Explicit depends_on for clarity
Although Terraform infers the dependency from the reference to github_team.default in github_team_membership, you may add an explicit depends_on = [github_team.default] to make the ordering clear:

resource "github_team_membership" "default" {
  for_each = local.memberships_map
  team_id   = each.value.team_id
  username  = each.value.username
  role      = each.value.role

  depends_on = [github_team.default]
}
checks.tf (1)

18-28: Consider stable ordering for user list

To ensure predictable ordering in data.github_users, wrap the distinct list in sort(...) for reproducible data lookups and test consistency:

- all_usernames = distinct(concat(
+ all_usernames = sort(distinct(concat(
     # Organization members
     [for member in var.organization_memberships : member.username],
     # Repository collaborators
     flatten([
       for collaborators in var.repository_collaborators : [
         for collab in collaborators : collab.username
       ]
     ])
- ))
+ )))
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d986567 and d0dd4a8.

📒 Files selected for processing (9)
  • .terraform-docs.yaml (1 hunks)
  • README.md (2 hunks)
  • checks.tf (1 hunks)
  • examples/complete/main.tf (1 hunks)
  • examples/complete/versions.tf (1 hunks)
  • main.tf (1 hunks)
  • outputs.tf (1 hunks)
  • tests/main.tftest.hcl (1 hunks)
  • variables.tf (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .terraform-docs.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.tf`: You're a Terraform expert who has thoroughly studied all the documentation from Hashicorp https://developer.hashicorp.com/terraform/docs and OpenTofu https://opentofu.or...

**/*.tf: You're a Terraform expert who has thoroughly studied all the documentation from Hashicorp https://developer.hashicorp.com/terraform/docs and OpenTofu https://opentofu.org/docs/.
You have a strong grasp of Terraform syntax and prioritize providing accurate and insightful code suggestions.
As a fan of the Cloud Posse / SweetOps ecosystem, you incorporate many of their best practices https://docs.cloudposse.com/best-practices/terraform/ while balancing them with general Terraform guidelines.

  • examples/complete/versions.tf
  • outputs.tf
  • checks.tf
  • examples/complete/main.tf
  • variables.tf
  • main.tf
🧠 Learnings (1)
outputs.tf (1)
Learnt from: Gowiem
PR: masterpointio/terraform-github-teams#1
File: outputs.tf:35-47
Timestamp: 2025-04-18T16:33:59.357Z
Learning: In Terraform for-expressions that iterate over resource collections, if the collection is empty (no resources of that type exist), Terraform won't execute the loop body at all, preventing potential index errors. For the GitHub Teams module, github_team_settings resources are only created when teams have review_request_delegation configured, so there's no need to add safeguards against empty delegation lists.
🔇 Additional comments (14)
examples/complete/versions.tf (1)

5-8: Approve provider source & version
The GitHub provider is correctly sourced (integrations/github) and pinned (~> 6.0), ensuring compatibility with the module inputs.

examples/complete/main.tf (3)

1-4: Approve module instantiation
The example correctly references the module source and sets the required github_organization.


25-49: Approve teams and delegation example
The teams block demonstrates name, privacy, members with roles, and review request delegation settings in line with your variable definitions and validations.


52-59: Approve repository collaborators example
The repository_collaborators mapping follows the required 'owner/repo' pattern and uses valid permissions.

checks.tf (5)

1-7: Clear identification of organization owners

The local org_owners correctly collects all “admin” users into a set for deduplication, making downstream checks reliable.


9-16: Accurate detection of invalid memberships

The invalid_memberships local filters org owners who aren’t maintainers and builds a structured list for error reporting. This will produce clear, actionable messages.


31-38: Well-scoped data sources

The github_organization and github_users data blocks are properly scoped to use the locals, ensuring only relevant users are fetched. Nice separation of concerns.


39-55: Robust check for organization owner roles

The github_team_org_owners_role assertion leverages alltrue() to enforce that every org owner has a “maintainer” role. The error aggregation using invalid_memberships is clear and user-friendly.


57-65: Validation of existing GitHub users

The validate_users_exist check succinctly asserts that no unknown logins remain. The formatted error message will clearly list any missing users.

outputs.tf (5)

1-11: Comprehensive team output

The teams output cleanly maps team names to their core properties (id, slug, name, privacy), making it straightforward to reference teams downstream.


13-22: Structured team memberships output

The team_memberships output provides a clear map of membership identifiers to their team_id, username, and role, which is great for auditing team composition.


24-33: Detailed repository collaborator output

The repository_collaborators output exposes each collaborator’s repository and permission level in a predictable map structure—very useful for governance checks.


35-47: Handling of team settings with delegation

The team_settings output captures review request delegation attributes succinctly. Since github_team_settings resources only exist when delegation is configured, indexing into [0] is safe here.


49-54: Organization membership roles output

The organization_memberships output cleanly maps usernames to their org roles, offering a quick reference of membership status.

Comment on lines +1 to +10
locals {
memberships_map = merge([
for team_name, team in var.teams : {
for member in team.members : "${team_name}:${member.username}" => {
team_id = github_team.default[team_name].id
username = member.username
role = member.role
}
}
]...)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard merge() when inputs are empty
Using merge([…]…) on an empty list errors out if var.teams (default {}) has no entries. Wrap with a conditional fallback:

locals {
  memberships_map = length(var.teams) > 0 ?
    merge([
      for team_name, team in var.teams : {
        for member in team.members :
        "${team_name}:${member.username}" => {
          team_id  = github_team.default[team_name].id
          username = member.username
          role     = member.role
        }
      }
    ]...) : {}
}

This prevents failures when the map is empty.

Comment on lines +18 to +28
repository_collaborators = merge([
for repo_name, collaborators in var.repository_collaborators : {
for collab in collaborators : "${repo_name}:${collab.username}" => {
repository = repo_name
username = collab.username
permission = collab.permission
permission_diff_suppression = collab.permission_diff_suppression
}
}
]...)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard collaborators merge for empty input
Similarly, protect the collaborators merge against an empty var.repository_collaborators:

locals {
  repository_collaborators = length(var.repository_collaborators) > 0 ?
    merge([
      for repo_name, collaborators in var.repository_collaborators : {
        for collab in collaborators :
        "${repo_name}:${collab.username}" => {
          repository                  = repo_name
          username                    = collab.username
          permission                  = collab.permission
          permission_diff_suppression = collab.permission_diff_suppression
        }
      }
    ]...) : {}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants