From f368238ec5b087d9795b9b3c325c35e534bd42bf Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Thu, 30 May 2024 09:24:05 +0200 Subject: [PATCH] first POC with GH settings and CODEOWNERS (#14) This is an enhancement for trying to GitHub repo settings via terraform and providing also files like CODEOWNER. --- .github/workflows/terraform-apply.yaml | 60 +++++++++++++ terraform-plans/README.md | 48 ++++++++++ terraform-plans/configs/github.tfvars | 1 + .../configs/soleng-tf-test-repo.tfvars | 8 ++ terraform-plans/files/github/CODEOWNERS | 4 + terraform-plans/imports.tf | 9 ++ terraform-plans/main.tf | 17 ++++ .../modules/GitHub/settings/main.tf | 87 +++++++++++++++++++ .../modules/GitHub/settings/variables.tf | 30 +++++++ .../modules/GitHub/workflows/main.tf | 49 +++++++++++ .../modules/GitHub/workflows/variables.tf | 41 +++++++++ terraform-plans/variables.tf | 23 +++++ 12 files changed, 377 insertions(+) create mode 100644 .github/workflows/terraform-apply.yaml create mode 100644 terraform-plans/README.md create mode 100644 terraform-plans/configs/github.tfvars create mode 100644 terraform-plans/configs/soleng-tf-test-repo.tfvars create mode 100644 terraform-plans/files/github/CODEOWNERS create mode 100644 terraform-plans/imports.tf create mode 100644 terraform-plans/main.tf create mode 100644 terraform-plans/modules/GitHub/settings/main.tf create mode 100644 terraform-plans/modules/GitHub/settings/variables.tf create mode 100644 terraform-plans/modules/GitHub/workflows/main.tf create mode 100644 terraform-plans/modules/GitHub/workflows/variables.tf create mode 100644 terraform-plans/variables.tf diff --git a/.github/workflows/terraform-apply.yaml b/.github/workflows/terraform-apply.yaml new file mode 100644 index 0000000..a0a1be7 --- /dev/null +++ b/.github/workflows/terraform-apply.yaml @@ -0,0 +1,60 @@ +name: Apply new changes to SolEng repositories + +on: + workflow_dispatch: + pull_request: + branches: + - main + paths: + - terraform-plans/** + push: + branches: + - main + paths: + - terraform-plans/** + +jobs: + terraform: + name: Run Terraform + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + repository: + - soleng-tf-test-repo + steps: + - name: Checkout branch + uses: actions/checkout@v4 + - name: Install Terraform + run: sudo snap install terraform --classic + - name: Terraform init + working-directory: ./terraform-plans + run: terraform init + - name: Terraform validate + working-directory: ./terraform-plans + env: + GITHUB_APP_ID: ${{ secrets.SOLENG_APP_ID }} + GITHUB_APP_INSTALLATION_ID: ${{ secrets.SOLENG_APP_INSTALLATION_ID }} + GITHUB_APP_PEM_FILE: ${{ secrets.SOLENG_APP_PEM_FILE }} + run: terraform validate -no-color + - name: Terraform plan + working-directory: ./terraform-plans + env: + GITHUB_APP_ID: ${{ secrets.SOLENG_APP_ID }} + GITHUB_APP_INSTALLATION_ID: ${{ secrets.SOLENG_APP_INSTALLATION_ID }} + GITHUB_APP_PEM_FILE: ${{ secrets.SOLENG_APP_PEM_FILE }} + run: | + terraform plan -no-color \ + -var-file=configs/github.tfvars \ + -var-file=configs/${{ matrix.repository }}.tfvars + - name: Terraform Apply only on merge to main + if: ${{ github.event_name == 'push' }} + working-directory: ./terraform-plans + env: + GITHUB_APP_ID: ${{ secrets.SOLENG_APP_ID }} + GITHUB_APP_INSTALLATION_ID: ${{ secrets.SOLENG_APP_INSTALLATION_ID }} + GITHUB_APP_PEM_FILE: ${{ secrets.SOLENG_APP_PEM_FILE }} + run: | + terraform apply -no-color -auto-approve \ + -var-file=configs/github.tfvars \ + -var-file=configs/${{ matrix.repository }}.tfvars diff --git a/terraform-plans/README.md b/terraform-plans/README.md new file mode 100644 index 0000000..248d9a0 --- /dev/null +++ b/terraform-plans/README.md @@ -0,0 +1,48 @@ +# Terraform plans for Solution Engineering + +Currently these plans are only for setting GitHub repos and to add workflow files. + +## How to start + +1. Initialize Terraform. + +```bash +terraform init +``` + +1. [Optional] If it's used locally for multiple repos. Create a workspace for each repo, otherwise terraform will try to overwrite the existing resource, e.g. repo. + +```bash +terraform workspace new +``` + +1. Set GitHub authenetication for GitHub application. +```bash +export GITHUB_APP_ID="1234" +export GITHUB_APP_INSTALLATION_ID="56789" +export GITHUB_APP_PEM_FILE=$(cat ./my-app.private-key.pem) +``` + +1. [Optional] Create custom configuration or use one of defined in config directory. + +```tfvars +owner = "" +repository = "" +branch = "main" +workflow_files = { + jira_sync_config = { + source = "./files/workflows/jira_sync_config.yaml" + destination = ".github/workflows/jira_sync_config.yaml" + } + codeowners = { + source = "./files/workflows/CODEOWNERS" + destination = ".github/CODEOWNERS" + } +} +``` + +1. Generate Terraform plan to validate it. + +```bash +terraform plan -var-file=configs/github.tfvars -var-file=configs/soleng-tf-test-repo.tfvars +``` diff --git a/terraform-plans/configs/github.tfvars b/terraform-plans/configs/github.tfvars new file mode 100644 index 0000000..9f4a285 --- /dev/null +++ b/terraform-plans/configs/github.tfvars @@ -0,0 +1 @@ +owner = "canonical" diff --git a/terraform-plans/configs/soleng-tf-test-repo.tfvars b/terraform-plans/configs/soleng-tf-test-repo.tfvars new file mode 100644 index 0000000..4fbb1a1 --- /dev/null +++ b/terraform-plans/configs/soleng-tf-test-repo.tfvars @@ -0,0 +1,8 @@ +repository = "soleng-tf-test-repo" +branch = "main" +workflow_files = { + codeowners = { + source = "./files/github/CODEOWNERS" + destination = ".github/CODEOWNERS" + } +} diff --git a/terraform-plans/files/github/CODEOWNERS b/terraform-plans/files/github/CODEOWNERS new file mode 100644 index 0000000..dd3bf64 --- /dev/null +++ b/terraform-plans/files/github/CODEOWNERS @@ -0,0 +1,4 @@ +# These owners will be the default owners for everything in the repo. Unless a +# later match takes precedence, @canonical/soleng-reviewers will be requested for +# review when someone opens a pull request. + @canonical/soleng-reviewers diff --git a/terraform-plans/imports.tf b/terraform-plans/imports.tf new file mode 100644 index 0000000..e54fc18 --- /dev/null +++ b/terraform-plans/imports.tf @@ -0,0 +1,9 @@ +import { + to = module.github_settings.github_repository.repo + id = var.repository +} + +import { + to = module.github_settings.github_branch_protection.branch_protection + id = "${var.repository}:${var.branch}" +} diff --git a/terraform-plans/main.tf b/terraform-plans/main.tf new file mode 100644 index 0000000..45955a6 --- /dev/null +++ b/terraform-plans/main.tf @@ -0,0 +1,17 @@ +module "github_settings" { + source = "./modules/GitHub/settings" + owner = var.owner + repository = var.repository + branch = var.branch + force_push_bypassers = ["${var.owner}/soleng-admin"] + dismissal_restrictions = ["${var.owner}/soleng-admin", "${var.owner}/soleng-reviewers"] + pull_request_bypassers = ["${var.owner}/soleng-admin"] +} + +module "github_workflow_files" { + source = "./modules/GitHub/workflows" + owner = var.owner + repository = var.repository + branch = var.branch + workflow_files = var.workflow_files +} diff --git a/terraform-plans/modules/GitHub/settings/main.tf b/terraform-plans/modules/GitHub/settings/main.tf new file mode 100644 index 0000000..c1ff943 --- /dev/null +++ b/terraform-plans/modules/GitHub/settings/main.tf @@ -0,0 +1,87 @@ +terraform { + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +} + +provider "github" { + owner = var.owner + app_auth {} # using environmet variables for auth +} + +resource "github_repository" "repo" { + name = var.repository + + has_issues = true + has_projects = false + has_wiki = false + has_discussions = false + + allow_merge_commit = false + allow_squash_merge = true + allow_rebase_merge = false + allow_auto_merge = false + + allow_update_branch = true + delete_branch_on_merge = false + +} + +data "github_team" "admins" { + slug = "soleng-admin" +} + +data "github_team" "reviewers" { + slug = "soleng-reviewers" +} + +data "github_team" "engineering" { + slug = "solutions-engineering" +} + +resource "github_repository_collaborators" "repo_collaborators" { + repository = var.repository + + team { + permission = "admin" + team_id = data.github_team.admins.id + } + + team { + permission = "maintain" + team_id = data.github_team.reviewers.id + } + + team { + permission = "push" + team_id = data.github_team.engineering.id + } +} + +resource "github_branch_protection" "branch_protection" { + repository_id = github_repository.repo.node_id + pattern = var.branch + enforce_admins = true + require_signed_commits = true + required_linear_history = true + require_conversation_resolution = true + allows_deletions = false + + allows_force_pushes = false + force_push_bypassers = var.force_push_bypassers + + required_status_checks { + strict = true + } + + required_pull_request_reviews { + dismiss_stale_reviews = true + dismissal_restrictions = var.dismissal_restrictions + pull_request_bypassers = var.pull_request_bypassers + require_code_owner_reviews = true + required_approving_review_count = 2 + } +} diff --git a/terraform-plans/modules/GitHub/settings/variables.tf b/terraform-plans/modules/GitHub/settings/variables.tf new file mode 100644 index 0000000..8c5c75a --- /dev/null +++ b/terraform-plans/modules/GitHub/settings/variables.tf @@ -0,0 +1,30 @@ +variable "owner" { + type = string + description = "GitHub repository owner" +} + +variable "repository" { + type = string + description = "GitHub repository name" +} + +variable "branch" { + type = string + description = "Branch name" + default = "main" +} + +variable "force_push_bypassers" { + type = list(any) + description = "List of user / groups that are allowed to bypass force push restrictions." +} + +variable "dismissal_restrictions" { + type = list(any) + description = "List of user / groups with dismissal access." +} + +variable "pull_request_bypassers" { + type = list(any) + description = "List of user / groups that are allowed to bypass pull request requirements." +} diff --git a/terraform-plans/modules/GitHub/workflows/main.tf b/terraform-plans/modules/GitHub/workflows/main.tf new file mode 100644 index 0000000..3b6ed1b --- /dev/null +++ b/terraform-plans/modules/GitHub/workflows/main.tf @@ -0,0 +1,49 @@ +terraform { + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +} + +provider "github" { + owner = var.owner + app_auth {} # using environmet variables for auth +} + + +resource "random_string" "update_uid" { + length = 8 + numeric = true + special = false +} + +resource "github_branch" "workflows_branch" { + repository = var.repository + branch = "${var.pr_branch}-${random_string.update_uid.id}" + source_branch = var.branch +} + +resource "github_repository_file" "workflows_files" { + for_each = var.workflow_files + repository = var.repository + branch = github_branch.workflows_branch.branch + file = each.value.destination + content = file(each.value.source) + commit_message = "update ${each.value.destination}" + overwrite_on_create = true +} + +resource "github_repository_pull_request" "workflows_update_pr" { + base_repository = var.repository + base_ref = var.branch + head_ref = github_branch.workflows_branch.branch + title = var.pr_title + body = var.pr_body + + depends_on = [ + github_branch.workflows_branch, + github_repository_file.workflows_files + ] +} diff --git a/terraform-plans/modules/GitHub/workflows/variables.tf b/terraform-plans/modules/GitHub/workflows/variables.tf new file mode 100644 index 0000000..7a84ea7 --- /dev/null +++ b/terraform-plans/modules/GitHub/workflows/variables.tf @@ -0,0 +1,41 @@ +variable "owner" { + type = string + description = "GitHub repository owner" +} + +variable "repository" { + type = string + description = "GitHub repository name." +} + +variable "branch" { + type = string + description = "Default branch name." + default = "main" +} + +variable "pr_branch" { + type = string + description = "Pull request branch name." + default = "chore/update-workflows" +} + +variable "pr_title" { + type = string + description = "Pull request title." + default = "Updating workflows files" +} + +variable "pr_body" { + type = string + description = "Pull request body message." + default = "Updates workflows files by SolEng bot." +} + +variable "workflow_files" { + type = map(object({ + source = string + destination = string + })) + description = "GitHub workflow files" +} diff --git a/terraform-plans/variables.tf b/terraform-plans/variables.tf new file mode 100644 index 0000000..574e5ed --- /dev/null +++ b/terraform-plans/variables.tf @@ -0,0 +1,23 @@ +variable "owner" { + type = string + description = "GitHub repository owner" +} + +variable "repository" { + type = string + description = "GitHub repository name" +} + +variable "branch" { + type = string + description = "git branch name" + default = "main" +} + +variable "workflow_files" { + type = map(object({ + source = string + destination = string + })) + description = "GitHub workflow files" +}