diff --git a/README-ja.md b/README-ja.md index 6257adf7..5c4630b1 100644 --- a/README-ja.md +++ b/README-ja.md @@ -48,6 +48,7 @@ - Environment、AWS region、IAM role は `workflow-config.yaml` で定義する。環境の追加・削除は同ファイルの編集で行う。 - 認証は GitHub OIDC。`aws/github-oidc-auth/` が environment ごとの plan / apply role を発行し、executor がそれを引き受ける。 +- GitHub environment `production` は tag pattern `*-v*` に一致する ref からの deployment だけを受け付ける。この設定は `github/repository/` で管理する。 - Terragrunt の remote state は S3 + DynamoDB に保存される。bucket / lock table 名は各 `aws/{service}/root.hcl`(および `github/` 配下の同等ファイル)で定義される。 ### Pipeline Flow diff --git a/README.md b/README.md index a7d2ee81..988eaad5 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ A PR labeled by `panicboat/deploy-actions` is resolved against `workflow-config. - Environments, AWS regions, and IAM roles are defined in `workflow-config.yaml`. Adding or removing an environment means editing that file. - Authentication uses GitHub OIDC. `aws/github-oidc-auth/` issues per-environment plan / apply roles that the executors assume. +- The GitHub environment `production` accepts deployments only from refs that match the tag pattern `*-v*`. The setting is managed in `github/repository/`. - Terragrunt remote state lives in S3 + DynamoDB; bucket and lock-table names are defined in each `aws/{service}/root.hcl` (and the equivalent under `github/`). ### Pipeline Flow diff --git a/github/repository/master/platform.hcl b/github/repository/master/platform.hcl index ee248241..7933f3a1 100644 --- a/github/repository/master/platform.hcl +++ b/github/repository/master/platform.hcl @@ -9,5 +9,6 @@ locals { wiki = false projects = true } + environments = { production = { deployment_tag_patterns = ["*-v*"] } } } } diff --git a/github/repository/modules/main.tf b/github/repository/modules/main.tf index d52908d2..130db629 100644 --- a/github/repository/modules/main.tf +++ b/github/repository/modules/main.tf @@ -38,3 +38,36 @@ resource "github_workflow_repository_permissions" "repository" { default_workflow_permissions = "read" can_approve_pull_request_reviews = false } + +resource "github_repository_environment" "repository" { + for_each = merge([ + for repo_key, repo in var.repositories : { + for env_name, env in repo.environments : + "${repo_key}/${env_name}" => { repo_key = repo_key, name = env_name } + } + ]...) + + repository = github_repository.repository[each.value.repo_key].name + environment = each.value.name + can_admins_bypass = false + + deployment_branch_policy { + protected_branches = false + custom_branch_policies = true + } +} + +resource "github_repository_environment_deployment_policy" "repository" { + for_each = merge([ + for repo_key, repo in var.repositories : merge([ + for env_name, env in repo.environments : { + for pattern in env.deployment_tag_patterns : + "${repo_key}/${env_name}/${pattern}" => { env_key = "${repo_key}/${env_name}", pattern = pattern } + } + ]...) + ]...) + + repository = github_repository_environment.repository[each.value.env_key].repository + environment = github_repository_environment.repository[each.value.env_key].environment + tag_pattern = each.value.pattern +} diff --git a/github/repository/modules/tests/environment.tftest.hcl b/github/repository/modules/tests/environment.tftest.hcl new file mode 100644 index 00000000..a0ddccc4 --- /dev/null +++ b/github/repository/modules/tests/environment.tftest.hcl @@ -0,0 +1,106 @@ +mock_provider "aws" {} + +mock_provider "github" {} + +variables { + project_name = "repository" + environment = "master" + github_org = "panicboat" + github_token = "dummy" +} + +run "creates_tag_policy_for_declared_environment" { + command = plan + + variables { + repositories = { + platform = { + name = "platform" + description = "Platform" + visibility = "public" + features = { + issues = true + wiki = false + projects = true + } + environments = { + production = { deployment_tag_patterns = ["*-v*"] } + } + } + } + } + + assert { + condition = keys(github_repository_environment.repository) == ["platform/production"] + error_message = "One environment must exist per declared repository and environment." + } + + assert { + condition = github_repository_environment.repository["platform/production"].environment == "production" + error_message = "The environment must use the declared name." + } + + assert { + condition = github_repository_environment.repository["platform/production"].can_admins_bypass == false + error_message = "Admins must not bypass the deployment policy." + } + + assert { + condition = github_repository_environment.repository["platform/production"].deployment_branch_policy[0].custom_branch_policies == true + error_message = "The environment must restrict deployments with custom policies." + } + + assert { + condition = github_repository_environment.repository["platform/production"].deployment_branch_policy[0].protected_branches == false + error_message = "The environment must not rely on protected branches." + } + + assert { + condition = keys(github_repository_environment_deployment_policy.repository) == ["platform/production/*-v*"] + error_message = "One deployment policy must exist per declared tag pattern." + } + + assert { + condition = github_repository_environment_deployment_policy.repository["platform/production/*-v*"].tag_pattern == "*-v*" + error_message = "The deployment policy must match tags, not branches." + } + + assert { + condition = github_repository_environment_deployment_policy.repository["platform/production/*-v*"].environment == "production" + error_message = "The deployment policy must target the declared environment." + } + + assert { + condition = github_repository_environment_deployment_policy.repository["platform/production/*-v*"].repository == "platform" + error_message = "The deployment policy must target the declared repository." + } +} + +run "creates_no_environment_by_default" { + command = plan + + variables { + repositories = { + platform = { + name = "platform" + description = "Platform" + visibility = "public" + features = { + issues = true + wiki = false + projects = true + } + } + } + } + + assert { + condition = length(github_repository_environment.repository) == 0 + error_message = "No environment must exist when environments is not declared." + } + + assert { + condition = length(github_repository_environment_deployment_policy.repository) == 0 + error_message = "No deployment policy must exist when environments is not declared." + } +} diff --git a/github/repository/modules/variables.tf b/github/repository/modules/variables.tf index 29bb0879..1b2d859c 100644 --- a/github/repository/modules/variables.tf +++ b/github/repository/modules/variables.tf @@ -43,6 +43,7 @@ variable "repositories" { wiki = bool projects = bool }) + environments = optional(map(object({ deployment_tag_patterns = list(string) })), {}) })) }