From ada369129acd46ce63a7696cf0715aa968b1e2c5 Mon Sep 17 00:00:00 2001 From: Dave Arnold Date: Wed, 17 Apr 2024 09:42:02 -0700 Subject: [PATCH] initial add --- .gitignore | 30 ++++++++ CODEOWNERS | 1 + README.md | 9 +++ action_secrets.tf | 13 ++++ data.tf | 3 + github_branch.tf | 68 +++++++++++++++++++ github_files.tf | 29 ++++++++ github_repo.tf | 36 ++++++++++ github_team_access.tf | 27 ++++++++ outputs.tf | 3 + templates/CODEOWNERS | 4 ++ variables.tf | 154 ++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 377 insertions(+) create mode 100644 .gitignore create mode 100644 CODEOWNERS create mode 100644 README.md create mode 100644 action_secrets.tf create mode 100644 data.tf create mode 100644 github_branch.tf create mode 100644 github_files.tf create mode 100644 github_repo.tf create mode 100644 github_team_access.tf create mode 100644 outputs.tf create mode 100644 templates/CODEOWNERS create mode 100644 variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95dfea7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +# +# example.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* +.terraform.lock.hcl diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..1ff224d --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +# These owners will be the default owners for everything in the repo. Unless a later match takes precedence \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fb0364 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# terraform-tfe-module +Module to automate creation of +* terraform workspace +* github related resources + * repo + * default branch + * branch protection rule for main branch + * default codeowners and backend.tf file + * team access diff --git a/action_secrets.tf b/action_secrets.tf new file mode 100644 index 0000000..0470449 --- /dev/null +++ b/action_secrets.tf @@ -0,0 +1,13 @@ +resource "github_actions_secret" "secret" { + for_each = tomap({ for secret in var.secrets : secret.name => secret.value }) + secret_name = each.key + plaintext_value = each.value + repository = github_repository.repo.name +} + +resource "github_actions_variable" "variable" { + for_each = tomap({ for _var in var.vars : _var.name => _var.value }) + repository = github_repository.repo.name + variable_name = each.key + value = each.value +} diff --git a/data.tf b/data.tf new file mode 100644 index 0000000..02ee089 --- /dev/null +++ b/data.tf @@ -0,0 +1,3 @@ +locals { + codeowners = length(var.additional_codeowners) > 0 ? flatten(["${var.repo_org}/${var.github_codeowners_team}", formatlist("${var.repo_org}/%s", var.additional_codeowners)]) : ["${var.repo_org}/${var.github_codeowners_team}"] +} diff --git a/github_branch.tf b/github_branch.tf new file mode 100644 index 0000000..3525f00 --- /dev/null +++ b/github_branch.tf @@ -0,0 +1,68 @@ + +# https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/team +# data "github_team" "github_codeowners_team" { +# slug = var.github_codeowners_team +# } + +# not creating main branch because its created by default when repo is created +resource "github_branch" "branch" { + count = var.github_default_branch == "main" ? 0 : 1 + repository = github_repository.repo.name + branch = var.github_default_branch +} + + +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_default +resource "github_branch_default" "default_main_branch" { + count = var.github_default_branch == "main" ? 0 : 1 + repository = github_repository.repo.name + branch = var.github_default_branch + depends_on = [ + github_branch.branch + ] +} + + +data "github_user" "pull_request_bypassers" { + for_each = toset(var.pull_request_bypassers) + username = each.value +} + +locals { + pull_request_bypassers = [for user in data.github_user.pull_request_bypassers : user.node_id] +} + +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_protection +resource "github_branch_protection" "main" { + enforce_admins = var.github_enforce_admins_branch_protection + pattern = var.github_default_branch + push_restrictions = var.github_push_restrictions + repository_id = github_repository.repo.node_id + required_pull_request_reviews { + dismiss_stale_reviews = var.github_dismiss_stale_reviews + require_code_owner_reviews = var.github_require_code_owner_reviews + required_approving_review_count = var.github_required_approving_review_count + pull_request_bypassers = local.pull_request_bypassers + } + lifecycle { + ignore_changes = [ + required_status_checks[0].contexts + ] + } + + dynamic "required_status_checks" { + # A bogus map for a conditional block + for_each = length(var.required_status_checks) > 0 ? ["*"] : [] + content { + contexts = var.required_status_checks + strict = true + } + } + + depends_on = [ + # first let the automation create the codeowners and backend file then only create branch protection rule + # if branch protection rule is created first, codeowners will fail + github_repository_file.codeowners, + github_repository_file.extra_files + ] +} diff --git a/github_files.tf b/github_files.tf new file mode 100644 index 0000000..07ae2a9 --- /dev/null +++ b/github_files.tf @@ -0,0 +1,29 @@ +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_file +resource "github_repository_file" "codeowners" { + repository = github_repository.repo.name + branch = var.github_default_branch + file = "CODEOWNERS" + content = templatefile("${path.module}/templates/CODEOWNERS", { codeowners = local.codeowners }) + overwrite_on_create = true + lifecycle { + ignore_changes = [ + content, + branch + ] + } +} + +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 }) + 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 = [ + content, + branch + ] + } +} \ No newline at end of file diff --git a/github_repo.tf b/github_repo.tf new file mode 100644 index 0000000..cb8c751 --- /dev/null +++ b/github_repo.tf @@ -0,0 +1,36 @@ +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 + visibility = var.github_is_private ? "private" : "public" + auto_init = var.github_auto_init + 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 + 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" + is_template = var.is_template + archived = var.archived + lifecycle { + ignore_changes = [ + name + ] + } + dynamic "template" { + # A bogus map for a conditional block + for_each = var.template_repo == null ? [] : ["*"] + content { + owner = var.template_repo_org + repository = var.template_repo + # include_all_branches = var.template_include_all_branches + } + } +} diff --git a/github_team_access.tf b/github_team_access.tf new file mode 100644 index 0000000..f825ff8 --- /dev/null +++ b/github_team_access.tf @@ -0,0 +1,27 @@ +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository +data "github_organization_teams" "root_teams" { + count = var.github_org_teams == null ? 1 : 0 + root_teams_only = false +} + +locals { + github_org_teams = var.github_org_teams == null ? data.github_organization_teams.root_teams[0].teams : var.github_org_teams + github_teams = { for obj in local.github_org_teams : "${obj.slug}" => obj.id } +} + +# data "github_team" "nit_admin" { +# slug = "nit" +# } + +# https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_repository +resource "github_team_repository" "admin" { + for_each = toset(var.admin_teams) + team_id = lookup(local.github_teams, each.value) + repository = github_repository.repo.name + permission = "admin" + lifecycle { + ignore_changes = [ + team_id + ] + } +} \ No newline at end of file diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..4544f4f --- /dev/null +++ b/outputs.tf @@ -0,0 +1,3 @@ +output "github_repo" { + value = github_repository.repo +} diff --git a/templates/CODEOWNERS b/templates/CODEOWNERS new file mode 100644 index 0000000..3da2911 --- /dev/null +++ b/templates/CODEOWNERS @@ -0,0 +1,4 @@ +# These owners will be the default owners for everything in the repo. Unless a later match takes precedence +%{ for codeowner in codeowners ~} +* @${codeowner} +%{ endfor ~} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..89234e1 --- /dev/null +++ b/variables.tf @@ -0,0 +1,154 @@ +variable "name" { + description = "Name of the terraform workspace and optionally github repo" +} + +variable "repo_org" { + default = null +} + +variable "github_codeowners_team" { + default = "terraform-reviewers" +} + +variable "github_repo_description" { + default = "Terraform Workspace" +} + +variable "github_repo_topics" { + description = "Github Repo Topics" + type = list(any) + default = [] +} + +variable "github_push_restrictions" { + description = "Github Push Restrictions" + type = list(any) + default = [] +} +variable "github_is_private" { + default = true +} +variable "github_auto_init" { + default = true +} +variable "github_allow_merge_commit" { + default = false +} +variable "github_allow_squash_merge" { + default = true +} +variable "github_allow_rebase_merge" { + default = false +} +variable "github_delete_branch_on_merge" { + default = true +} +variable "github_has_projects" { + default = true +} +variable "github_has_issues" { + default = false +} +variable "github_has_wiki" { + default = true +} +variable "github_default_branch" { + default = "main" +} +variable "github_required_approving_review_count" { + default = 1 +} +variable "github_require_code_owner_reviews" { + default = true +} +variable "github_dismiss_stale_reviews" { + default = true +} +variable "github_enforce_admins_branch_protection" { + default = true +} + +variable "additional_codeowners" { + description = "Enable adding of Codeowner Teams" + type = list(any) + default = [] +} + +variable "prefix" { + default = null +} + +variable "force_name" { + description = "Force Naming of Repo. If forced, archive management will not operate on this repo" + default = false +} + +variable "github_org_teams" { + type = list(any) + description = "provide module with list of teams so that module does not need to look them up" + default = null +} + +variable "template_repo_org" { + default = null +} + +variable "template_repo" { + default = null +} + +variable "is_template" { + default = false +} + + +variable "admin_teams" { + description = "Admin Teams" + type = list(any) + default = [] +} + + +variable "required_status_checks" { + description = "Required Status Checks" + type = list(any) + default = [] +} + +variable "archived" { + default = false +} + +variable "secrets" { + type = list(object({ + name = string, + value = string + })) + default = [] + description = "Github Action Secrets" +} + +variable "vars" { + type = list(object({ + name = string, + value = string + })) + default = [] + description = "Github Action Vars" +} + +variable "extra_files" { + type = list(object({ + path = string, + content = string + })) + default = [] + description = "Extra Files" +} + + +variable "pull_request_bypassers" { + default = [] + type = list(any) +} +