diff --git a/.github/workflows/apply.yml b/.github/workflows/apply.yml new file mode 100644 index 0000000..9ba67f4 --- /dev/null +++ b/.github/workflows/apply.yml @@ -0,0 +1,44 @@ +name: "Apply org changes" + +on: + push: + branches: + - main + paths: + - 'terraform/production/*.tfvars' + +jobs: + apply-changes: + name: "Org changes plan" + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: terraform apply + # v1.43.0 + # Use the commit hash for security hardening + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions + uses: dflook/terraform-apply@dcda97d729f1843ede471d2fac989cb946f5622a + env: + TERRAFORM_ACTIONS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + path: "terraform" + variables: | + github_token = "${{ secrets.TERRAFORM_MANAGEMENT_GITHUB_TOKEN }}" + var_file: | + terraform/production/org.tfvars + terraform/production/repositories.tfvars + terraform/production/teams.tfvars + + - name: Commit changes + uses: devops-infra/action-commit-push@v0.9.2 + with: + github_token: "${{ secrets.GITHUB_TOKEN }}" + commit_prefix: "[AUTO]" + commit_message: "State changes after apply" + force: false diff --git a/.github/workflows/new_team.yml b/.github/workflows/new_team.yml deleted file mode 100644 index 6c3e03b..0000000 --- a/.github/workflows/new_team.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Create new Django Commons teams for a project 🐍 - -on: - workflow_dispatch: - inputs: - repo: - description: "The slug of the repository in django-commons" - required: true - type: string - base-team-name: - description: "The base team name. Typically should match repo" - required: true - type: string - -env: - ORG: django-commons - -jobs: - create-teams: - name: Create teams 📦 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Create teams and assign permissions - env: - GH_TOKEN: ${{ secrets.ORG_CONTROLS_PAT }} - run: >- - TEAM_ID=$(gh api -X POST /orgs/${{ env.ORG }}/teams -f name=${{ inputs.base-team-name }} -f description="People who have access to the project." -f privacy="closed" --jq ".id") - - gh api -X POST /orgs/${{ env.ORG }}/teams -f name="${{ inputs.base-team-name }}-committers" -f description="People who have the ability to commit and maintain the project." -f privacy="closed" -F parent_team_id=${TEAM_ID} - - gh api -X POST /orgs/${{ env.ORG }}/teams -f name="${{ inputs.base-team-name }}-maintainers" -f description="People who administrate the project." -f privacy="closed" -F parent_team_id=${TEAM_ID} - - gh api -X PUT "/orgs/${{ env.ORG }}/teams/${{ inputs.base-team-name }}/repos/${{ env.ORG }}/${{ inputs.repo }}" -f permission="triage" - - gh api -X PUT "/orgs/${{ env.ORG }}/teams/${{ inputs.base-team-name }}-committers/repos/${{ env.ORG }}/${{ inputs.repo }}" -f permission="push" - - gh api -X PUT "/orgs/${{ env.ORG }}/teams/${{ inputs.base-team-name }}-maintainers/repos/${{ env.ORG }}/${{ inputs.repo }}" -f permission="maintain" \ No newline at end of file diff --git a/.github/workflows/plan.yml b/.github/workflows/plan.yml new file mode 100644 index 0000000..27c685c --- /dev/null +++ b/.github/workflows/plan.yml @@ -0,0 +1,37 @@ +name: "Plan org changes and list them in a PR" +on: + pull_request: + branches: + - main + paths: + - 'terraform/production/*.tfvars' + +jobs: + plan-changes: + name: "Org changes plan" + runs-on: ubuntu-latest + + permissions: + pull-requests: write + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: terraform plan + # v1.43.0 + # Use the commit hash for security hardening + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions + uses: dflook/terraform-plan@d9df4f6c2484e709ba7ffaa16c98a6906f4760cd + env: + TERRAFORM_ACTIONS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + add_github_comment: true + path: "terraform" + variables: | + github_token = "${{ secrets.TERRAFORM_MANAGEMENT_GITHUB_TOKEN }}" + var_file: | + terraform/production/org.tfvars + terraform/production/repositories.tfvars + terraform/production/teams.tfvars diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b250a4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.backup +notes.txt \ No newline at end of file diff --git a/README.md b/README.md index fdff1cf..f54c9cd 100644 --- a/README.md +++ b/README.md @@ -9,43 +9,168 @@ Django Commons packages. - [New project](#new-project-playbook) - [Remove project](#remove-project-playbook) - ## New Member Playbook 1. Review new issues/application at https://github.com/django-commons/membership/issues/ -2. If they are a real human and are reasonably trustworthy, comment "Approved" and nothing else -3. The [new_member](https://github.com/django-commons/membership/blob/main/.github/workflows/new_member.yml) action will send the invite and close the issue -4. Add the member to any relevant teams if requested - -If they aren't a real human or reasonably trustworthy, close the issue. +2. If they are not a real human or not reasonably trustworthy, close the issue, asking for more information they are a human and not a spam bot. You can explain that by being a member, they can impact repositories immediately. +3. Add the user to the `members` collection in the [`terraform/production/org.tfvars`](https://github.com/django-commons/controls/blob/main/terraform/production/org.tfvars) file. + ```terraform + members = [ + # ... + "new_user" + ] + ``` +5. If they requested to be on specific repository team(s), in the [`terraform/production/teams.tfvars`](https://github.com/django-commons/controls/blob/main/terraform/production/teams.tfvars) file, for the repository's key under `teams_repositories`, add them to the `members` collection. + ```terraform + teams_repositories = { + "[REPOSITORY]" = { + # ... + members = [ + # ... + "new_user" + ] + } + } + ``` +6. Create a pull-request to `main` branch, it will trigger terraform to plan the changes in the organization to be + executed. Review the changes and make sure they align with the request. +7. Merge the pull-request, it will trigger terraform to apply the changes in the organization. ## Team Change Playbook -1. If they are a real human and are reasonably trustworthy, comment "Approved" and close the issue manually -2. Add the member to requested team(s) +1. If they are not a real human or not reasonably trustworthy, close the issue, asking for more information they are a human and not a spam bot. You can explain that by being a member, they can impact repositories immediately. +2. For the requested repository's team(s), in the [`terraform/production/teams.tfvars`](https://github.com/django-commons/controls/blob/main/terraform/production/teams.tfvars) file, for the repository's key under `teams_repositories`, add them to the `members` collection. + ```terraform + teams_repositories = { + "[REPOSITORY]" = { + # ... + members = [ + # ... + "new_user" + ] + } + } + ``` +3. Create a pull-request to `main` branch, it will trigger terraform to plan the changes in the organization to be + executed. Review the changes and make sure they align with the request. +4. Merge the pull-request, it will trigger terraform to apply the changes in the organization. -## New Repository Admin Playbook +## New Repository Admin or Committer Playbook -1. Confirm with all existing admins that they are okay with the prospective admin +1. Confirm with all existing admins that they are okay with the change 2. If there's disagreement, close the issue and ask for the admins to come to a consensus -3. If there's agreement, add the prospective admin to the [repo]-admins team +3. For the requested repository's team(s), in the [`terraform/production/teams.tfvars`](https://github.com/django-commons/controls/blob/main/terraform/production/teams.tfvars) + file, for the repository's key under `teams_repositories_privileged`, add them to the `members` collection for the correct team. There will be two privileged teams for each repository, `*-admins` and `*-committers`, the user should be added to the requested team. + ```terraform + teams_repositories_privileged = { + "[REPOSITORY]-[admins | committers]" = { + # ... + members = [ + # ... + "new_user" + ] + } + } + ``` +4. Create a pull-request to `main` branch, it will trigger terraform to plan the changes in the organization to be + executed. Review the changes and make sure they align with the request. +5. Merge the pull-request, it will trigger terraform to apply the changes in the organization. ## New Project Playbook -1. Check if repository meets [inbound requirements](https://github.com/django-commons/membership/blob/main/incoming_repo_requirements.md) +1. Check if repository + meets [inbound requirements](https://github.com/django-commons/membership/blob/main/incoming_repo_requirements.md) 2. Confirm who will be the admins and maintainers for the repository 3. PyPI project owner must add you (Django Commons admin) as owner in PyPI -4. (TODO: Determine how this works with transfering out of an org and into the Django Commons org) -5. [Add repository owner to Django Commons as member](https://github.com/orgs/django-commons/people) (they'll be added to a team later) -6. Share link ([https://docs.github.com/en/repositories/creating-and-managing-repositories/transferring-a-repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/transferring-a-repository)) with repo owner to transfer repo +4. (TODO: Determine how this works with transferring out of an org and into the Django Commons org) +5. [Add repository owner to Django Commons as member](#new-member-playbook) (they'll be added + to a team later) +6. Share + link ([https://docs.github.com/en/repositories/creating-and-managing-repositories/transferring-a-repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/transferring-a-repository)) + with repo owner to transfer repo 7. Wait for repository transferred in 8. [Run new team action](https://github.com/django-commons/controls/actions/workflows/new_team.yml) -9. Invite repository admins to [repo]-admins team, repository maintainers to [repo]-committers team -10. Configure environments pypi and testpypi -11. For pypi environment, add Deployment protection rule with reviewers as [repo]-admins and enable "Allow administrators to bypass configured protection rules" -12. Under Actions > General > "Fork pull request workflows from outside collaborators", set "Require approval for first-time contributors" -13. Add previous repository owner to [repo]-admins team -14. Set a calender event or reminder for 30 days in the future to remove previous repository owner from team +9. [Make Terraform changes to add new project](#terraform-changes-to-add-a-new-project) +10. [Configure environments](https://docs.github.com/en/actions/administering-github-actions/managing-environments-for-deployment#creating-an-environment) pypi and testpypi in the repository to enable [publishing packages via GitHub Actions](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#) +11. For pypi environment, add Deployment protection rule with reviewers as [repo]-admins and enable "Allow + administrators to bypass configured protection rules" +12. Under Actions > General > "Fork pull request workflows from outside collaborators", set "Require approval for + first-time contributors" +13. Set a calendar event or reminder for 30 days in the future to remove previous repository owner from team + +### Terraform changes to add a new project + +Assuming repository name is `repo-name`: + +1. In [`terraform/production/respositories.tfvars`](https://github.com/django-commons/controls/blob/main/terraform/production/respositories.tfvars), Add the new repository to the `repositories` section: + ```terraform + repositories = { + # ... + "repo-name" = { + description = "repo description" + allow_auto_merge = false # optional, default is false + allow_merge_commit = false # optional, default is false + allow_rebase_merge = false # optional, default is false + allow_squash_merge = false # optional, default is false + allow_update_branch = false # optional, default is false + enable_branch_protection = true # optional, default is true + has_discussions = true # optional, default is true + has_downloads = true # optional, default is true + has_wiki = false # optional, default is false + is_template = false # optional, default is false + push_allowances = [] + required_status_checks_contexts = [] # optional, default is [] + template = "" # optional, default is "" + topics = [] + visibility = "public" # optional, default is "public" + } + } + ``` +2. In [`terraform/production/teams.tfvars`](https://github.com/django-commons/controls/blob/main/terraform/production/teams.tfvars), add the new team `repo-name` for the repository in the `teams_repositories` section with the + relevant members: + ```terraform + teams_repositories = { + # ... + "repo-name" = { + description = "repo-name team" + members = [ + # Put the user from Step 5 here + "username", + ] + permission = "triage" + repositories = [ + "repo-name", + ] + } + } + ``` +3. Add two new child teams `repo-name-admins` and `repo-name-committers` for the repository in the `teams_repositories_privileged` + section with the relevant members: + ```terraform + teams_repositories_privileged = { + # ... + "repo-name-admins" = { + description = "repo-name admins team" + parent_team_key = "repo-name" + members = [ + # Put the user from Step 5 here + "username", + ] + permission = "admin" + } + "repo-name-committers" = { + description = "repo-name committers team" + parent_team_key = "repo-name" + members = [ + # Leave empty unless there are committers ready to be designated + ] + permission = "push" + } + } + ``` +4. Create a pull-request to `main` branch, it will trigger terraform to plan the changes in the organization to be + executed. Review the changes and make sure they align with the request. +5. Merge the pull-request, it will trigger terraform to apply the changes in the organization. ## Remove Project Playbook @@ -53,6 +178,15 @@ If they aren't a real human or reasonably trustworthy, close the issue. 2. Add new Owner(s) to project in PyPI 3. [Transfer GitHub repo to new owner or Org](https://github.com/orgs/django-commons/people) 4. Wait for repository to be transferred out. -5. [Delete top-level team for repository](https://github.com/orgs/django-commons/teams) (the delete will cascade to the sub-teams) -6. Remove all Django Commons members from PyPI project (except any that are staying on from step 2) -7. (TODO: Determine how to handle transferring a PyPI project out of an organization) +5. Remove all Django Commons members from PyPI project (except any that are staying on from step 2) +6. (TODO: Determine how to handle transferring a PyPI project out of an organization) + +### Terraform changes to remove a project + +1. Remove the repository from the `repositories` section in [`terraform/production/respositories.tfvars`](https://github.com/django-commons/controls/blob/main/terraform/production/respositories.tfvars) +2. Remove the parent team and child teams for the repository from the `teams_repositories` and `teams_repositories_privileged` sections in + [`terraform/production/teams.tfvars`](https://github.com/django-commons/controls/blob/main/terraform/production/teams.tfvars) +3. Create a pull-request to `main` branch, it will trigger terraform to plan the changes in the organization to be + executed. + Review the changes and make sure they align with the request. +4. Merge the pull-request, it will trigger terraform to apply the changes in the organization. diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..f8712d7 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,107 @@ +GitHub Organization as Terraform +================================ + +# Structure + +- `variables.tf` - define variable types (classes?), notice there is `variable "repositories" {...` there which has a + few variables marked as optional with default values. Why I chose to have `has_discussions` as a repo variable + while `has_issues` as a constant - I am embarrassed to say I don't have a better answer than laziness :smile: - I just + figured if this is the path we want to take, we can continue adding to it. +- `production.tfvars` - instances, should strictly follow the types in `variables.tf`. +- `main.tf` - build configuration based on instances values from `production.tfvars` (or, if not defined explicitly, + then default value from `variables.tf`) +- `tfstate.json` - Current state file, pulled using `terraform import ..` + +# Why Terraform? + +We can define our "desired/default" repository configuration, and within this configuration: + +- What is enforced from day one (i.e., constant in `resource "github_repository" "this"`) +- What is recommended but can be changed by users (i.e., variable with a default value in `variables.tf` that can be + updated in `production.tfvars`) => Note this can also help us review outliers, you can see all repos which have + non-default values in the `production.tfvars` file. +- What is determined by users (i.e., variables without default value, like `description`) +- What is not configured in the infra-as-code (currently, for example, repo-labels). + +# What changes can be made + +All changes should be made in `production.tfvars`: + +- Add/Remove organization admins by editing the `admins` list. +- Add/Remove organization members by editing the `members` list. +- Add/Remove/Update repositories by editing the `repositories`. A repository can have the following variables: + ```terraform + repositories = { + "repo-name" = { + description = "repo description" + allow_auto_merge = false # optional, default is false + allow_merge_commit = false # optional, default is false + allow_rebase_merge = false # optional, default is false + allow_squash_merge = false # optional, default is false + allow_update_branch = false # optional, default is false + enable_branch_protection = true # optional, default is true + has_discussions = true # optional, default is true + has_downloads = true # optional, default is true + has_wiki = false # optional, default is false + is_template = false # optional, default is false + push_allowances = [] + required_status_checks_contexts = [] # optional, default is [] + template = "" # optional, default is "" + topics = [] + visibility = "public" # optional, default is "public" + } + # ... + } + ``` +- Add/Remove/Update repository teams by editing the `teams_repositories`. A team can have the following variables: + ```terraform + teams_repositories = { + "some-repo" = { + description = "some-repo team" + members = ["cunla",] + permission = "triage" + privacy = "closed" # optional, default is "closed" + repositories = [ # optional, default is [] + "django-commons/some-repo", + ] + review_request_delegation = false # optional, default is false + } + # ... + } + ``` +- Add/Remove/Update privileged repository teams by editing the `teams_repositories_privileged`. A team can have the following variables: + ```terraform + teams_repositories_privileged = { + "some-repo-admins" = { + description = "some-repo administrators" + parent_team_key = "some-repo" + members = ["cunla",] + permission = "admin" # optional, default is null + privacy = "closed" # optional, default is "closed" + repositories = [ # optional, default is [] + "django-commons/some-repo", + ] + review_request_delegation = false # optional, default is false + } + # ... + } + ``` + +# How to use locally + +You might want to try new settings locally before applying them to the repository automation. +To do so, you can use the following steps: + +1. Clone the repository. +2. From the `terraform/` directory, run `terraform init`. +3. Create a github-token with the necessary permissions on the organization (see [permissions documentation][1]). + - The `repo` permisison for full control of private repositories. + - The `admin:org` permission for full control of orgs and teams, read and write org projects + - The `delete_repo` permission to delete repositories + +4. Make changes to `production.tfvars` to reflect the desired state (add/update users, repositories, teams, etc.) +5. To see what changes between the current state of the GitHub organization and the plan + run: `terraform plan -var-file=tfvars/production.tfvars -var github_token=...` +6. To apply the changes, run: `terraform apply -var-file=tfvars/production.tfvars -var github_token=...` + +[1]: https://developer.hashicorp.com/terraform/tutorials/it-saas/github-user-teams#configure-your-credentials \ No newline at end of file diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 0000000..a113544 --- /dev/null +++ b/terraform/backend.tf @@ -0,0 +1,9 @@ +# Backend Configuration +# https://www.terraform.io/language/settings/backends/configuration + +terraform { + backend "local" { + path = "tfstate.json" + } + +} diff --git a/terraform/locals.tf b/terraform/locals.tf new file mode 100644 index 0000000..160ba6f --- /dev/null +++ b/terraform/locals.tf @@ -0,0 +1,52 @@ +# Local Values +# https://www.terraform.io/language/values/locals + +locals { + + admins = { + for user in var.admins : user => "admin" + } + + branch_protections = { + for repository_key, repository in var.repositories : repository_key => repository + if repository.enable_branch_protection + } + + privileged_repository_team_permissions = { + + for repository in flatten([ + for team_key, team in var.teams_repositories_privileged : [ + for repository in team.repositories : { + team_child = team_key + repository = repository + permission = team.permission + } + ] + ]) : "${repository.team_child}-${repository.repository}" => repository + } + + + members = { + for user in var.members : user => "member" + } + + repository_team_permissions = { + + for repository in flatten([ + for team_key, team in var.teams_repositories : [ + for repository in team.repositories : { + team_parent = team_key + repository = repository + permission = team.permission + } + ] + ]) : "${repository.team_parent}-${repository.repository}" => repository + } + + review_request_delegations = { + for team_key, team in var.teams_repositories : team_key => team + if team.review_request_delegation + } + + users = merge(local.admins, local.members) +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..de0425f --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,297 @@ +# Required Providers +# https://www.terraform.io/docs/language/providers/requirements.html#requiring-providers + +terraform { + required_providers { + github = { + source = "integrations/github" + } + + # Random Provider + # https://registry.terraform.io/providers/hashicorp/random/latest/docs + + random = { + source = "hashicorp/random" + } + + # Time Provider + # https://registry.terraform.io/providers/hashicorp/time/latest/docs + + time = { + source = "hashicorp/time" + } + } +} + +# Github Provider +# https://registry.terraform.io/providers/integrations/github/latest/docs + +provider "github" { + owner = "django-commons" + token = var.github_token +} + +# Github Actions Secret Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_secret + +resource "github_actions_organization_secret" "this" { + + # Ensure GitHub Actions secrets are encrypted + # checkov:skip=CKV_GIT_4: We are passing the secret from the random_password resource which is sensitive by default + # and not checking in any plain text values. State is always secured. + + for_each = var.organization_secrets + + plaintext_value = random_password.this[each.key].result + secret_name = each.key + visibility = each.value.visibility +} + +# Github Branch Protection Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_protection +# +# resource "github_branch_protection" "this" { +# +# # GitHub pull requests should require at least 2 approvals +# # checkov:skip=CKV_GIT_5: 1 approval is reasonable for a small team +# for_each = local.branch_protections +# +# enforce_admins = false +# pattern = "main" +# repository_id = github_repository.this[each.key].name +# require_conversation_resolution = true +# required_linear_history = true +# require_signed_commits = true +# +# required_pull_request_reviews { +# dismiss_stale_reviews = true +# require_code_owner_reviews = true +# required_approving_review_count = 1 +# } +# +# required_status_checks { +# contexts = each.value.required_status_checks_contexts +# strict = true +# } +# +# restrict_pushes { +# push_allowances = each.value.push_allowances +# } +# } + +# GitHub Membership Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/membership + +resource "github_membership" "this" { + for_each = local.users + + role = each.value + username = each.key +} + +# Github Organization Security Manager Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/organization_security_manager + +resource "github_organization_security_manager" "this" { + team_slug = github_team.parents["security-team"].slug +} + +# Github Repository Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository + +resource "github_repository" "this" { + + # Ensure GitHub repository is Private + # checkov:skip=CKV_GIT_1: Public is ok for us since we are an open source project + + for_each = var.repositories + + allow_auto_merge = each.value.allow_auto_merge + allow_merge_commit = each.value.allow_merge_commit + allow_rebase_merge = each.value.allow_rebase_merge + allow_squash_merge = each.value.allow_squash_merge + allow_update_branch = each.value.allow_update_branch + archive_on_destroy = true + delete_branch_on_merge = true + description = each.value.description + has_downloads = each.value.has_downloads + has_discussions = each.value.has_discussions + has_issues = true + has_projects = true + has_wiki = each.value.has_wiki + is_template = each.value.is_template + name = each.key + squash_merge_commit_message = "BLANK" + squash_merge_commit_title = "PR_TITLE" + topics = each.value.topics + visibility = each.value.visibility + vulnerability_alerts = true + + dynamic "template" { + for_each = each.value.template != null ? [each.value.template] : [] + + content { + owner = "django-commons" + repository = template.value + } + } +} + +# GitHub Repository File Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_file +# This could be used to generate a file such as a security template. +# See https://github.com/osinfra-io/ for an example + + +# Github Team Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team + +# If you need to import a team, you can do so with the following command: +# terraform import github_team.this\[\"google-cloud-platform\"\] + +# To get the team ids, you can run the following curl command with a token that has the read:org scope against your own organization. +# curl -H "Authorization: token $GITHUB_READ_ORG_TOKEN" https://api.github.com/orgs/osinfra-io/teams + +# Create the base teams for each repository and the organization teams for Django Commons. +resource "github_team" "parents" { + for_each = merge(var.teams_repositories, var.teams_organization) + + name = each.key + description = each.value.description + privacy = each.value.privacy +} + +# Create the privileged teams for each repository, +# such committers or maintainers. +resource "github_team" "children" { + for_each = var.teams_repositories_privileged + + description = each.value.description + name = each.key + parent_team_id = github_team.parents[each.value.parent_team_key].id + privacy = github_team.parents[each.value.parent_team_key].privacy +} + +# GitHub Team Membership Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_members + +# Define the team membership for each repository. The members here +# will have triage permissions. +resource "github_team_members" "parents" { + for_each = var.teams_repositories + + team_id = github_team.parents[each.key].id + + dynamic "members" { + for_each = each.value.members + + content { + # members here references the dynamic name, not the looped entity. + username = members.value + role = "member" + } + } + + # Maintainer here means the maintainer role for the team. + # It's not a maintainer of the repo. + dynamic "members" { + for_each = each.value.maintainers + + content { + # members here references the dynamic name, not the looped entity. + username = members.value + role = "maintainer" + } + } +} + + +# Define the privileged team membership for each repository. The members here +# will have commit or maintainer permissions depending on the team. +resource "github_team_members" "children" { + for_each = var.teams_repositories_privileged + + team_id = github_team.children[each.key].id + + dynamic "members" { + for_each = each.value.members + + content { + # members here references the dynamic name, not the looped entity. + username = members.value + role = "member" + } + } + + # Maintainer here means the maintainer role for the team. + # It's not a maintainer of the repo. + dynamic "members" { + for_each = each.value.maintainers + + content { + # members here references the dynamic name, not the looped entity. + username = members.value + role = "maintainer" + } + } +} + +# Github Team Repository Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_repository + +# Create the elevated permissions for the repositories' privileged teams. +resource "github_team_repository" "children" { + for_each = local.privileged_repository_team_permissions + + team_id = github_team.children[each.value.team_child].id + repository = each.value.repository + permission = each.value.permission +} + +# Create the appropriate permissions for the repository teams. +resource "github_team_repository" "parents" { + for_each = local.repository_team_permissions + + team_id = github_team.parents[each.value.team_parent].id + repository = each.value.repository + permission = each.value.permission +} + +# GitHub Team Settings Resource +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_settings + +# This is used to enable automatic PR review requests +resource "github_team_settings" "this" { + for_each = local.review_request_delegations + + review_request_delegation { + algorithm = "LOAD_BALANCE" + member_count = 2 + notify = false + } + + team_id = github_team.parents[each.key].id +} + +# Random Password Resource +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password + +# This is necessary to set a GitHub org secret +resource "random_password" "this" { + for_each = var.organization_secrets + length = 32 + special = false + + keepers = { + rotation_time = time_rotating.this.rotation_rfc3339 + } +} + +# Time Rotating Resource +# https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/rotating + +# This is necessary to use random_password, which is needed +# to set a GitHub org secret +resource "time_rotating" "this" { + rotation_days = 5 +} diff --git a/terraform/production/org.tfvars b/terraform/production/org.tfvars new file mode 100644 index 0000000..07186be --- /dev/null +++ b/terraform/production/org.tfvars @@ -0,0 +1,27 @@ +# Organization admins +admins = [ + "tim-schilling", + "williln", + "ryancheley", + "Stormheg", + "cunla", +] +# Organization members +members = [ + "gav-fyi", + "jcjudkins", + "joshuadavidthomas", + "matthiask", + "nanorepublica", + "Natim", + "pfouque", + "priyapahwa", + "testSchilling", +] + +organization_secrets = { + # "GPG_PASSPHRASE" = { + # description = "GPG Passphrase used to encrypt plan.out files" + # visibility = "all" + # } +} \ No newline at end of file diff --git a/terraform/production/repositories.tfvars b/terraform/production/repositories.tfvars new file mode 100644 index 0000000..05e6ffc --- /dev/null +++ b/terraform/production/repositories.tfvars @@ -0,0 +1,33 @@ +# Organization repositories +repositories = { + # Keep the following repositories in alphabetical order + + ".github" = { + description = "A Special Repository." + enable_branch_protection = false + + topics = [] + push_allowances = [] + } + + "controls" = { + description = "The controls for managing Django Commons projects" + enable_branch_protection = false + + topics = [] + push_allowances = [] + visibility = "public" + } + + "membership" = { + description = "Membership repository for the django-commons organization." + visibility = "public" + topics = [] + push_allowances = [] + } + + "django-commons-playground" = { + description = "A sample project to test things out" + topics = [] + } +} diff --git a/terraform/production/teams.tfvars b/terraform/production/teams.tfvars new file mode 100644 index 0000000..8202f0a --- /dev/null +++ b/terraform/production/teams.tfvars @@ -0,0 +1,79 @@ +teams_organization = { + "Admins" = { + description = "django-commons administrators" + # Use maintainers for organizational teams + maintainers = [ + "tim-schilling", + "williln", + "ryancheley", + "Stormheg", + "cunla", + ] + } + "security-team" = { + description = "django-commons security team" + # Use maintainers for organizational teams + maintainers = [ + "tim-schilling", + "matthiask" + ] + permission = "push" + + repositories = [ + ] + } +} + +teams_repositories = { + "django-community-playground" = { + description = "django-community-playground team" + members = [ + "tim-schilling", + "williln", + "ryancheley", + "Stormheg", + "cunla", + "priyapahwa", + ] + permission = "triage" + + repositories = [ + "django-commons-playground", + ] + + review_request_delegation = true + } + +} + +teams_repositories_privileged = { + "django-community-playground-admins" = { + description = "django-community-playground administrators" + parent_team_key = "django-community-playground" + permission = "admin" + members = [ + "tim-schilling", + "williln", + "ryancheley", + "Stormheg", + "cunla", + ] + + repositories = [ + "django-commons-playground", + ] + } + "django-community-playground-committers" = { + description = "django-community-playground committers" + parent_team_key = "django-community-playground" + permission = "maintain" + members = [ + "priyapahwa", + ] + + repositories = [ + "django-commons-playground" + ] + } + +} diff --git a/terraform/tfstate.json b/terraform/tfstate.json new file mode 100644 index 0000000..20ceeca --- /dev/null +++ b/terraform/tfstate.json @@ -0,0 +1,818 @@ +{ + "version": 4, + "terraform_version": "1.5.7", + "serial": 30, + "lineage": "425397de-8394-a003-8a6c-bce854d9cc53", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "github_actions_organization_secret", + "name": "this", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [] + }, + { + "mode": "managed", + "type": "github_membership", + "name": "this", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": "Natim", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"68caaa2a1f43f8105fc4f2b11539ab0a4274703153c2003ec2c3a5b4ef9ce6c7\"", + "id": "django-commons:Natim", + "role": "member", + "username": "Natim" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "Stormheg", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"6213547b039be42df8cb11807508ced8ff28c328528a11ab0e26d66e6d78af31\"", + "id": "django-commons:Stormheg", + "role": "admin", + "username": "Stormheg" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "cunla", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"9359ff5dc674c7a5f666266818ba0976714f1a09e3a0422ac379aafea7adeed4\"", + "id": "django-commons:cunla", + "role": "admin", + "username": "cunla" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "gav-fyi", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"cfb584ec4e3496693ac6d7a7afe2592209ef3970f4f53c0e9b1e7885a63f93b6\"", + "id": "django-commons:gav-fyi", + "role": "member", + "username": "gav-fyi" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "jcjudkins", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"ebbf5aa3bb02451b568616140a51580ec56f433d065c5e003a502362dd808add\"", + "id": "django-commons:jcjudkins", + "role": "member", + "username": "jcjudkins" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "joshuadavidthomas", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"405875598c46e245e59326fca4937f09dda150a215fffb0c61908d08f7d437d7\"", + "id": "django-commons:joshuadavidthomas", + "role": "member", + "username": "joshuadavidthomas" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "matthiask", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"41d15a470cac92785493809b1fe7c6a6b3e21180aa1c20be2feaa185c63bb292\"", + "id": "django-commons:matthiask", + "role": "member", + "username": "matthiask" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "nanorepublica", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"b2f0f248f8e6b7d47c4c6862abe1069531ffa6d2db127237e0264a301581bb5b\"", + "id": "django-commons:nanorepublica", + "role": "member", + "username": "nanorepublica" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "pfouque", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"0571b46dbaa3e06d741cd6ac213d57c36f154725636eee4f93f566f31d307651\"", + "id": "django-commons:pfouque", + "role": "member", + "username": "pfouque" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "priyapahwa", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"804a24a7934e528c0c8c7c6501308f4471d73d5ed38e24a17457f535e1d1f634\"", + "id": "django-commons:priyapahwa", + "role": "member", + "username": "priyapahwa" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "ryancheley", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"d4049c77383fa1d8f3415168a07c78f11ca78cfa89f1c927b679b25aa5231dcb\"", + "id": "django-commons:ryancheley", + "role": "admin", + "username": "ryancheley" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "testSchilling", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"031db6d5f4a2df778bba9d4177ee9adc7f19f75d9de9b00052e6284895753703\"", + "id": "django-commons:testSchilling", + "role": "member", + "username": "testSchilling" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "tim-schilling", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"260d3a31af83c84f08c409b8ae8457ac2cbc2054c6769e7161e36ecb59601060\"", + "id": "django-commons:tim-schilling", + "role": "admin", + "username": "tim-schilling" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "williln", + "schema_version": 0, + "attributes": { + "downgrade_on_destroy": null, + "etag": "W/\"ab687b07552879a71ff6f83c6b7981a86a89dbf8f12d681db1824e939e0ef153\"", + "id": "django-commons:williln", + "role": "admin", + "username": "williln" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + } + ] + }, + { + "mode": "managed", + "type": "github_repository", + "name": "this", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": ".github", + "schema_version": 1, + "attributes": { + "allow_auto_merge": false, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_squash_merge": false, + "allow_update_branch": false, + "archive_on_destroy": null, + "archived": false, + "auto_init": false, + "default_branch": "main", + "delete_branch_on_merge": false, + "description": "", + "etag": "W/\"1b93e4b77f56afa8f82793bdd73f5ba75a49c5c5a4f666d10218be75adef2a2b\"", + "full_name": "django-commons/.github", + "git_clone_url": "git://github.com/django-commons/.github.git", + "gitignore_template": null, + "has_discussions": false, + "has_downloads": true, + "has_issues": true, + "has_projects": true, + "has_wiki": true, + "homepage_url": "", + "html_url": "https://github.com/django-commons/.github", + "http_clone_url": "https://github.com/django-commons/.github.git", + "id": ".github", + "ignore_vulnerability_alerts_during_read": null, + "is_template": false, + "license_template": null, + "merge_commit_message": "", + "merge_commit_title": "", + "name": ".github", + "node_id": "R_kgDOLkyRSQ", + "pages": [], + "primary_language": "", + "private": false, + "repo_id": 776769865, + "security_and_analysis": [ + { + "advanced_security": [], + "secret_scanning": [ + { + "status": "disabled" + } + ], + "secret_scanning_push_protection": [ + { + "status": "disabled" + } + ] + } + ], + "squash_merge_commit_message": "", + "squash_merge_commit_title": "", + "ssh_clone_url": "git@github.com:django-commons/.github.git", + "svn_url": "https://github.com/django-commons/.github", + "template": [], + "topics": [], + "visibility": "public", + "vulnerability_alerts": false, + "web_commit_signoff_required": false + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==" + }, + { + "index_key": "controls", + "schema_version": 1, + "attributes": { + "allow_auto_merge": false, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_squash_merge": false, + "allow_update_branch": false, + "archive_on_destroy": null, + "archived": false, + "auto_init": false, + "default_branch": "main", + "delete_branch_on_merge": false, + "description": "The controls for managing Django Commons projects", + "etag": "W/\"2904577e11d0752a0dd0d33dccfad6cbdecaef68554143a74c4a8b71296b8487\"", + "full_name": "django-commons/controls", + "git_clone_url": "git://github.com/django-commons/controls.git", + "gitignore_template": null, + "has_discussions": false, + "has_downloads": true, + "has_issues": true, + "has_projects": true, + "has_wiki": true, + "homepage_url": "", + "html_url": "https://github.com/django-commons/controls", + "http_clone_url": "https://github.com/django-commons/controls.git", + "id": "controls", + "ignore_vulnerability_alerts_during_read": null, + "is_template": false, + "license_template": null, + "merge_commit_message": "", + "merge_commit_title": "", + "name": "controls", + "node_id": "R_kgDOLkDK8g", + "pages": [], + "primary_language": "", + "private": false, + "repo_id": 775998194, + "security_and_analysis": [ + { + "advanced_security": [], + "secret_scanning": [ + { + "status": "disabled" + } + ], + "secret_scanning_push_protection": [ + { + "status": "disabled" + } + ] + } + ], + "squash_merge_commit_message": "", + "squash_merge_commit_title": "", + "ssh_clone_url": "git@github.com:django-commons/controls.git", + "svn_url": "https://github.com/django-commons/controls", + "template": [], + "topics": [], + "visibility": "public", + "vulnerability_alerts": false, + "web_commit_signoff_required": false + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==" + }, + { + "index_key": "django-commons-playground", + "schema_version": 1, + "attributes": { + "allow_auto_merge": false, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_squash_merge": false, + "allow_update_branch": false, + "archive_on_destroy": null, + "archived": false, + "auto_init": false, + "default_branch": "main", + "delete_branch_on_merge": false, + "description": "A sample project to test things out", + "etag": "W/\"fc80aa6c9b3ef87f460913e9aa0e2d9dc37a6f744647390c091d98bc2cd66dea\"", + "full_name": "django-commons/django-commons-playground", + "git_clone_url": "git://github.com/django-commons/django-commons-playground.git", + "gitignore_template": null, + "has_discussions": false, + "has_downloads": true, + "has_issues": true, + "has_projects": true, + "has_wiki": true, + "homepage_url": "", + "html_url": "https://github.com/django-commons/django-commons-playground", + "http_clone_url": "https://github.com/django-commons/django-commons-playground.git", + "id": "django-commons-playground", + "ignore_vulnerability_alerts_during_read": null, + "is_template": false, + "license_template": null, + "merge_commit_message": "", + "merge_commit_title": "", + "name": "django-commons-playground", + "node_id": "R_kgDOLkANrg", + "pages": [], + "primary_language": "Python", + "private": false, + "repo_id": 775949742, + "security_and_analysis": [ + { + "advanced_security": [], + "secret_scanning": [ + { + "status": "disabled" + } + ], + "secret_scanning_push_protection": [ + { + "status": "disabled" + } + ] + } + ], + "squash_merge_commit_message": "", + "squash_merge_commit_title": "", + "ssh_clone_url": "git@github.com:django-commons/django-commons-playground.git", + "svn_url": "https://github.com/django-commons/django-commons-playground", + "template": [], + "topics": [], + "visibility": "public", + "vulnerability_alerts": false, + "web_commit_signoff_required": false + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==" + }, + { + "index_key": "membership", + "schema_version": 1, + "attributes": { + "allow_auto_merge": false, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_squash_merge": false, + "allow_update_branch": false, + "archive_on_destroy": null, + "archived": false, + "auto_init": false, + "default_branch": "main", + "delete_branch_on_merge": false, + "description": "", + "etag": "W/\"fe64192cf880ca2292a4ac8b0e9cd75087401fc870c3c5c7f4c3fda2f29aad22\"", + "full_name": "django-commons/membership", + "git_clone_url": "git://github.com/django-commons/membership.git", + "gitignore_template": null, + "has_discussions": true, + "has_downloads": true, + "has_issues": true, + "has_projects": true, + "has_wiki": true, + "homepage_url": "", + "html_url": "https://github.com/django-commons/membership", + "http_clone_url": "https://github.com/django-commons/membership.git", + "id": "membership", + "ignore_vulnerability_alerts_during_read": null, + "is_template": false, + "license_template": null, + "merge_commit_message": "", + "merge_commit_title": "", + "name": "membership", + "node_id": "R_kgDOLklIpA", + "pages": [], + "primary_language": "", + "private": false, + "repo_id": 776554660, + "security_and_analysis": [ + { + "advanced_security": [], + "secret_scanning": [ + { + "status": "disabled" + } + ], + "secret_scanning_push_protection": [ + { + "status": "disabled" + } + ] + } + ], + "squash_merge_commit_message": "", + "squash_merge_commit_title": "", + "ssh_clone_url": "git@github.com:django-commons/membership.git", + "svn_url": "https://github.com/django-commons/membership", + "template": [], + "topics": [], + "visibility": "public", + "vulnerability_alerts": false, + "web_commit_signoff_required": false + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==" + } + ] + }, + { + "mode": "managed", + "type": "github_team", + "name": "children", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": "django-community-playground-admins", + "schema_version": 0, + "attributes": { + "create_default_maintainer": false, + "description": "", + "etag": "W/\"29ad110c85967ccc4c149e51eebbbed2f56a49260d60161eb505a443be1f3014\"", + "id": "9757650", + "ldap_dn": "", + "members_count": 5, + "name": "django-community-playground-admins", + "node_id": "T_kwDOCaaRBM4AlOPS", + "parent_team_id": "9757678", + "parent_team_read_id": "9757678", + "parent_team_read_slug": "django-community-playground", + "privacy": "closed", + "slug": "django-community-playground-admins" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "django-community-playground-committers", + "schema_version": 0, + "attributes": { + "create_default_maintainer": false, + "description": "", + "etag": "W/\"ab9f29d07ec9c93b0f69329396cc19415de8fcf9d74154649cf4c58dcc604ea4\"", + "id": "9757668", + "ldap_dn": "", + "members_count": 6, + "name": "django-community-playground-committers", + "node_id": "T_kwDOCaaRBM4AlOPk", + "parent_team_id": "9757678", + "parent_team_read_id": "9757678", + "parent_team_read_slug": "django-community-playground", + "privacy": "closed", + "slug": "django-community-playground-committers" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + } + ] + }, + { + "mode": "managed", + "type": "github_team", + "name": "parents", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": "Admins", + "schema_version": 0, + "attributes": { + "create_default_maintainer": false, + "description": "", + "etag": "W/\"edeb1adc95c341b74155a478bb2b9ded90cc7ecfb784acd0949f0f88ed585c51\"", + "id": "9763562", + "ldap_dn": "", + "members_count": 5, + "name": "Admins", + "node_id": "T_kwDOCaaRBM4AlPrq", + "parent_team_id": "", + "parent_team_read_id": "", + "parent_team_read_slug": "", + "privacy": "closed", + "slug": "admins" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "django-community-playground", + "schema_version": 0, + "attributes": { + "create_default_maintainer": false, + "description": "", + "etag": "W/\"3ace9f2ddef61397d31c277d9788b10b548997e4b3af63e8bedd48ac037d75fb\"", + "id": "9757678", + "ldap_dn": "", + "members_count": 6, + "name": "django-community-playground", + "node_id": "T_kwDOCaaRBM4AlOPu", + "parent_team_id": "", + "parent_team_read_id": "", + "parent_team_read_slug": "", + "privacy": "closed", + "slug": "django-community-playground" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + } + ] + }, + { + "mode": "managed", + "type": "github_team_members", + "name": "children", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": "django-community-playground-admins", + "schema_version": 0, + "attributes": { + "id": "9757650", + "members": [ + { + "role": "maintainer", + "username": "Stormheg" + }, + { + "role": "maintainer", + "username": "cunla" + }, + { + "role": "maintainer", + "username": "ryancheley" + }, + { + "role": "maintainer", + "username": "tim-schilling" + }, + { + "role": "maintainer", + "username": "williln" + } + ], + "team_id": "9757650" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "django-community-playground-committers", + "schema_version": 0, + "attributes": { + "id": "9757668", + "members": [ + { + "role": "maintainer", + "username": "Stormheg" + }, + { + "role": "maintainer", + "username": "cunla" + }, + { + "role": "maintainer", + "username": "ryancheley" + }, + { + "role": "maintainer", + "username": "tim-schilling" + }, + { + "role": "maintainer", + "username": "williln" + }, + { + "role": "member", + "username": "priyapahwa" + } + ], + "team_id": "9757668" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + } + ] + }, + { + "mode": "managed", + "type": "github_team_members", + "name": "parents", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": "Admins", + "schema_version": 0, + "attributes": { + "id": "9763562", + "members": [ + { + "role": "maintainer", + "username": "Stormheg" + }, + { + "role": "maintainer", + "username": "cunla" + }, + { + "role": "maintainer", + "username": "ryancheley" + }, + { + "role": "maintainer", + "username": "tim-schilling" + }, + { + "role": "maintainer", + "username": "williln" + } + ], + "team_id": "9763562" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "django-community-playground", + "schema_version": 0, + "attributes": { + "id": "9757678", + "members": [ + { + "role": "maintainer", + "username": "Stormheg" + }, + { + "role": "maintainer", + "username": "cunla" + }, + { + "role": "maintainer", + "username": "ryancheley" + }, + { + "role": "maintainer", + "username": "tim-schilling" + }, + { + "role": "maintainer", + "username": "williln" + }, + { + "role": "member", + "username": "priyapahwa" + } + ], + "team_id": "9757678" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + } + ] + }, + { + "mode": "managed", + "type": "github_team_repository", + "name": "children", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": "django-community-playground-admins-django-commons-playground", + "schema_version": 0, + "attributes": { + "etag": "W/\"3993a06d298b6b2f996a4dc2e0bea9e8a4cac3b14f5f202263e52c89398f66fc\"", + "id": "9757650:django-commons-playground", + "permission": "admin", + "repository": "django-commons-playground", + "team_id": "9757650" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + }, + { + "index_key": "django-community-playground-committers-django-commons-playground", + "schema_version": 0, + "attributes": { + "etag": "W/\"8059d2bfd583f22009db85d9b03c38faa2039416b0e5ed3c7651967314f37132\"", + "id": "9757668:django-commons-playground", + "permission": "maintain", + "repository": "django-commons-playground", + "team_id": "9757668" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + } + ] + }, + { + "mode": "managed", + "type": "github_team_repository", + "name": "parents", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": "django-community-playground-django-commons-playground", + "schema_version": 0, + "attributes": { + "etag": "W/\"5eb93e6ede47d339fe46c6e87e5831b4293538f2e1aba8efb4eaf2ef51e812f5\"", + "id": "9757678:django-commons-playground", + "permission": "triage", + "repository": "django-commons-playground", + "team_id": "9757678" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + } + ] + }, + { + "mode": "managed", + "type": "github_team_settings", + "name": "this", + "provider": "provider[\"registry.terraform.io/integrations/github\"]", + "instances": [ + { + "index_key": "django-community-playground", + "schema_version": 0, + "attributes": { + "id": "T_kwDOCaaRBM4AlOPu", + "review_request_delegation": [], + "team_id": "9757678", + "team_slug": "django-community-playground", + "team_uid": "T_kwDOCaaRBM4AlOPu" + }, + "sensitive_attributes": [], + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ==" + } + ] + }, + { + "mode": "managed", + "type": "random_password", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [] + } + ], + "check_results": null +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..739b6c5 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,91 @@ +# Input Variables +# https://www.terraform.io/language/values/variables + +variable "admins" { + description = "A set of admins to add to the organization" + type = set(string) +} + +variable "github_token" { + description = "The GitHub token used for managing the organization" + type = string + sensitive = true +} + +variable "members" { + description = "A set of members to add to the organization" + type = set(string) + default = [] +} + +variable "organization_secrets" { + description = "Map of secrets to add to the organization" + type = map(object({ + description = string + visibility = string + })) +} + +variable "repositories" { + description = "Map of repositories to create" + type = map(object({ + description = string + allow_auto_merge = optional(bool, false) + allow_merge_commit = optional(bool, false) + allow_rebase_merge = optional(bool, false) + allow_squash_merge = optional(bool, false) + allow_update_branch = optional(bool, false) + enable_branch_protection = optional(bool, true) + has_discussions = optional(bool, true) + has_downloads = optional(bool, true) + has_wiki = optional(bool, false) + is_template = optional(bool, false) + push_allowances = optional(list(string), []) + required_status_checks_contexts = optional(list(string), []) + template = optional(string) + topics = optional(list(string)) + + # In most cases, the visibility of your organizations repository should be private. + # However, we are keeping our code public to encourage others to learn from our work. + + visibility = optional(string, "public") + })) +} + +variable "teams_organization" { + description = "Map of Django Commons organization teams to manage" + type = map(object({ + description = string + maintainers = optional(set(string), []) + members = optional(set(string), []) + permission = optional(string, null) + privacy = optional(string, "closed") + repositories = optional(set(string), []) + review_request_delegation = optional(bool, false) + })) +} + +variable "teams_repositories" { + description = "Map of repository teams to manage" + type = map(object({ + description = string + maintainers = optional(set(string), []) + members = optional(set(string), []) + permission = optional(string, null) + privacy = optional(string, "closed") + repositories = optional(set(string), []) + review_request_delegation = optional(bool, false) + })) +} + +variable "teams_repositories_privileged" { + description = "Map of repository teams with elevated permissions to manage" + type = map(object({ + description = string + maintainers = optional(set(string), []) + members = optional(set(string), []) + permission = optional(string, null) + parent_team_key = string + repositories = optional(set(string), []) + })) +}