Skip to content
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

Migrate org mgmt #57

Merged
merged 3 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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