-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
2,511 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
# } |
Oops, something went wrong.