diff --git a/.github/workflows/modtest-dev.yaml b/.github/workflows/modtest-dev.yaml new file mode 100644 index 0000000..c98e5f1 --- /dev/null +++ b/.github/workflows/modtest-dev.yaml @@ -0,0 +1,29 @@ +name: "ModTest: dev" + +on: + pull_request: + push: + branches: + - main + +jobs: + modtest: + if : ${{ github.event_name }} == "pull_request" + uses: HappyPathway/centralized-actions/.github/workflows/modtest.yml@main + with: + workspace: dev + workspace_repo: github-repos + workspace_branch: main + repo_clone_type: https + mod_source: repo/github + + github_server: ${{vars.GH_SERVER}} + github_org: ${{ github.repository_owner }} + branch: ${{ github.head_ref }} + terraform_version: ${{vars.TERRAFORM_VERSION}} + terraform_api_token_name: ${{ vars.TERRAFORM_API_TOKEN_NAME }} + terraform_api: ${{vars.TERRAFORM_API}} + + secrets: + TFE_TOKEN: ${{ secrets.TFE_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/terraform-doc.yaml b/.github/workflows/terraform-doc.yaml new file mode 100644 index 0000000..aeb3272 --- /dev/null +++ b/.github/workflows/terraform-doc.yaml @@ -0,0 +1,45 @@ +name: "Terraform Doc" + +on: + pull_request: + +env: + GITHUB_OWNER: ${{ vars.GH_ORG }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} + TF_WORKSPACE: happypathway + TFE_TOKEN: ${{ secrets.TFE_TOKEN }} + +jobs: + tf-doc: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + id: checkout + with: + ref: ${{ github.event.pull_request.head.ref }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3.1.2 + with: + terraform_version: ${{ vars.terraform_version }} + cli_config_credentials_token: ${{ secrets.TFE_TOKEN }} + cli_config_credentials_hostname: ${{ vars.terraform_api }} + + - name: terraform init + run: terraform init -upgrade + + - name: Render terraform docs inside the README.md and push changes back to PR branch + uses: terraform-docs/gh-actions@v1.2.0 + with: + working-dir: . + output-file: README.md + output-method: inject + git-push: "true" + + # terraform-docs/gh-actions@v1.0.0 modifies .git files with owner root:root, and the following steps fail with + # insufficient permission for adding an object to repository database .git/objects + # since the expected user is runner:docker. See https://github.com/terraform-docs/gh-actions/issues/90 + - name: Fix .git owner + run: sudo chown runner:docker -R .git \ No newline at end of file diff --git a/.github/workflows/terraform.yaml b/.github/workflows/terraform.yaml new file mode 100644 index 0000000..0df3f90 --- /dev/null +++ b/.github/workflows/terraform.yaml @@ -0,0 +1,84 @@ +name: "Terraform Validate" + +on: + workflow_dispatch: + push: + branches: + - main + +env: + GITHUB_OWNER: ${{ vars.GH_ORG }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} + TF_WORKSPACE: happypathway + TFE_TOKEN: ${{ secrets.TFE_TOKEN }} + +jobs: + setup-terraform: + outputs: + commit_sha: ${{ steps.checkout.outputs.commit }} + + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + id: checkout + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3.1.2 + with: + terraform_version: ${{ vars.terraform_version }} + cli_config_credentials_token: ${{ secrets.TFE_TOKEN }} + cli_config_credentials_hostname: ${{ vars.terraform_api }} + + - name: terraform init + run: terraform init -upgrade + + - uses: actions/upload-artifact@master + name: Archive Configuration + if: github.ref == 'refs/heads/main' + with: + name: terraform_dir + path: .terraform + retention-days: 1 + include-hidden-files: true + + - uses: actions/upload-artifact@master + name: Archive Lockfile + if: github.ref == 'refs/heads/main' + with: + name: terraform_lockfile + path: .terraform.lock.hcl + retention-days: 1 + include-hidden-files: true + + terraform-validate: + needs: setup-terraform + uses: HappyPathway/centralized-actions/.github/workflows/terraform-test.yml@main + with: + terraform_version: ${{ vars.terraform_version }} + terraform_api: ${{ vars.terraform_api }} + github_username: ${{ github.actor }} + github_email: ${{ github.actor }}@roknsound.com + github_org: ${{ github.repository_owner }} + setup_terraform: true + terraform_init: false + cache: ${{ github.workspace }} + download_cache: true + commit_sha: ${{ needs.setup-terraform.outputs.commit_sha }} + secrets: + TFE_TOKEN: ${{ secrets.TFE_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} + + gtag: + needs: terraform-validate + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + uses: HappyPathway/centralized-actions/.github/workflows/gtag.yml@main + with: + patch: true + github_org: ${{ vars.GH_ORG }} + github_username: ${{ vars.GH_USERNAME }} + github_email: ${{ vars.GH_EMAIL }} + secrets: + GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml new file mode 100644 index 0000000..b69c8ca --- /dev/null +++ b/.github/workflows/terraform.yml @@ -0,0 +1,17 @@ +name: "Terraform" + +on: + workflow_dispatch: + pull_request: + +jobs: + terraform: + uses: HappyPathway/centralized-actions/.github/workflows/terraform.yml@main + with: + terraform_version: 1.9.1 + terraform_api: app.terraform.io + github_username: djaboxx + github_email: git@roknsound.com + github_org: HappyPathway + secrets: + TFE_TOKEN: ${{ secrets.TFE_TOKEN }} diff --git a/CODEOWNERS b/CODEOWNERS index 1ff224d..b3ac177 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,3 @@ -# These owners will be the default owners for everything in the repo. Unless a later match takes precedence \ No newline at end of file +#### How to use this file: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners +# These owners will be the default owners for everything in the repo. Unless a later match takes precedence +* @RoknSound-Public-Modules/terraform-reviewers diff --git a/README.md b/README.md index 413393e..3ccb028 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -# terraform-github-repo -Module to automate creation of -* github related resources - * repo - * default branch - * branch protection rule for main branch - * default codeowners and backend.tf file - * team access + + +[![Terraform Validation](https://github.com/HappyPathway/terraform-github-repo/actions/workflows/terraform.yaml/badge.svg)](https://github.com/HappyPathway/terraform-github-repo/actions/workflows/terraform.yaml) + + +[![Modtest Dev](https://github.com/HappyPathway/terraform-github-repo/actions/workflows/modtest-dev.yaml/badge.svg)](https://github.com/HappyPathway/terraform-github-repo/actions/workflows/modtest-dev.yaml) + + +{{ .Content }} + \ No newline at end of file diff --git a/collaborators.tf b/collaborators.tf index f403f85..5ffe416 100644 --- a/collaborators.tf +++ b/collaborators.tf @@ -1,12 +1,10 @@ # Add a collaborator to a repository resource "github_repository_collaborator" "collaborators" { - for_each = tomap(var.collaborators) + for_each = tomap(var.collaborators) repository = github_repository.repo.name username = each.key permission = each.value -# lifecycle { -# ignore_changes = [ -# permission -# ] -# } + depends_on = [ + github_repository.repo + ] } diff --git a/github_branch.tf b/github_branch.tf index db022d7..cce7ccd 100644 --- a/github_branch.tf +++ b/github_branch.tf @@ -34,7 +34,7 @@ locals { # https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_protection resource "github_branch_protection" "main" { - count = var.enforce_prs ? 1 : 0 + count = var.enforce_prs && !var.github_is_private ? 1 : 0 enforce_admins = var.github_enforce_admins_branch_protection pattern = var.github_default_branch # push_restrictions = var.github_push_restrictions @@ -52,11 +52,10 @@ resource "github_branch_protection" "main" { } dynamic "required_status_checks" { - # A bogus map for a conditional block - for_each = length(var.required_status_checks) > 0 ? ["*"] : [] + for_each = var.required_status_checks == null ? [] : ["*"] content { - contexts = var.required_status_checks - strict = true + contexts = required_status_checks.value.contexts + strict = required_status_checks.value.strict } } diff --git a/github_files.tf b/github_files.tf index ca280c4..a0335c1 100644 --- a/github_files.tf +++ b/github_files.tf @@ -14,8 +14,33 @@ resource "github_repository_file" "codeowners" { } } + +data "github_repository" "template_repo" { + count = var.template_repo == null ? 0 : 1 + full_name = "${var.template_repo_org}/${var.template_repo}" +} + +data "github_ref" "ref" { + count = var.template_repo == null ? 0 : 1 + owner = var.template_repo_org + repository = var.template_repo + ref = "heads/${element(data.github_repository.template_repo, 0).default_branch}" +} + +locals { + extra_files = concat( + var.extra_files, + var.template_repo == null ? [] : [ + { + path = ".TEMPLATE_SHA", + content = data.github_ref.ref[0].sha + } + ] + ) +} + resource "github_repository_file" "extra_files" { - for_each = tomap({ for file in var.extra_files : "${element(split("/", file.path), length(split("/", file.path)) - 1)}" => file }) + for_each = tomap({ for file in local.extra_files : "${element(split("/", file.path), length(split("/", file.path)) - 1)}" => file }) repository = github_repository.repo.name branch = var.github_default_branch file = each.value.path @@ -28,3 +53,17 @@ resource "github_repository_file" "extra_files" { ] } } + +resource "github_repository_file" "managed_extra_files" { + for_each = tomap({ for file in var.managed_extra_files : "${element(split("/", file.path), length(split("/", file.path)) - 1)}" => file }) + repository = github_repository.repo.name + branch = var.github_default_branch + file = each.value.path + content = each.value.content + overwrite_on_create = true + lifecycle { + ignore_changes = [ + branch + ] + } +} diff --git a/github_repo.tf b/github_repo.tf index 7cfe650..c000836 100644 --- a/github_repo.tf +++ b/github_repo.tf @@ -2,6 +2,7 @@ locals { repo_name = var.force_name ? var.name : "${var.name}-${formatdate("YYYYMMDD", timestamp())}" } + resource "github_repository" "repo" { name = local.repo_name description = var.github_repo_description @@ -10,18 +11,19 @@ resource "github_repository" "repo" { allow_merge_commit = var.github_allow_merge_commit allow_squash_merge = var.github_allow_squash_merge allow_rebase_merge = var.github_allow_rebase_merge - archive_on_destroy = true + archive_on_destroy = var.archive_on_destroy delete_branch_on_merge = var.github_delete_branch_on_merge has_projects = var.github_has_projects has_issues = var.github_has_issues has_wiki = var.github_has_wiki topics = var.github_repo_topics - gitignore_template = "Terraform" + gitignore_template = var.gitignore_template is_template = var.is_template archived = var.archived + homepage_url = var.homepage_url + vulnerability_alerts = var.vulnerability_alerts lifecycle { ignore_changes = [ - name, has_issues, has_projects, has_wiki diff --git a/github_repo.tftest.hcl b/github_repo.tftest.hcl new file mode 100644 index 0000000..25ccacb --- /dev/null +++ b/github_repo.tftest.hcl @@ -0,0 +1,21 @@ +# valid_string_concat.tftest.hcl +variables { + force_name = true + github_is_private = true + repo_org = "HappyPathway" + name = "github-repo-test" + enforce_prs = false + archive_on_destroy = false + github_org_teams = [] + admin_teams = [] +} + +run "repo_tests" { + + command = plan + + assert { + condition = github_repository.repo.name == "github-repo-test" + error_message = "Github Repo name did not match expected" + } +} diff --git a/github_team_access.tf b/github_team_access.tf index f825ff8..c530e6a 100644 --- a/github_team_access.tf +++ b/github_team_access.tf @@ -24,4 +24,7 @@ resource "github_team_repository" "admin" { team_id ] } -} \ No newline at end of file + depends_on = [ + github_repository.repo + ] +} diff --git a/variables.tf b/variables.tf index ee0abc0..128c9f6 100644 --- a/variables.tf +++ b/variables.tf @@ -110,9 +110,26 @@ variable "admin_teams" { variable "required_status_checks" { - description = "Required Status Checks" - type = list(any) - default = [] + description = <[, ]). Matrixes should be specified +based on the order of matrix properties in the workflow file. See GitHub Documentation for more +information. For workflows that use reusable workflows, +the pattern is / . +This can extend multiple levels. +EOT + type = object({ + contexts = list(string) + strict = optional(bool, false) + }) + default = null } variable "archived" { @@ -146,6 +163,14 @@ variable "extra_files" { description = "Extra Files" } +variable "managed_extra_files" { + type = list(object({ + path = string, + content = string + })) + default = [] + description = "Managed Extra Files. Changes to Content will be updated" +} variable "pull_request_bypassers" { default = [] @@ -162,8 +187,62 @@ variable "enforce_prs" { type = bool } -variable collaborators { - type = map(string) +variable "collaborators" { + type = map(string) description = "list of repo callaborators" - default = {} + default = {} +} + + +variable "archive_on_destroy" { + type = bool + default = true +} + +variable "vulnerability_alerts" { + type = bool + default = false +} + +variable "gitignore_template" { + default = null +} + +variable "homepage_url" { + default = null +} + +variable "security_and_analysis" { + description = <