Skip to content

Commit

Permalink
Migrate org mgmt (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
cunla authored Oct 6, 2024
1 parent 1001216 commit 48072c3
Show file tree
Hide file tree
Showing 18 changed files with 2,511 additions and 44 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/apply.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: "Apply org changes"

on:
push:
branches:
- main
paths:
- 'terraform/production/*.tfvars'

jobs:
apply-changes:
name: "Org changes apply"
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
- name: Commit changes
if: ${{ always() }}
uses: devops-infra/[email protected]
with:
github_token: "${{ secrets.GITHUB_TOKEN }}"
commit_prefix: "[AUTO]"
commit_message: "State changes after apply"
force: false
44 changes: 0 additions & 44 deletions .github/workflows/new_member.yml

This file was deleted.

62 changes: 62 additions & 0 deletions .github/workflows/plan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: "Plan org changes and list them in a PR"
on:
pull_request:
branches:
- main
paths:
- 'terraform/production/*.tfvars'

jobs:
format-terraform-code:
name: "Format Terraform code"
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: "${{ github.event.pull_request.head.ref }}"


- name: terraform fmt
uses: dflook/terraform-fmt@2ec321e746af7edf90e43513dda2086a92a07b4c
with:
path: "terraform"

- name: Commit changes
uses: devops-infra/[email protected]
with:
github_token: "${{ secrets.GITHUB_TOKEN }}"
commit_prefix: "[AUTO]"
commit_message: "Format code"
force: false
# target_branch: "${{ github.event.pull_request.head.ref }}"

plan-changes:
name: "Org changes plan"
runs-on: ubuntu-latest
needs: [ "format-terraform-code" ]
permissions:
pull-requests: write
contents: write

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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.backup
notes.txt
.terraform
.terraform.lock.hcl
.idea
.dflook-terraform-github-actions
tags
102 changes: 102 additions & 0 deletions terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
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`)
- `resources-*.tf` - define resources, like `github_repository`, `github_team`, etc.
- `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"
homepage_url = "" # optional, default is ""
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 = true # optional, default is true
allow_update_branch = true # optional, default is true
delete_branch_on_merge = 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 = []
template = "" # optional, default is ""
topics = []
visibility = "public" # optional, default is "public"
is_django_commons_repo = optional(bool, false) # Do not create teams for repository
enable_branch_protection = true # optional, default is true
required_status_checks_contexts = [] # optional, default is []
admins = [] # Members of the repository's admin and repository teams. Have admin permissions
committers = [] # Members of the repository's committers and repository teams. Have write permissions
members = [] # Members of the repository team. Have triage permissions
}
# ...
}
```

# 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=production/org.tfvars -var-file=production/repositories.tfvars -var github_token=...`
6. To apply the changes,
run: `terraform apply -var-file=production/org.tfvars -var-file=production/repositories.tfvars -var github_token=...`

# Integration with GitHub Actions

The repository is configured to run `terraform plan` on every new pull-request as well as an update to a pull-request
and list the expected changes as a comment on the pull-request.
Once the pull-request is merged to the `main` branch, `terraform apply` applies the changes to the GitHub organization, and
the updated current state is committed to the `main` branch.
To achieve this, the workflows use `TERRAFORM_MANAGEMENT_GITHUB_TOKEN` secret to plan/apply terraform changes.

`TERRAFORM_MANAGEMENT_GITHUB_TOKEN` is a fine-grained personal access token with permissions the following permissions
required (see documentation [here][2]):

- The `repo` permission 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
- Additionally, the token should have permissions to write content to the repository (see, [here][3])

[1]: https://developer.hashicorp.com/terraform/tutorials/it-saas/github-user-teams#configure-your-credentials

[2]: https://developer.hashicorp.com/terraform/tutorials/it-saas/github-user-teams#configure-your-credentials

[3]: https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository
9 changes: 9 additions & 0 deletions terraform/backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Backend Configuration
# https://www.terraform.io/language/settings/backends/configuration

terraform {
backend "local" {
path = "tfstate.json"
}

}
20 changes: 20 additions & 0 deletions terraform/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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
}

members = {
for user in var.members : user => "member"
}

users = merge(local.admins, local.members)
}
60 changes: 60 additions & 0 deletions terraform/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Required Providers
# https://www.terraform.io/docs/language/providers/requirements.html#requiring-providers

terraform {
required_providers {
github = {
source = "integrations/github"
}
}
}

# Github Provider
# https://registry.terraform.io/providers/integrations/github/latest/docs

provider "github" {
owner = "django-commons"
token = var.github_token
}


# Random Password Resource
# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password

############# GitHub Organization Secret Resource #############

# 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
# }

# 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
# }
Loading

0 comments on commit 48072c3

Please sign in to comment.