From 87696163ff06420fbc1179a45f8bbd1ee89c446a Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Fri, 17 May 2024 13:28:07 +0200 Subject: [PATCH 1/5] first POC with GH settings and CODEOWNERS --- .github/workflows/terraform-apply.yaml | 51 +++++++++++ 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, 368 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..90318ae --- /dev/null +++ b/.github/workflows/terraform-apply.yaml @@ -0,0 +1,51 @@ +name: Apply new changes to SolEng repositories + +on: + workflow_dispatch: + pull_request: + branches: + - main + paths: + - terraform-plans/** + on: + push: + branches: + - main + paths: + - terraform-plans/** + +jobs: + runs-on: ubuntu-latest + 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 }} + strategy: + fail-fast: false + matrix: + repository: + - soleng-tf-test-repo + steps: + - name: Checkout branch + uses: actions/checkout@v4 + - name: Install Terraform + uses: sudo snap install terraform + - name: Terraform init + working-directory: ./terraform-plans + run: terraform init + - name: Terraform validate + working-directory: ./terraform-plans + run: terraform validate --no-color + - name: Terraform plan + working-directory: ./terraform-plans + 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 + 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..c896dd6 --- /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..a997bbd --- /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 = true + +} + +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 = true + 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" +} From 6c308878ddd0c90fa67e44cca7aa9ad0be3fb03a Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Fri, 17 May 2024 13:34:01 +0200 Subject: [PATCH 2/5] fix workflow --- .github/workflows/terraform-apply.yaml | 89 ++++++++++++++------------ 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/.github/workflows/terraform-apply.yaml b/.github/workflows/terraform-apply.yaml index 90318ae..a0a1be7 100644 --- a/.github/workflows/terraform-apply.yaml +++ b/.github/workflows/terraform-apply.yaml @@ -7,45 +7,54 @@ on: - main paths: - terraform-plans/** - on: - push: - branches: - - main - paths: - - terraform-plans/** + push: + branches: + - main + paths: + - terraform-plans/** jobs: - runs-on: ubuntu-latest - 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 }} - strategy: - fail-fast: false - matrix: - repository: - - soleng-tf-test-repo - steps: - - name: Checkout branch - uses: actions/checkout@v4 - - name: Install Terraform - uses: sudo snap install terraform - - name: Terraform init - working-directory: ./terraform-plans - run: terraform init - - name: Terraform validate - working-directory: ./terraform-plans - run: terraform validate --no-color - - name: Terraform plan - working-directory: ./terraform-plans - 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 - run: | - terraform apply -no-color -auto-approve \ - -var-file=configs/github.tfvars \ - -var-file=configs/${{ matrix.repository }}.tfvars + 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 From ad8603fdd51a405f9170b10f0dda5a59b5426f4b Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Fri, 24 May 2024 10:44:18 +0200 Subject: [PATCH 3/5] change owner from Canonical to canonical --- terraform-plans/configs/github.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform-plans/configs/github.tfvars b/terraform-plans/configs/github.tfvars index c896dd6..9f4a285 100644 --- a/terraform-plans/configs/github.tfvars +++ b/terraform-plans/configs/github.tfvars @@ -1 +1 @@ -owner = "Canonical" +owner = "canonical" From ad2deba313045377c1b90cf726af85412a376575 Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Tue, 28 May 2024 12:04:22 +0200 Subject: [PATCH 4/5] disable force push --- terraform-plans/modules/GitHub/settings/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform-plans/modules/GitHub/settings/main.tf b/terraform-plans/modules/GitHub/settings/main.tf index a997bbd..1570948 100644 --- a/terraform-plans/modules/GitHub/settings/main.tf +++ b/terraform-plans/modules/GitHub/settings/main.tf @@ -70,7 +70,7 @@ resource "github_branch_protection" "branch_protection" { require_conversation_resolution = true allows_deletions = false - allows_force_pushes = true + allows_force_pushes = false force_push_bypassers = var.force_push_bypassers required_status_checks { From 11663f13b86f3f97a29bee6ed801f495cb7ba68e Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Wed, 29 May 2024 08:36:30 +0200 Subject: [PATCH 5/5] disable delete_branch_on_merge --- terraform-plans/modules/GitHub/settings/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform-plans/modules/GitHub/settings/main.tf b/terraform-plans/modules/GitHub/settings/main.tf index 1570948..c1ff943 100644 --- a/terraform-plans/modules/GitHub/settings/main.tf +++ b/terraform-plans/modules/GitHub/settings/main.tf @@ -26,7 +26,7 @@ resource "github_repository" "repo" { allow_auto_merge = false allow_update_branch = true - delete_branch_on_merge = true + delete_branch_on_merge = false }