From 7b0b311ba0dd183ab1eeb301531a88efa12c0d82 Mon Sep 17 00:00:00 2001 From: David Arnold <10138997+djaboxx@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:50:16 -0800 Subject: [PATCH] Create repo (#12) * Refactor repository references to use local variable for consistency * Enhance GitHub repository management by adding environment support, refining secret handling, and improving collaborator permissions mapping * terraform-docs: automated action * Update terraform.yml * Refactor GitHub workflows and update Terraform configurations for improved repository management * Clean up whitespace in Terraform configuration files for consistency * Remove redundant variable definition for GitHub branch update allowance * Remove unused GitHub repository variables from Terraform configuration * Remove unused push restrictions from GitHub branch protection configuration * Remove unused security and analysis settings from GitHub repository configuration * Refactor GitHub repository configuration to remove unused security settings and update visibility to public * Format code for consistency in GitHub repository configuration * Add environment variables for GitHub token and owner in Terraform test workflow --------- Co-authored-by: Dave Arnold Co-authored-by: github-actions[bot] --- .github/workflows/modtest-dev.yaml | 29 --- .github/workflows/terraform-doc.yaml | 45 ---- .github/workflows/terraform-test.yml | 58 +++++ .github/workflows/terraform.yaml | 84 ------- .github/workflows/terraform.yml | 17 -- .terraform.lock.hcl | 24 ++ README.md | 251 +++++++++++++++++++-- action_secrets.tf | 17 +- collaborators.tf | 23 +- environment.tf | 56 +++++ github_branch.tf | 60 ++--- github_files.tf | 15 +- github_repo.tf | 97 ++++++-- github_repo.tftest.hcl | 155 ++++++++++++- github_team_access.tf | 31 +-- outputs.tf | 55 ++++- variables.tf | 318 +++++++++++++++++++-------- 17 files changed, 965 insertions(+), 370 deletions(-) delete mode 100644 .github/workflows/modtest-dev.yaml delete mode 100644 .github/workflows/terraform-doc.yaml create mode 100644 .github/workflows/terraform-test.yml delete mode 100644 .github/workflows/terraform.yaml delete mode 100644 .github/workflows/terraform.yml create mode 100644 .terraform.lock.hcl create mode 100644 environment.tf diff --git a/.github/workflows/modtest-dev.yaml b/.github/workflows/modtest-dev.yaml deleted file mode 100644 index c98e5f1..0000000 --- a/.github/workflows/modtest-dev.yaml +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index aeb3272..0000000 --- a/.github/workflows/terraform-doc.yaml +++ /dev/null @@ -1,45 +0,0 @@ -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-test.yml b/.github/workflows/terraform-test.yml new file mode 100644 index 0000000..c9e7e55 --- /dev/null +++ b/.github/workflows/terraform-test.yml @@ -0,0 +1,58 @@ +name: "Terraform Test and Tag" + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: write + pull-requests: read + +jobs: + terraform: + name: "Terraform Test" + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_OWNER: ${{ vars.GH_ORG }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "~>1.6.0" + terraform_wrapper: false + + - name: Terraform Format + id: fmt + run: terraform fmt -check + continue-on-error: false + + - name: Terraform Init + id: init + run: terraform init -backend=false + + - name: Terraform Validate + id: validate + run: terraform validate + + - name: Run Terraform Tests + id: test + run: terraform test + + - name: Bump version and push tag + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: anothrNick/github-tag-action@1.67.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEFAULT_BUMP: patch + WITH_V: true \ No newline at end of file diff --git a/.github/workflows/terraform.yaml b/.github/workflows/terraform.yaml deleted file mode 100644 index 0df3f90..0000000 --- a/.github/workflows/terraform.yaml +++ /dev/null @@ -1,84 +0,0 @@ -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 deleted file mode 100644 index b69c8ca..0000000 --- a/.github/workflows/terraform.yml +++ /dev/null @@ -1,17 +0,0 @@ -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/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..18b0383 --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/integrations/github" { + version = "6.5.0" + hashes = [ + "h1:KN6W+TRczQXMQLAI5Cn/xpvJzq8r+/AQCZaxGURXQ3A=", + "zh:3088bfd30c51ebfcb7c8d829465ec7b3c19af684cf1aff1ea1111ad3c6421c11", + "zh:34f9054b0123f9fa7ab8ebc73591d2cf502f1cc75e7594bde42ce799fcac32b6", + "zh:406dc2e63d43a24ac4f1b004e5c60ada3347207ea750bbd51e6199eb7f044f9f", + "zh:43e7b6cb7e5062d9b7b7cf4d23f6ea99fb9605fb014fede62cda307051063c05", + "zh:6a0923ebcc09cb98c488c11582375d2145ba965d1e6f2f69c077be8e1224020b", + "zh:a2331f06b7ed57e83eadb784211067d675826f67cf0ed051c8ab20335d83de9a", + "zh:a3f82213c98319f20438bdb92145ce1b0407cd8b8eec9745c036db10deb3d3a2", + "zh:b4b8db8537d8e6fb3f05ed875726823e1dc6925c479db8749016e71568ebafc4", + "zh:cdcf76f6f6f5c638db540490ab35bb1aacfc27204f1197004da5e950024afc06", + "zh:de36cea60efe2b74cec958f88ec5c39d467ad9443c9c9e311424c3db229c4e78", + "zh:dfb8949edc6722da66c78a19ccb1b81ac855439a28ca3badfdac5c10bbf2190d", + "zh:e1a81734cc81f4f51dd11ca8a62b420f68e72d00835ed54f84d71bd56d19f37f", + "zh:ec0d51640c3e3cf933c73d0ed79ba8b395d1b94fed8117a6438dba872aa5561f", + "zh:ec59b7c420a2358e9750e9c6a8a5ef26ccbb8a2cae417e115e86d63520759ea5", + "zh:fbd1fee2c9df3aa19cf8851ce134dea6e45ea01cb85695c1726670c285797e25", + ] +} diff --git a/README.md b/README.md index d2d1411..0769638 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Terraform GitHub Repository Module -A comprehensive Terraform module for managing GitHub repositories with advanced features like branch protection, file management, and team access control. +A comprehensive Terraform module for managing GitHub repositories with advanced features like branch protection, file management, and team access control. You can use this module to create new repositories or manage existing ones. ## Features - +- Create new repositories or manage existing ones - Complete GitHub repository management - Branch protection rules - File content management @@ -15,37 +15,35 @@ A comprehensive Terraform module for managing GitHub repositories with advanced ## Usage +### Creating a New Repository ```hcl -module "repository" { +module "new_repository" { source = "HappyPathway/repo/github" - + name = "my-repository" repo_org = "MyOrganization" + create_repo = true # Default, can be omitted force_name = true github_repo_description = "Repository description" github_repo_topics = ["terraform", "automation"] github_is_private = false - github_has_issues = true - github_has_projects = true - github_has_wiki = true - vulnerability_alerts = true - gitignore_template = "Node" - - # Managed file content - managed_extra_files = { - "README.md" = { - content = file("${path.module}/templates/readme.md") - overwrite = true - } - "docs/getting-started.md" = { - content = file("${path.module}/templates/getting-started.md") - overwrite = false - } - } } ``` -## Examples +### Managing an Existing Repository +```hcl +module "existing_repository" { + source = "HappyPathway/repo/github" + + name = "existing-repository" + repo_org = "MyOrganization" + create_repo = false # Tell Terraform to manage existing repository + + # All other settings will be applied to the existing repository + github_repo_topics = ["managed", "terraform"] + github_has_issues = true +} +``` ### Basic Repository @@ -99,10 +97,108 @@ module "managed_repo" { } ``` +## Inputs + +| Name | Description | Type | Required | Default | +|------|-------------|------|----------|---------| +| name | Repository name | string | Yes | - | +| repo_org | GitHub organization name | string | No | null | +| create_repo | Whether to create a new repository or manage existing | bool | No | true | +| force_name | Keep exact repository name (no date suffix) | bool | No | false | +| github_repo_description | Repository description | string | No | null | +| github_repo_topics | Repository topics | list(string) | No | [] | +| github_is_private | Make repository private | bool | No | true | +| // ...other inputs... | + +## Outputs + +| Name | Description | +|------|-------------| +| github_repo | All repository attributes (see details below) | +| ssh_clone_url | SSH clone URL | +| node_id | Repository node ID for GraphQL | +| full_name | Full repository name (org/repo) | +| repo_id | Repository ID | +| html_url | Repository web URL | +| http_clone_url | HTTPS clone URL | +| git_clone_url | Git protocol clone URL | +| visibility | Repository visibility (public/private) | +| default_branch | Default branch name | +| topics | Repository topics | +| template | Template repository info | + +### Complete Repository Attributes + +The `github_repo` output includes: + +Basic Info: +- `name` - Repository name +- `full_name` - Full repository name (org/repo) +- `description` - Repository description +- `html_url` - GitHub web URL +- `ssh_clone_url` - SSH clone URL +- `http_clone_url` - HTTPS clone URL +- `git_clone_url` - Git protocol URL +- `visibility` - Public or private status + +Settings: +- `topics` - Repository topics +- `has_issues` - Issue tracking enabled +- `has_projects` - Project boards enabled +- `has_wiki` - Wiki enabled +- `is_template` - Template repository status +- `allow_merge_commit` - Merge commit allowed +- `allow_squash_merge` - Squash merge allowed +- `allow_rebase_merge` - Rebase merge allowed +- `allow_auto_merge` - Auto-merge enabled +- `delete_branch_on_merge` - Branch deletion on merge + +Additional Info: +- `default_branch` - Default branch name +- `archived` - Archive status +- `homepage_url` - Homepage URL if set +- `vulnerability_alerts` - Vulnerability alerts status +- `template` - Template repository details if used +- `gitignore_template` - .gitignore template if used +- `license_template` - License template if used + +## Limitations and Important Notes + +### Managing Existing Repositories +When managing existing repositories (`create_repo = false`): +- The repository must already exist in the specified organization +- You must have admin access to the repository +- Some settings may be read-only if they were set during repository creation +- Initial repository settings (like `auto_init`) are ignored +- Branch protection rules can only be added, not removed + +### Error Cases +The module will fail if: +- When `create_repo = false` and the repository doesn't exist +- When `create_repo = false` and `repo_org` is not specified +- When trying to manage a repository you don't have admin access to +- When applying branch protection rules to a private repository without a GitHub Enterprise plan + +### Best Practices +1. When managing existing repositories: + - Start with `create_repo = false` and minimal settings + - Gradually add configuration to avoid conflicts + - Use `terraform plan` to verify changes + - Consider using `lifecycle` blocks to ignore specific attributes + +2. For new repositories: + - Use `create_repo = true` (default) + - Set `force_name = true` to maintain consistent naming + - Configure all settings during initial creation + ## Testing -This module includes automated tests using Terraform's built-in testing framework: +This module includes automated tests that verify: +- Repository creation +- Data source lookups for existing repositories +- All output attributes +Run the tests using: ```bash terraform test ``` @@ -126,5 +222,112 @@ MIT License - see [LICENSE](LICENSE) for details [![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 }} +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [github](#provider\_github) | 6.5.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [github_actions_environment_secret.environment_secrets](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret) | resource | +| [github_actions_environment_variable.environment_variables](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_variable) | resource | +| [github_actions_secret.secret](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) | resource | +| [github_actions_variable.variable](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_variable) | resource | +| [github_branch.branch](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch) | resource | +| [github_branch_default.default_main_branch](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_default) | resource | +| [github_branch_protection.main](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_protection) | resource | +| [github_repository.repo](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository) | resource | +| [github_repository_collaborator.collaborators](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_collaborator) | resource | +| [github_repository_environment.environments](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_environment) | resource | +| [github_repository_file.codeowners](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_file) | resource | +| [github_repository_file.extra_files](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_file) | resource | +| [github_repository_file.managed_extra_files](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_file) | resource | +| [github_team_repository.admin](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/team_repository) | resource | +| [github_actions_public_key.repo_key](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/actions_public_key) | data source | +| [github_organization_teams.root_teams](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/organization_teams) | data source | +| [github_ref.ref](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/ref) | data source | +| [github_repository.existing](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/repository) | data source | +| [github_repository.template_repo](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/repository) | data source | +| [github_team.admin_teams](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/team) | data source | +| [github_user.collaborators](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/user) | data source | +| [github_user.pull_request_bypassers](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/user) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_codeowners](#input\_additional\_codeowners) | Additional entries for CODEOWNERS file | `list(string)` | `[]` | no | +| [admin\_teams](#input\_admin\_teams) | Teams to grant admin access | `list(string)` | `[]` | no | +| [archive\_on\_destroy](#input\_archive\_on\_destroy) | Archive repository instead of deleting on destroy | `bool` | `true` | no | +| [archived](#input\_archived) | Archive this repository | `bool` | `false` | no | +| [collaborators](#input\_collaborators) | Map of collaborators and their permission levels | `map(string)` | `{}` | no | +| [create\_codeowners](#input\_create\_codeowners) | Create CODEOWNERS file | `bool` | `true` | no | +| [create\_repo](#input\_create\_repo) | Whether to create a new repository or manage an existing one | `bool` | `true` | no | +| [enforce\_prs](#input\_enforce\_prs) | Enforce pull request reviews | `bool` | `true` | no | +| [environments](#input\_environments) | List of GitHub environments to create for the repository |
list(object({
name = string
reviewers = optional(object({
teams = optional(list(string), [])
users = optional(list(string), [])
}), {})
deployment_branch_policy = optional(object({
protected_branches = optional(bool, true)
custom_branch_policies = optional(bool, false)
}), {})
secrets = optional(list(object({
name = string
value = string
})), [])
vars = optional(list(object({
name = string
value = string
})), [])
}))
| `[]` | no | +| [extra\_files](#input\_extra\_files) | Additional files to create in the repository |
list(object({
path = string
content = string
}))
| `[]` | no | +| [force\_name](#input\_force\_name) | Keep exact repository name (no date suffix) | `bool` | `false` | no | +| [github\_allow\_auto\_merge](#input\_github\_allow\_auto\_merge) | Allow auto-merging pull requests | `bool` | `false` | no | +| [github\_allow\_merge\_commit](#input\_github\_allow\_merge\_commit) | Allow merge commits | `bool` | `false` | no | +| [github\_allow\_rebase\_merge](#input\_github\_allow\_rebase\_merge) | Allow rebase merging | `bool` | `false` | no | +| [github\_allow\_squash\_merge](#input\_github\_allow\_squash\_merge) | Allow squash merging | `bool` | `true` | no | +| [github\_auto\_init](#input\_github\_auto\_init) | Initialize repository with README | `bool` | `true` | no | +| [github\_codeowners\_team](#input\_github\_codeowners\_team) | n/a | `string` | `"terraform-reviewers"` | no | +| [github\_default\_branch](#input\_github\_default\_branch) | Default branch name | `string` | `"main"` | no | +| [github\_delete\_branch\_on\_merge](#input\_github\_delete\_branch\_on\_merge) | Delete head branch after merge | `bool` | `true` | no | +| [github\_dismiss\_stale\_reviews](#input\_github\_dismiss\_stale\_reviews) | Dismiss stale pull request approvals | `bool` | `true` | no | +| [github\_enforce\_admins\_branch\_protection](#input\_github\_enforce\_admins\_branch\_protection) | Enforce branch protection rules on administrators | `bool` | `true` | no | +| [github\_has\_issues](#input\_github\_has\_issues) | Enable issues feature | `bool` | `false` | no | +| [github\_has\_projects](#input\_github\_has\_projects) | Enable projects feature | `bool` | `true` | no | +| [github\_has\_wiki](#input\_github\_has\_wiki) | Enable wiki feature | `bool` | `true` | no | +| [github\_is\_private](#input\_github\_is\_private) | Make repository private | `bool` | `true` | no | +| [github\_org\_teams](#input\_github\_org\_teams) | Organization teams configuration | `list(any)` | `null` | no | +| [github\_push\_restrictions](#input\_github\_push\_restrictions) | List of team/user IDs with push access | `list(string)` | `[]` | no | +| [github\_repo\_description](#input\_github\_repo\_description) | Repository description | `string` | `null` | no | +| [github\_repo\_topics](#input\_github\_repo\_topics) | Repository topics | `list(string)` | `[]` | no | +| [github\_require\_code\_owner\_reviews](#input\_github\_require\_code\_owner\_reviews) | Require code owner review | `bool` | `true` | no | +| [github\_required\_approving\_review\_count](#input\_github\_required\_approving\_review\_count) | Number of approvals needed for pull requests | `number` | `1` | no | +| [gitignore\_template](#input\_gitignore\_template) | Gitignore template to use | `string` | `null` | no | +| [homepage\_url](#input\_homepage\_url) | Repository homepage URL | `string` | `null` | no | +| [is\_template](#input\_is\_template) | Make this repository a template | `bool` | `false` | no | +| [managed\_extra\_files](#input\_managed\_extra\_files) | Additional files to manage in the repository |
list(object({
path = string
content = string
}))
| `[]` | no | +| [name](#input\_name) | Name of the repository | `string` | n/a | yes | +| [prefix](#input\_prefix) | Prefix to add to repository name | `string` | `null` | no | +| [pull\_request\_bypassers](#input\_pull\_request\_bypassers) | Users/teams that can bypass pull request requirements | `list(string)` | `[]` | no | +| [repo\_org](#input\_repo\_org) | GitHub organization name | `string` | `null` | no | +| [required\_status\_checks](#input\_required\_status\_checks) | Required status checks for protected branches |
object({
contexts = list(string)
strict = optional(bool, false)
})
| `null` | no | +| [secrets](#input\_secrets) | GitHub Actions secrets |
list(object({
name = string
value = string
}))
| `[]` | no | +| [security\_and\_analysis](#input\_security\_and\_analysis) | Security and analysis settings for the repository |
object({
advanced_security = optional(object({
status = string
}), { status = "disabled" })
secret_scanning = optional(object({
status = string
}), { status = "disabled" })
secret_scanning_push_protection = optional(object({
status = string
}), { status = "disabled" })
})
| `null` | no | +| [template\_repo](#input\_template\_repo) | Template repository name | `string` | `null` | no | +| [template\_repo\_org](#input\_template\_repo\_org) | Template repository organization | `string` | `null` | no | +| [vars](#input\_vars) | GitHub Actions variables |
list(object({
name = string
value = string
}))
| `[]` | no | +| [vulnerability\_alerts](#input\_vulnerability\_alerts) | Enable Dependabot alerts | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [default\_branch](#output\_default\_branch) | Default branch of the repository | +| [full\_name](#output\_full\_name) | Full name of the repository in org/repo format | +| [git\_clone\_url](#output\_git\_clone\_url) | URL that can be provided to git clone to clone the repository anonymously via the git protocol | +| [github\_repo](#output\_github\_repo) | All attributes of the GitHub repository | +| [html\_url](#output\_html\_url) | URL to the repository on GitHub | +| [http\_clone\_url](#output\_http\_clone\_url) | URL that can be provided to git clone to clone the repository via HTTPS | +| [node\_id](#output\_node\_id) | Node ID of the repository, used for GraphQL API access | +| [repo\_id](#output\_repo\_id) | Repository ID | +| [ssh\_clone\_url](#output\_ssh\_clone\_url) | URL that can be provided to git clone to clone the repository via SSH | +| [template](#output\_template) | Template repository this repository was created from | +| [topics](#output\_topics) | List of topics applied to the repository | +| [visibility](#output\_visibility) | Whether the repository is private or public | diff --git a/action_secrets.tf b/action_secrets.tf index 0470449..b96586f 100644 --- a/action_secrets.tf +++ b/action_secrets.tf @@ -1,13 +1,24 @@ +locals { + repo_exists = var.create_repo ? github_repository.repo[0] : data.github_repository.existing[0] +} + +# data "github_actions_public_key" "repo_key" { +# repository = local.github_repo.name +# count = local.repo_exists != null ? 1 : 0 +# } + resource "github_actions_secret" "secret" { for_each = tomap({ for secret in var.secrets : secret.name => secret.value }) + repository = local.github_repo.name secret_name = each.key - plaintext_value = each.value - repository = github_repository.repo.name + encrypted_value = base64encode(each.value) + depends_on = [local.repo_exists] } resource "github_actions_variable" "variable" { for_each = tomap({ for _var in var.vars : _var.name => _var.value }) - repository = github_repository.repo.name + repository = local.github_repo.name variable_name = each.key value = each.value + depends_on = [local.repo_exists] } diff --git a/collaborators.tf b/collaborators.tf index 5ffe416..ae4a1f2 100644 --- a/collaborators.tf +++ b/collaborators.tf @@ -1,10 +1,27 @@ +locals { + # Permission mapping for collaborator roles + permission_map = { + "pull" = "read" + "triage" = "triage" + "push" = "write" + "maintain" = "maintain" + "admin" = "admin" + } +} + +data "github_user" "collaborators" { + for_each = var.collaborators + username = each.key +} + # Add a collaborator to a repository resource "github_repository_collaborator" "collaborators" { for_each = tomap(var.collaborators) - repository = github_repository.repo.name + repository = local.github_repo.name username = each.key - permission = each.value + permission = local.permission_map[each.value] + depends_on = [ - github_repository.repo + data.github_user.collaborators ] } diff --git a/environment.tf b/environment.tf new file mode 100644 index 0000000..2a43c81 --- /dev/null +++ b/environment.tf @@ -0,0 +1,56 @@ +resource "github_repository_environment" "environments" { + for_each = { for env in var.environments : env.name => env } + + environment = each.value.name + repository = github_repository.repo[0].name + reviewers { + teams = try(each.value.reviewers.teams, []) + users = try(each.value.reviewers.users, []) + } + deployment_branch_policy { + protected_branches = try(each.value.deployment_branch_policy.protected_branches, true) + custom_branch_policies = try(each.value.deployment_branch_policy.custom_branch_policies, false) + } +} + +resource "github_actions_environment_secret" "environment_secrets" { + for_each = { + for pair in flatten([ + for env in var.environments : [ + for secret in coalesce(env.secrets, []) : { + env_name = env.name + name = secret.name + value = secret.value + } + ] + ]) : "${pair.env_name}.${pair.name}" => pair + } + + repository = github_repository.repo[0].name + environment = each.value.env_name + secret_name = each.value.name + plaintext_value = each.value.value + + depends_on = [github_repository_environment.environments] +} + +resource "github_actions_environment_variable" "environment_variables" { + for_each = { + for pair in flatten([ + for env in var.environments : [ + for _var in coalesce(env.vars, []) : { + env_name = env.name + name = _var.name + value = _var.value + } + ] + ]) : "${pair.env_name}.${pair.name}" => pair + } + + repository = github_repository.repo[0].name + environment = each.value.env_name + variable_name = each.value.name + value = each.value.value + + depends_on = [github_repository_environment.environments] +} \ No newline at end of file diff --git a/github_branch.tf b/github_branch.tf index cce7ccd..c188c8b 100644 --- a/github_branch.tf +++ b/github_branch.tf @@ -1,4 +1,3 @@ - # https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/team # data "github_team" "github_codeowners_team" { # slug = var.github_codeowners_team @@ -7,7 +6,7 @@ # 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 + repository = local.github_repo.name branch = var.github_default_branch } @@ -15,7 +14,7 @@ resource "github_branch" "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 + repository = local.github_repo.name branch = var.github_default_branch depends_on = [ github_branch.branch @@ -34,35 +33,40 @@ locals { # https://registry.terraform.io/providers/integrations/github/latest/docs/resources/branch_protection resource "github_branch_protection" "main" { - 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 - 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 - ] - } + count = (var.enforce_prs && !var.github_is_private) || var.github_is_private ? 1 : 0 + + repository_id = local.github_repo.node_id + pattern = var.github_default_branch + enforce_admins = var.github_enforce_admins_branch_protection + allows_deletions = false + allows_force_pushes = false + require_signed_commits = true + required_linear_history = true + require_conversation_resolution = true + lock_branch = false dynamic "required_status_checks" { - for_each = var.required_status_checks == null ? [] : ["*"] + for_each = var.required_status_checks != null ? ["true"] : [] content { - contexts = required_status_checks.value.contexts - strict = required_status_checks.value.strict + strict = try(var.required_status_checks.strict, false) + contexts = try(var.required_status_checks.contexts, []) } } - 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 - ] + dynamic "required_pull_request_reviews" { + for_each = var.enforce_prs ? ["true"] : [] + content { + dismiss_stale_reviews = var.github_dismiss_stale_reviews + restrict_dismissals = true + require_code_owner_reviews = var.github_require_code_owner_reviews + required_approving_review_count = var.github_required_approving_review_count + require_last_push_approval = true + } + } + + lifecycle { + ignore_changes = [ + required_status_checks[0].contexts + ] + } } diff --git a/github_files.tf b/github_files.tf index a0335c1..38c3e10 100644 --- a/github_files.tf +++ b/github_files.tf @@ -1,10 +1,13 @@ # https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_file resource "github_repository_file" "codeowners" { count = var.create_codeowners ? 1 : 0 - repository = github_repository.repo.name + repository = local.github_repo.name branch = var.github_default_branch file = "CODEOWNERS" content = templatefile("${path.module}/templates/CODEOWNERS", { codeowners = local.codeowners }) + commit_message = "Update CODEOWNERS file" + commit_author = "Terraform" + commit_email = "terraform@example.com" overwrite_on_create = true lifecycle { ignore_changes = [ @@ -41,10 +44,13 @@ locals { resource "github_repository_file" "extra_files" { for_each = tomap({ for file in local.extra_files : "${element(split("/", file.path), length(split("/", file.path)) - 1)}" => file }) - repository = github_repository.repo.name + repository = local.github_repo.name branch = var.github_default_branch file = each.value.path content = each.value.content + commit_message = "Update ${each.value.path}" + commit_author = "Terraform" + commit_email = "terraform@example.com" overwrite_on_create = true lifecycle { ignore_changes = [ @@ -56,10 +62,13 @@ 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 + repository = local.github_repo.name branch = var.github_default_branch file = each.value.path content = each.value.content + commit_message = "Update ${each.value.path}" + commit_author = "Terraform" + commit_email = "terraform@example.com" overwrite_on_create = true lifecycle { ignore_changes = [ diff --git a/github_repo.tf b/github_repo.tf index e642209..d0144b1 100644 --- a/github_repo.tf +++ b/github_repo.tf @@ -1,35 +1,98 @@ locals { repo_name = var.force_name ? var.name : "${var.name}-${formatdate("YYYYMMDD", timestamp())}" -} + github_repo = var.create_repo ? github_repository.repo[0] : data.github_repository.existing[0] + + validate_merge_options = ( + var.github_allow_merge_commit || + var.github_allow_squash_merge || + var.github_allow_rebase_merge + ) ? null : file("ERROR: At least one merge option must be enabled") +} 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 + count = var.create_repo ? 1 : 0 + name = local.repo_name + description = var.github_repo_description + visibility = var.github_is_private ? "private" : "public" + has_issues = var.github_has_issues + has_projects = var.github_has_projects + has_wiki = var.github_has_wiki + has_downloads = var.github_has_downloads + auto_init = var.github_auto_init + archive_on_destroy = var.archive_on_destroy + archived = var.archived + vulnerability_alerts = var.vulnerability_alerts + topics = var.github_repo_topics + homepage_url = var.homepage_url + gitignore_template = var.gitignore_template + license_template = var.license_template + is_template = var.is_template + has_discussions = try(var.github_has_discussions, false) + merge_commit_title = try(var.github_merge_commit_title, "MERGE_MESSAGE") + merge_commit_message = try(var.github_merge_commit_message, "PR_TITLE") + squash_merge_commit_title = try(var.github_squash_merge_commit_title, "COMMIT_OR_PR_TITLE") + squash_merge_commit_message = try(var.github_squash_merge_commit_message, "COMMIT_MESSAGES") + allow_update_branch = try(var.github_allow_update_branch, true) + 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 = var.archive_on_destroy + allow_auto_merge = var.github_allow_auto_merge 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 = var.gitignore_template - is_template = var.is_template - archived = var.archived - homepage_url = var.homepage_url - vulnerability_alerts = var.vulnerability_alerts 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 } } + + dynamic "security_and_analysis" { + for_each = var.security_and_analysis == null ? [] : ["*"] + content { + dynamic "advanced_security" { + for_each = try(var.security_and_analysis.advanced_security, null) == null ? [] : ["*"] + content { + status = var.security_and_analysis.advanced_security.status + } + } + dynamic "secret_scanning" { + for_each = try(var.security_and_analysis.secret_scanning, null) == null ? [] : ["*"] + content { + status = var.security_and_analysis.secret_scanning.status + } + } + dynamic "secret_scanning_push_protection" { + for_each = try(var.security_and_analysis.secret_scanning_push_protection, null) == null ? [] : ["*"] + content { + status = var.security_and_analysis.secret_scanning_push_protection.status + } + } + } + } + + dynamic "pages" { + for_each = var.pages_config == null ? [] : ["true"] + content { + source { + branch = try(var.pages_config.branch, "gh-pages") + path = try(var.pages_config.path, "/") + } + cname = try(var.pages_config.cname, null) + } + } + + lifecycle { + ignore_changes = [ + auto_init, + template + ] + } +} + +data "github_repository" "existing" { + count = var.create_repo ? 0 : 1 + name = var.name } diff --git a/github_repo.tftest.hcl b/github_repo.tftest.hcl index 25ccacb..5f4df56 100644 --- a/github_repo.tftest.hcl +++ b/github_repo.tftest.hcl @@ -1,21 +1,156 @@ # 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 = [] + name = "github-repo-test" + repo_org = "HappyPathway" + force_name = true + github_is_private = false + enforce_prs = false + archive_on_destroy = false + github_org_teams = [] + admin_teams = ["test-team"] + github_repo_description = "Test repository" + github_repo_topics = ["test", "terraform"] + create_repo = true + secrets = [ + { + name = "TEST_SECRET" + value = "secret-value" + } + ] + vars = [ + { + name = "TEST_VAR" + value = "test-value" + } + ] + extra_files = [ + { + path = "test.md" + content = "Test content" + } + ] } -run "repo_tests" { +# Test repository creation first +run "create_new_repository" { + command = apply +} +# Then test repository data source +run "verify_data_source" { + variables { + create_repo = false + } command = plan + assert { + condition = data.github_repository.existing[0].name == var.name + error_message = "Data source repository name does not match input" + } +} +# Now test other components that depend on the repository existing +run "repo_tests" { + command = plan assert { - condition = github_repository.repo.name == "github-repo-test" + condition = github_repository.repo[0].name == "github-repo-test" error_message = "Github Repo name did not match expected" } } + +run "verify_branch_protection" { + variables { + github_default_branch = "main" + enforce_prs = true + github_is_private = false + github_required_approving_review_count = 2 + } + command = plan + assert { + condition = github_branch_protection.main[0].pattern == "main" + error_message = "Branch protection pattern should be main" + } + assert { + condition = github_branch_protection.main[0].required_pull_request_reviews[0].required_approving_review_count == 2 + error_message = "Should require 2 review approvals" + } +} + +run "verify_repository_files" { + command = plan + assert { + condition = github_repository_file.extra_files["test.md"].file == "test.md" + error_message = "Extra file should be created" + } + assert { + condition = github_repository_file.extra_files["test.md"].content == "Test content" + error_message = "Extra file content should match input" + } +} + +run "verify_team_access" { + command = plan + assert { + condition = github_team_repository.admin["test-team"].permission == "admin" + error_message = "Team should have admin access" + } +} + +run "verify_action_secrets" { + command = plan + assert { + condition = github_actions_secret.secret["TEST_SECRET"].secret_name == "TEST_SECRET" + error_message = "Action secret should be created" + } + assert { + condition = github_actions_variable.variable["TEST_VAR"].variable_name == "TEST_VAR" + error_message = "Action variable should be created" + } +} + +run "verify_outputs" { + command = plan + assert { + condition = output.github_repo.name == var.name + error_message = "Output repository name does not match input" + } + assert { + condition = output.ssh_clone_url != "" + error_message = "SSH clone URL should not be empty" + } + assert { + condition = output.node_id != "" + error_message = "Node ID should not be empty" + } + assert { + condition = output.full_name != "" + error_message = "Full name should not be empty" + } + assert { + condition = output.repo_id != null + error_message = "Repository ID should not be null" + } + assert { + condition = output.html_url != "" + error_message = "HTML URL should not be empty" + } + assert { + condition = output.http_clone_url != "" + error_message = "HTTP clone URL should not be empty" + } + assert { + condition = output.git_clone_url != "" + error_message = "Git clone URL should not be empty" + } + assert { + condition = output.visibility == "public" + error_message = "Visibility should be public" + } + assert { + condition = output.default_branch == "main" + error_message = "Default branch should be 'main'" + } + assert { + condition = length(output.topics) == 2 + error_message = "Should have exactly 2 topics" + } +} diff --git a/github_team_access.tf b/github_team_access.tf index c530e6a..21e14bc 100644 --- a/github_team_access.tf +++ b/github_team_access.tf @@ -1,30 +1,35 @@ # 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 + count = var.github_org_teams == null && var.repo_org != null ? 1 : 0 root_teams_only = false } +data "github_team" "admin_teams" { + for_each = toset(var.admin_teams) + slug = each.value +} + locals { - github_org_teams = var.github_org_teams == null ? data.github_organization_teams.root_teams[0].teams : var.github_org_teams + github_org_teams = var.github_org_teams == null ? try(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 } + team_repository_permissions = { + "pull" = "read" + "triage" = "triage" + "push" = "write" + "maintain" = "maintain" + "admin" = "admin" + } } -# 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 + for_each = { for team in var.admin_teams : team => data.github_team.admin_teams[team].id } + team_id = each.value + repository = local.github_repo.name permission = "admin" + lifecycle { ignore_changes = [ team_id ] } - depends_on = [ - github_repository.repo - ] } diff --git a/outputs.tf b/outputs.tf index 1d937cf..5afc19d 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,8 +1,59 @@ output "github_repo" { - value = github_repository.repo + description = "All attributes of the GitHub repository" + value = local.github_repo } output "ssh_clone_url" { description = "URL that can be provided to git clone to clone the repository via SSH" - value = github_repository.repo.ssh_clone_url + value = local.github_repo.ssh_clone_url +} + +output "node_id" { + description = "Node ID of the repository, used for GraphQL API access" + value = local.github_repo.node_id +} + +output "full_name" { + description = "Full name of the repository in org/repo format" + value = local.github_repo.full_name +} + +output "repo_id" { + description = "Repository ID" + value = local.github_repo.repo_id +} + +output "html_url" { + description = "URL to the repository on GitHub" + value = local.github_repo.html_url +} + +output "http_clone_url" { + description = "URL that can be provided to git clone to clone the repository via HTTPS" + value = local.github_repo.http_clone_url +} + +output "git_clone_url" { + description = "URL that can be provided to git clone to clone the repository anonymously via the git protocol" + value = local.github_repo.git_clone_url +} + +output "visibility" { + description = "Whether the repository is private or public" + value = local.github_repo.visibility +} + +output "default_branch" { + description = "Default branch of the repository" + value = local.github_repo.default_branch +} + +output "topics" { + description = "List of topics applied to the repository" + value = local.github_repo.topics +} + +output "template" { + description = "Template repository this repository was created from" + value = local.github_repo.template } diff --git a/variables.tf b/variables.tf index 717a775..754647a 100644 --- a/variables.tf +++ b/variables.tf @@ -1,9 +1,12 @@ variable "name" { - description = "Name of the terraform workspace and optionally github repo" + description = "Name of the repository" + type = string } variable "repo_org" { - default = null + description = "GitHub organization name" + type = string + default = null } variable "github_codeowners_team" { @@ -11,120 +14,157 @@ variable "github_codeowners_team" { } variable "github_repo_description" { - default = null + description = "Repository description" + type = string + default = null } variable "github_repo_topics" { - description = "Github Repo Topics" - type = list(any) + description = "Repository topics" + type = list(string) default = [] } variable "github_push_restrictions" { - description = "Github Push Restrictions" - type = list(any) + description = "List of team/user IDs with push access" + type = list(string) default = [] } variable "github_is_private" { - default = true + description = "Make repository private" + type = bool + default = true } variable "github_auto_init" { - default = true + description = "Initialize repository with README" + type = bool + default = true } variable "github_allow_merge_commit" { - default = false + description = "Allow merge commits" + type = bool + default = false } variable "github_allow_squash_merge" { - default = true + description = "Allow squash merging" + type = bool + default = true } variable "github_allow_rebase_merge" { - default = false + description = "Allow rebase merging" + type = bool + default = false } variable "github_delete_branch_on_merge" { - default = true + description = "Delete head branch after merge" + type = bool + default = true } variable "github_has_projects" { - default = true + description = "Enable projects feature" + type = bool + default = true } variable "github_has_issues" { - default = true + description = "Enable issues feature" + type = bool + default = false } variable "github_has_wiki" { - default = true + description = "Enable wiki feature" + type = bool + default = true } variable "github_default_branch" { - default = "main" + description = "Default branch name" + type = string + default = "main" } variable "github_required_approving_review_count" { - default = 1 + description = "Number of approvals needed for pull requests" + type = number + default = 1 } variable "github_require_code_owner_reviews" { - default = true + description = "Require code owner review" + type = bool + default = true } variable "github_dismiss_stale_reviews" { - default = true + description = "Dismiss stale pull request approvals" + type = bool + default = true } variable "github_enforce_admins_branch_protection" { - default = true + description = "Enforce branch protection rules on administrators" + type = bool + default = true +} + +variable "github_allow_auto_merge" { + description = "Allow pull requests to be automatically merged" + type = bool + default = false +} + +variable "github_has_downloads" { + description = "Enable downloads feature" + type = bool + default = false } variable "additional_codeowners" { - description = "Enable adding of Codeowner Teams" - type = list(any) + description = "Additional entries for CODEOWNERS file" + type = list(string) default = [] } variable "prefix" { - default = null + description = "Prefix to add to repository name" + type = string + default = null } variable "force_name" { - description = "Force Naming of Repo. If forced, archive management will not operate on this repo" + description = "Keep exact repository name (no date suffix)" + type = bool default = false } variable "github_org_teams" { + description = "Organization teams configuration" 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 + description = "Template repository organization" + type = string + default = null } variable "template_repo" { - default = null + description = "Template repository name" + type = string + default = null } variable "is_template" { - default = false + description = "Make this repository a template" + type = bool + default = false } variable "admin_teams" { - description = "Admin Teams" - type = list(any) + description = "Teams to grant admin access" + type = list(string) default = [] } variable "required_status_checks" { - 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 + description = "Required status checks for protected branches" type = object({ contexts = list(string) strict = optional(bool, false) @@ -133,96 +173,116 @@ EOT } variable "archived" { - default = false + description = "Archive this repository" + type = bool + default = false } variable "secrets" { + description = "GitHub Actions secrets" type = list(object({ - name = string, + name = string value = string })) - default = [] - description = "Github Action Secrets" + default = [] + validation { + condition = alltrue([for s in var.secrets : can(regex("^[A-Z0-9_]+$", s.name))]) + error_message = "Secret names must contain only uppercase letters, numbers, and underscores." + } } variable "vars" { + description = "GitHub Actions variables" type = list(object({ - name = string, + name = string value = string })) - default = [] - description = "Github Action Vars" + default = [] + validation { + condition = alltrue([for v in var.vars : can(regex("^[A-Z0-9_]+$", v.name))]) + error_message = "Variable names must contain only uppercase letters, numbers, and underscores." + } } variable "extra_files" { + description = "Additional files to create in the repository" type = list(object({ - path = string, + path = string content = string })) - default = [] - description = "Extra Files" + default = [] } variable "managed_extra_files" { + description = "Additional files to manage in the repository" type = list(object({ - path = string, + path = string content = string })) - default = [] - description = "Managed Extra Files. Changes to Content will be updated" + default = [] } variable "pull_request_bypassers" { - default = [] - type = list(any) + description = "Users/teams that can bypass pull request requirements" + type = list(string) + default = [] } variable "create_codeowners" { - default = true - type = bool + description = "Create CODEOWNERS file" + type = bool + default = true } variable "enforce_prs" { - default = true - type = bool + description = "Enforce pull request reviews" + type = bool + default = true } variable "collaborators" { + description = "Map of collaborators and their permission levels" type = map(string) - description = "list of repo callaborators" default = {} + validation { + condition = alltrue([for perm in values(var.collaborators) : contains(["pull", "triage", "push", "maintain", "admin"], perm)]) + error_message = "Valid permissions are: pull, triage, push, maintain, admin" + } } variable "archive_on_destroy" { - type = bool - default = true + description = "Archive repository instead of deleting on destroy" + type = bool + default = true } variable "vulnerability_alerts" { - type = bool - default = false + description = "Enable Dependabot alerts" + type = bool + default = false } variable "gitignore_template" { - default = null + description = "Gitignore template to use" + type = string + default = null } variable "homepage_url" { - default = null + description = "Repository homepage URL" + type = string + default = null } -variable "security_and_analysis" { - description = <