From 1c67419fc2baf2b83c7c025fc912be19a66e56ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:20:01 +0000 Subject: [PATCH 1/8] Initial plan From 82dd60530fb1f0e5f576a176858d5edeca8c5921 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:25:17 +0000 Subject: [PATCH 2/8] Add comprehensive testing infrastructure for organizations folder Co-authored-by: bzarboni1 <99673202+bzarboni1@users.noreply.github.com> --- organizations/README.md | 51 ++++ organizations/TESTING.md | 273 ++++++++++++++++++ organizations/main.tftest.hcl | 272 +++++++++++++++++ .../organizations/example-org/terragrunt.hcl | 50 ++++ .../example-org/repositories/terragrunt.hcl | 109 +++++++ .../example-org/teams/terragrunt.hcl | 79 +++++ .../providers/example-org/providers.hcl | 36 +++ 7 files changed, 870 insertions(+) create mode 100644 organizations/TESTING.md create mode 100644 organizations/main.tftest.hcl create mode 100644 organizations/organizations/example-org/terragrunt.hcl create mode 100644 organizations/projects/example-project/example-org/repositories/terragrunt.hcl create mode 100644 organizations/projects/example-project/example-org/teams/terragrunt.hcl create mode 100644 organizations/providers/example-org/providers.hcl diff --git a/organizations/README.md b/organizations/README.md index 408515c..4499bbe 100644 --- a/organizations/README.md +++ b/organizations/README.md @@ -10,6 +10,7 @@ * [Configuring Repositories](#configuring-repositories) * [Configuring Teams](#configuring-teams) * [Secret Management](#secret-management) + * [Testing](#testing) * [Running the Organizations Layer locally](#running-the-organizations-layer-locally) * [Prerequisites](#prerequisites) * [Pre-installed tools](#pre-installed-tools) @@ -137,6 +138,56 @@ See the documentation [here](./TEAMS_REPOS.md#configuring-teams) See the documentation [here](./SECRETS.md) +## Testing + +The organizations layer includes comprehensive testing capabilities to validate your Terraform and Terragrunt configurations before committing changes. + +### Quick Start + +Run tests with: +```bash +cd organizations/ +terraform init +terraform test -verbose +``` + +Check formatting: +```bash +terraform fmt -check -recursive +terragrunt hclfmt --terragrunt-check --terragrunt-diff +``` + +### Pre-commit Hooks + +Install pre-commit hooks to automatically validate changes: +```bash +pip install pre-commit +pre-commit install +``` + +The hooks will automatically run format checks, validation, linting, and security scans before each commit. + +### Documentation + +For detailed testing instructions, see the [Testing Guide](./TESTING.md). + +The testing guide covers: +- Running Terraform native tests +- Format checking for Terraform and Terragrunt +- Pre-commit hook setup and usage +- Validation and security scanning +- Example configurations +- Troubleshooting common issues + +### Example Configurations + +The organizations layer includes example configurations to help you get started: +- **Organization settings**: `organizations/example-org/terragrunt.hcl` +- **Project structure**: `projects/example-project/example-org/` +- **Provider configuration**: `providers/example-org/providers.hcl` + +These examples demonstrate best practices and can be used as templates for your own configurations. + ## Running the Organizations Layer locally ### Prerequisites diff --git a/organizations/TESTING.md b/organizations/TESTING.md new file mode 100644 index 0000000..cd90711 --- /dev/null +++ b/organizations/TESTING.md @@ -0,0 +1,273 @@ +# Testing Guide for Organizations Layer + +This guide explains how to test your Terraform and Terragrunt configurations in the organizations layer before committing changes. + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Running Tests](#running-tests) +- [Format Checking](#format-checking) +- [Pre-commit Hooks](#pre-commit-hooks) +- [Validation](#validation) +- [Security Scanning](#security-scanning) +- [Example Configurations](#example-configurations) + +## Prerequisites + +Before running tests, ensure you have the following installed: + +- **Terraform** (v1.7.5 or later): [Installation Guide](https://developer.hashicorp.com/terraform/downloads) +- **Terragrunt** (v0.55.18 or later): [Installation Guide](https://terragrunt.gruntwork.io/docs/getting-started/install/) +- **TFLint** (optional but recommended): [Installation Guide](https://github.com/terraform-linters/tflint) +- **pre-commit** (optional but recommended): [Installation Guide](https://pre-commit.com/#install) + +## Running Tests + +### Terraform Native Tests + +The organizations layer includes a comprehensive test file (`main.tftest.hcl`) that validates: +- Organization settings configuration +- Repository configuration +- Team configuration +- Security settings +- Naming conventions +- Email format validation + +To run the tests: + +```bash +cd organizations/ +terraform init +terraform test -verbose +``` + +The test output will show: +- ✅ Passed tests (all assertions successful) +- ❌ Failed tests (with specific error messages) + +### Example Test Output + +``` +main.tftest.hcl... in progress + run "organization_settings_validation"... pass + run "repository_configuration_validation"... pass + run "team_configuration_validation"... pass + run "security_settings_validation"... pass + run "naming_conventions_validation"... pass + run "email_format_validation"... pass +main.tftest.hcl... tearing down +main.tftest.hcl... pass +``` + +## Format Checking + +### Check Terraform Formatting + +Check if your Terraform files are properly formatted: + +```bash +cd organizations/ +terraform fmt -check -diff -recursive +``` + +To automatically format files: + +```bash +terraform fmt -recursive +``` + +### Check Terragrunt Formatting + +Check Terragrunt HCL files: + +```bash +cd organizations/ +terragrunt hclfmt --terragrunt-check --terragrunt-diff +``` + +To automatically format Terragrunt files: + +```bash +terragrunt hclfmt +``` + +## Pre-commit Hooks + +This repository includes pre-commit hooks that automatically run checks before each commit. + +### Setup Pre-commit Hooks + +1. Install pre-commit: + ```bash + pip install pre-commit + # or + brew install pre-commit + ``` + +2. Install the hooks: + ```bash + cd /home/runner/work/github-foundations/github-foundations + pre-commit install + ``` + +3. (Optional) Run hooks manually: + ```bash + pre-commit run --all-files + ``` + +### Included Hooks + +The pre-commit configuration includes: +- **terraform_fmt**: Checks Terraform formatting +- **terragrunt_fmt**: Checks Terragrunt HCL formatting +- **terraform_tflint**: Runs TFLint on Terraform files +- **terraform_validate**: Validates Terraform syntax +- **terraform_trivy**: Security scanning of Terraform code +- **gitleaks**: Detects secrets in code +- **trailing-whitespace**: Removes trailing whitespace +- **end-of-file-fixer**: Ensures files end with a newline +- **check-added-large-files**: Prevents committing large files +- **detect-private-key**: Detects private keys + +## Validation + +### Validate Terraform Configuration + +Validate that your Terraform configuration is syntactically valid: + +```bash +cd organizations/ +terraform init +terraform validate +``` + +### Validate Terragrunt Configuration + +Validate your Terragrunt configuration: + +```bash +cd organizations/ +terragrunt validate-inputs +``` + +## Security Scanning + +### TFLint + +Run TFLint to check for potential issues: + +```bash +cd organizations/ +tflint --init +tflint --recursive +``` + +### Trivy + +Run Trivy for security scanning: + +```bash +cd organizations/ +trivy config . +``` + +## Example Configurations + +The `organizations/` folder includes example configurations to help you get started: + +### Organization Settings +- **Location**: `organizations/example-org/terragrunt.hcl` +- **Purpose**: Example organization-level settings + +### Project Structure +- **Location**: `projects/example-project/example-org/` +- **Purpose**: Example project with repositories and teams + +#### Repositories +- **Location**: `projects/example-project/example-org/repositories/terragrunt.hcl` +- **Includes**: + - Public repository example + - Private repository example + - Application repository with branch protection + +#### Teams +- **Location**: `projects/example-project/example-org/teams/terragrunt.hcl` +- **Includes**: + - Admin team + - Developer team + - Viewer team + - Security team (secret) + +### Provider Configuration +- **Location**: `providers/example-org/providers.hcl` +- **Purpose**: GitHub provider setup with GCP Secret Manager integration + +## Testing Workflow + +Follow this workflow before committing changes: + +1. **Make your changes** to Terraform/Terragrunt files + +2. **Format your code**: + ```bash + terraform fmt -recursive + terragrunt hclfmt + ``` + +3. **Validate syntax**: + ```bash + terraform validate + ``` + +4. **Run tests**: + ```bash + terraform test -verbose + ``` + +5. **Run security scans**: + ```bash + tflint --recursive + trivy config . + ``` + +6. **Commit your changes**: + ```bash + git add . + git commit -m "feat: your commit message" + ``` + + Pre-commit hooks will automatically run and validate your changes. + +## Continuous Integration + +When you push your changes or create a pull request, GitHub Actions will automatically: +- Check Terraform formatting +- Check Terragrunt formatting +- Run Terraform plan +- Validate configurations +- Scan for security issues + +The CI pipeline uses the same tools and configurations as local development, ensuring consistency. + +## Troubleshooting + +### Common Issues + +**Issue**: `terraform test` fails with module not found +- **Solution**: Run `terraform init` first to download required modules + +**Issue**: Pre-commit hooks fail +- **Solution**: Run `pre-commit run --all-files` to see detailed errors + +**Issue**: Format check fails +- **Solution**: Run `terraform fmt -recursive` and `terragrunt hclfmt` to auto-format + +**Issue**: TFLint fails +- **Solution**: Run `tflint --init` to download required plugins + +## Additional Resources + +- [Terraform Testing Documentation](https://developer.hashicorp.com/terraform/language/tests) +- [Terragrunt Documentation](https://terragrunt.gruntwork.io/docs/) +- [TFLint Rules](https://github.com/terraform-linters/tflint/tree/master/docs/rules) +- [Pre-commit Terraform Hooks](https://github.com/antonbabenko/pre-commit-terraform) diff --git a/organizations/main.tftest.hcl b/organizations/main.tftest.hcl new file mode 100644 index 0000000..e75d045 --- /dev/null +++ b/organizations/main.tftest.hcl @@ -0,0 +1,272 @@ +# Terraform test file for organizations layer +# This file provides comprehensive testing for the organizations folder structure +# Run with: terraform test -verbose + +# Mock all external providers to avoid requiring actual credentials +mock_provider "github" {} +mock_provider "google" {} +mock_provider "google-beta" {} + +# Override external modules to provide test data +# These override the external modules from github-foundations-modules +override_module { + target = module.organization_settings + outputs = { + organization_id = "123456" + organization_name = "test-org" + } +} + +override_module { + target = module.repository_set + outputs = { + repository_ids = { + "test-repo-1" = "repo-123" + "test-repo-2" = "repo-456" + } + } +} + +override_module { + target = module.team_set + outputs = { + team_ids = { + "test-team-1" = "team-123" + "test-team-2" = "team-456" + } + } +} + +# Test variables for organization settings +variables { + test_organization_name = "test-organization" + test_project_name = "test-project" + + # Example organization settings + organization_settings = { + name = "test-org" + billing_email = "billing@example.com" + company = "Test Company" + email = "contact@example.com" + location = "Test Location" + description = "Test organization for validation" + has_organization_projects = true + has_repository_projects = true + default_repository_permission = "read" + members_can_create_repositories = false + members_can_create_public_repositories = false + members_can_create_private_repositories = false + members_can_create_pages = false + members_can_fork_private_repositories = false + web_commit_signoff_required = true + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true + } + + # Example public repository configuration + public_repositories = { + "test-public-repo" = { + description = "Test public repository" + default_branch = "main" + repository_team_permissions_override = {} + advance_security = false + has_vulnerability_alerts = true + topics = ["test", "public"] + homepage = "https://example.com" + delete_head_on_merge = true + allow_auto_merge = true + dependabot_security_updates = true + } + } + + # Example private repository configuration + private_repositories = { + "test-private-repo" = { + description = "Test private repository" + default_branch = "main" + repository_team_permissions_override = {} + protected_branches = [] + advance_security = false + has_vulnerability_alerts = true + topics = ["test", "private"] + homepage = "" + delete_head_on_merge = true + allow_auto_merge = true + dependabot_security_updates = true + } + } + + # Example team configuration + teams = { + "test-team-admins" = { + name = "Test Admins" + description = "Administrative team for testing" + privacy = "closed" + members = ["admin1", "admin2"] + maintainers = ["admin1"] + } + "test-team-developers" = { + name = "Test Developers" + description = "Developer team for testing" + privacy = "closed" + members = ["dev1", "dev2", "dev3"] + maintainers = ["dev1"] + } + } +} + +# Test 1: Validate organization settings structure +run "organization_settings_validation" { + command = plan + + assert { + condition = var.organization_settings.name == "test-org" + error_message = "Organization name should be 'test-org'" + } + + assert { + condition = var.organization_settings.web_commit_signoff_required == true + error_message = "Web commit signoff should be required for security" + } + + assert { + condition = var.organization_settings.members_can_create_public_repositories == false + error_message = "Members should not be able to create public repositories by default" + } + + assert { + condition = var.organization_settings.advanced_security_enabled_for_new_repositories == true + error_message = "Advanced security should be enabled for new repositories" + } + + assert { + condition = var.organization_settings.secret_scanning_enabled_for_new_repositories == true + error_message = "Secret scanning should be enabled for new repositories" + } + + assert { + condition = var.organization_settings.secret_scanning_push_protection_enabled_for_new_repositories == true + error_message = "Secret scanning push protection should be enabled for new repositories" + } +} + +# Test 2: Validate repository configuration +run "repository_configuration_validation" { + command = plan + + assert { + condition = var.public_repositories["test-public-repo"].default_branch == "main" + error_message = "Default branch should be 'main'" + } + + assert { + condition = var.public_repositories["test-public-repo"].delete_head_on_merge == true + error_message = "Delete head on merge should be enabled for clean repository management" + } + + assert { + condition = var.public_repositories["test-public-repo"].has_vulnerability_alerts == true + error_message = "Vulnerability alerts should be enabled" + } + + assert { + condition = var.private_repositories["test-private-repo"].default_branch == "main" + error_message = "Default branch should be 'main'" + } + + assert { + condition = var.private_repositories["test-private-repo"].dependabot_security_updates == true + error_message = "Dependabot security updates should be enabled" + } +} + +# Test 3: Validate team configuration +run "team_configuration_validation" { + command = plan + + assert { + condition = length(var.teams) == 2 + error_message = "Should have 2 test teams configured" + } + + assert { + condition = var.teams["test-team-admins"].privacy == "closed" + error_message = "Team privacy should be 'closed' for security" + } + + assert { + condition = length(var.teams["test-team-admins"].members) == 2 + error_message = "Admin team should have 2 members" + } + + assert { + condition = length(var.teams["test-team-developers"].members) == 3 + error_message = "Developer team should have 3 members" + } + + assert { + condition = contains(var.teams["test-team-admins"].maintainers, "admin1") + error_message = "admin1 should be a maintainer of the admin team" + } +} + +# Test 4: Validate security settings +run "security_settings_validation" { + command = plan + + assert { + condition = alltrue([ + var.organization_settings.dependabot_alerts_enabled_for_new_repositories, + var.organization_settings.dependabot_security_updates_enabled_for_new_repositories, + var.organization_settings.dependency_graph_enabled_for_new_repositories + ]) + error_message = "All Dependabot security features should be enabled" + } + + assert { + condition = alltrue([ + var.organization_settings.secret_scanning_enabled_for_new_repositories, + var.organization_settings.secret_scanning_push_protection_enabled_for_new_repositories + ]) + error_message = "Secret scanning and push protection should both be enabled" + } + + assert { + condition = !var.organization_settings.members_can_fork_private_repositories + error_message = "Members should not be able to fork private repositories by default for security" + } +} + +# Test 5: Validate naming conventions +run "naming_conventions_validation" { + command = plan + + assert { + condition = can(regex("^[a-z0-9-]+$", var.test_organization_name)) + error_message = "Organization name should only contain lowercase letters, numbers, and hyphens" + } + + assert { + condition = can(regex("^[a-z0-9-]+$", var.test_project_name)) + error_message = "Project name should only contain lowercase letters, numbers, and hyphens" + } +} + +# Test 6: Validate email format in organization settings +run "email_format_validation" { + command = plan + + assert { + condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.organization_settings.billing_email)) + error_message = "Billing email should be in valid email format" + } + + assert { + condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.organization_settings.email)) + error_message = "Contact email should be in valid email format" + } +} diff --git a/organizations/organizations/example-org/terragrunt.hcl b/organizations/organizations/example-org/terragrunt.hcl new file mode 100644 index 0000000..b1e44e2 --- /dev/null +++ b/organizations/organizations/example-org/terragrunt.hcl @@ -0,0 +1,50 @@ +# Example organization settings configuration +# This file demonstrates how to configure organization settings using the organization_settings module +# Copy this file and modify for your own organization + +include "root" { + path = find_in_parent_folders() + expose = true +} + +include "providers" { + path = "${get_repo_root()}/organizations/providers/${basename(get_terragrunt_dir())}/providers.hcl" + expose = true +} + +terraform { + source = "github.com/FociSolutions/github-foundations-modules//modules/organization_settings" +} + +inputs = { + # Organization basic information + name = "example-org" + billing_email = "billing@example.com" + company = "Example Company" + email = "contact@example.com" + location = "Example Location" + description = "Example organization managed by GitHub Foundations" + + # Project settings + has_organization_projects = true + has_repository_projects = true + + # Repository creation permissions + default_repository_permission = "read" + members_can_create_repositories = false + members_can_create_public_repositories = false + members_can_create_private_repositories = false + members_can_create_pages = false + members_can_fork_private_repositories = false + + # Commit signoff requirement + web_commit_signoff_required = true + + # Security settings for new repositories + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true +} diff --git a/organizations/projects/example-project/example-org/repositories/terragrunt.hcl b/organizations/projects/example-project/example-org/repositories/terragrunt.hcl new file mode 100644 index 0000000..03ef192 --- /dev/null +++ b/organizations/projects/example-project/example-org/repositories/terragrunt.hcl @@ -0,0 +1,109 @@ +# Example repository configuration +# This file demonstrates how to configure repositories for a project using the repository_set module + +include "root" { + path = find_in_parent_folders() + expose = true +} + +include "providers" { + path = "${get_repo_root()}/organizations/providers/${basename(dirname(dirname(get_terragrunt_dir())))}/providers.hcl" + expose = true +} + +terraform { + source = "github.com/FociSolutions/github-foundations-modules//modules/repository_set" +} + +dependency "teams" { + config_path = "../teams" +} + +inputs = { + # Public repositories configuration + public_repositories = { + "example-public-repo" = { + description = "Example public repository" + default_branch = "main" + repository_team_permissions_override = { + # Reference teams from dependency + # "team-slug" = "permission-level" (pull, push, admin, maintain, triage) + } + advance_security = false + has_vulnerability_alerts = true + topics = ["example", "public", "opensource"] + homepage = "https://example.com" + delete_head_on_merge = true + allow_auto_merge = true + dependabot_security_updates = true + + # Template for new repository (optional) + # template = { + # owner = "template-owner" + # repository = "template-repo" + # } + } + } + + # Private repositories configuration + private_repositories = { + "example-private-repo" = { + description = "Example private repository" + default_branch = "main" + repository_team_permissions_override = { + # Example: Give specific teams custom permissions + # "developers" = "push" + # "maintainers" = "admin" + } + protected_branches = [] # Explicitly disable branch protection or configure as needed + advance_security = false + has_vulnerability_alerts = true + topics = ["example", "private", "internal"] + homepage = "" + delete_head_on_merge = true + allow_auto_merge = true + dependabot_security_updates = true + + # Archive settings (optional) + # archived = false + } + + "example-app-repo" = { + description = "Example application repository with advanced features" + default_branch = "main" + repository_team_permissions_override = {} + protected_branches = [ + { + pattern = "main" + enforce_admins = true + require_signed_commits = true + required_linear_history = true + allow_force_pushes = false + allow_deletions = false + require_conversation_resolution = true + + required_pull_request_reviews = { + dismiss_stale_reviews = true + restrict_dismissals = false + dismissal_restrictions = [] + require_code_owner_reviews = true + required_approving_review_count = 2 + require_last_push_approval = true + } + + required_status_checks = { + strict = true + contexts = ["ci/test", "ci/build"] + } + } + ] + advance_security = true + has_vulnerability_alerts = true + topics = ["example", "application", "production"] + homepage = "https://app.example.com" + delete_head_on_merge = true + allow_auto_merge = false # Disabled for production repos + dependabot_security_updates = true + } + } +} diff --git a/organizations/projects/example-project/example-org/teams/terragrunt.hcl b/organizations/projects/example-project/example-org/teams/terragrunt.hcl new file mode 100644 index 0000000..288a260 --- /dev/null +++ b/organizations/projects/example-project/example-org/teams/terragrunt.hcl @@ -0,0 +1,79 @@ +# Example team configuration +# This file demonstrates how to configure teams for a project using the team_set module + +include "root" { + path = find_in_parent_folders() + expose = true +} + +include "providers" { + path = "${get_repo_root()}/organizations/providers/${basename(dirname(dirname(get_terragrunt_dir())))}/providers.hcl" + expose = true +} + +terraform { + source = "github.com/FociSolutions/github-foundations-modules//modules/team_set" +} + +inputs = { + teams = { + # Administrative team + "example-admins" = { + name = "Example Admins" + description = "Administrative team for the example project" + privacy = "closed" # Options: closed, secret + members = [ + "admin1", + "admin2" + ] + maintainers = [ + "admin1" + ] + } + + # Developer team + "example-developers" = { + name = "Example Developers" + description = "Developer team for the example project" + privacy = "closed" + members = [ + "developer1", + "developer2", + "developer3", + "developer4" + ] + maintainers = [ + "developer1", + "developer2" + ] + } + + # Read-only team + "example-viewers" = { + name = "Example Viewers" + description = "Read-only access team for the example project" + privacy = "closed" + members = [ + "viewer1", + "viewer2" + ] + maintainers = [ + "viewer1" + ] + } + + # Security team + "example-security" = { + name = "Example Security" + description = "Security team responsible for code reviews and security updates" + privacy = "secret" # Secret teams are only visible to organization owners and team members + members = [ + "security1", + "security2" + ] + maintainers = [ + "security1" + ] + } + } +} diff --git a/organizations/providers/example-org/providers.hcl b/organizations/providers/example-org/providers.hcl new file mode 100644 index 0000000..53f8ab8 --- /dev/null +++ b/organizations/providers/example-org/providers.hcl @@ -0,0 +1,36 @@ +# Example provider configuration for an organization +# This file demonstrates how to configure the GitHub provider for a specific organization + +locals { + organization_name = "example-org" + secret_manager_project = get_env("GCP_SECRET_MANAGER_PROJECT") +} + +generate "github_provider" { + path = "provider.tf" + if_exists = "overwrite" + contents = < Date: Wed, 18 Feb 2026 20:27:43 +0000 Subject: [PATCH 3/8] Add validation module and format all configurations Co-authored-by: bzarboni1 <99673202+bzarboni1@users.noreply.github.com> --- .gitignore | 31 ++ organizations/main.tftest.hcl | 360 ++++++++++-------- .../organizations/example-org/terragrunt.hcl | 26 +- .../example-org/repositories/terragrunt.hcl | 62 +-- .../example-org/teams/terragrunt.hcl | 14 +- organizations/validation.tf | 180 +++++++++ 6 files changed, 459 insertions(+), 214 deletions(-) create mode 100644 .gitignore create mode 100644 organizations/validation.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b2218e --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Terraform and Terragrunt +.terraform/ +.terragrunt-cache/ +*.tfstate +*.tfstate.* +*.tfvars +.terraform.lock.hcl +crash.log +crash.*.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraformrc +terraform.rc + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Build artifacts +dist/ +build/ +*.log diff --git a/organizations/main.tftest.hcl b/organizations/main.tftest.hcl index e75d045..988c189 100644 --- a/organizations/main.tftest.hcl +++ b/organizations/main.tftest.hcl @@ -1,72 +1,47 @@ # Terraform test file for organizations layer -# This file provides comprehensive testing for the organizations folder structure +# This file provides comprehensive validation testing for configuration patterns # Run with: terraform test -verbose +# +# This test file validates: +# - Organization settings best practices +# - Repository configuration standards +# - Team configuration patterns +# - Security settings compliance +# - Naming conventions +# - Input validation # Mock all external providers to avoid requiring actual credentials mock_provider "github" {} mock_provider "google" {} mock_provider "google-beta" {} -# Override external modules to provide test data -# These override the external modules from github-foundations-modules -override_module { - target = module.organization_settings - outputs = { - organization_id = "123456" - organization_name = "test-org" - } -} - -override_module { - target = module.repository_set - outputs = { - repository_ids = { - "test-repo-1" = "repo-123" - "test-repo-2" = "repo-456" - } - } -} - -override_module { - target = module.team_set - outputs = { - team_ids = { - "test-team-1" = "team-123" - "test-team-2" = "team-456" - } - } -} - # Test variables for organization settings variables { - test_organization_name = "test-organization" - test_project_name = "test-project" - # Example organization settings organization_settings = { - name = "test-org" - billing_email = "billing@example.com" - company = "Test Company" - email = "contact@example.com" - location = "Test Location" - description = "Test organization for validation" - has_organization_projects = true - has_repository_projects = true - default_repository_permission = "read" - members_can_create_repositories = false - members_can_create_public_repositories = false - members_can_create_private_repositories = false - members_can_create_pages = false - members_can_fork_private_repositories = false - web_commit_signoff_required = true - advanced_security_enabled_for_new_repositories = true - dependabot_alerts_enabled_for_new_repositories = true - dependabot_security_updates_enabled_for_new_repositories = true - dependency_graph_enabled_for_new_repositories = true - secret_scanning_enabled_for_new_repositories = true + name = "test-org" + billing_email = "billing@example.com" + company = "Test Company" + email = "contact@example.com" + location = "Test Location" + description = "Test organization for validation" + has_organization_projects = true + has_repository_projects = true + default_repository_permission = "read" + members_can_create_repositories = false + members_can_create_public_repositories = false + members_can_create_private_repositories = false + members_can_create_pages = false + members_can_fork_private_repositories = false + web_commit_signoff_required = true + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true secret_scanning_push_protection_enabled_for_new_repositories = true } - + # Example public repository configuration public_repositories = { "test-public-repo" = { @@ -82,7 +57,7 @@ variables { dependabot_security_updates = true } } - + # Example private repository configuration private_repositories = { "test-private-repo" = { @@ -99,7 +74,7 @@ variables { dependabot_security_updates = true } } - + # Example team configuration teams = { "test-team-admins" = { @@ -122,151 +97,210 @@ variables { # Test 1: Validate organization settings structure run "organization_settings_validation" { command = plan - + assert { - condition = var.organization_settings.name == "test-org" + condition = output.organization_name == "test-org" error_message = "Organization name should be 'test-org'" } - + assert { - condition = var.organization_settings.web_commit_signoff_required == true - error_message = "Web commit signoff should be required for security" + condition = output.security_settings.advanced_security_enabled == true + error_message = "Advanced security should be enabled" } - + assert { - condition = var.organization_settings.members_can_create_public_repositories == false - error_message = "Members should not be able to create public repositories by default" + condition = output.security_settings.secret_scanning_enabled == true + error_message = "Secret scanning should be enabled" } - + assert { - condition = var.organization_settings.advanced_security_enabled_for_new_repositories == true - error_message = "Advanced security should be enabled for new repositories" + condition = output.security_settings.secret_push_protection_enabled == true + error_message = "Secret push protection should be enabled" } - + assert { - condition = var.organization_settings.secret_scanning_enabled_for_new_repositories == true - error_message = "Secret scanning should be enabled for new repositories" + condition = output.security_settings.dependabot_alerts_enabled == true + error_message = "Dependabot alerts should be enabled" } - + assert { - condition = var.organization_settings.secret_scanning_push_protection_enabled_for_new_repositories == true - error_message = "Secret scanning push protection should be enabled for new repositories" + condition = output.security_settings.dependabot_updates_enabled == true + error_message = "Dependabot security updates should be enabled" } } -# Test 2: Validate repository configuration +# Test 2: Validate repository counts run "repository_configuration_validation" { command = plan - - assert { - condition = var.public_repositories["test-public-repo"].default_branch == "main" - error_message = "Default branch should be 'main'" - } - - assert { - condition = var.public_repositories["test-public-repo"].delete_head_on_merge == true - error_message = "Delete head on merge should be enabled for clean repository management" - } - - assert { - condition = var.public_repositories["test-public-repo"].has_vulnerability_alerts == true - error_message = "Vulnerability alerts should be enabled" - } - + assert { - condition = var.private_repositories["test-private-repo"].default_branch == "main" - error_message = "Default branch should be 'main'" + condition = output.repository_count == 2 + error_message = "Should have 2 repositories configured (1 public + 1 private)" } - + assert { - condition = var.private_repositories["test-private-repo"].dependabot_security_updates == true - error_message = "Dependabot security updates should be enabled" + condition = output.validation_passed == true + error_message = "All validation checks should pass" } } -# Test 3: Validate team configuration +# Test 3: Validate team counts run "team_configuration_validation" { command = plan - - assert { - condition = length(var.teams) == 2 - error_message = "Should have 2 test teams configured" - } - - assert { - condition = var.teams["test-team-admins"].privacy == "closed" - error_message = "Team privacy should be 'closed' for security" - } - - assert { - condition = length(var.teams["test-team-admins"].members) == 2 - error_message = "Admin team should have 2 members" - } - - assert { - condition = length(var.teams["test-team-developers"].members) == 3 - error_message = "Developer team should have 3 members" - } - + assert { - condition = contains(var.teams["test-team-admins"].maintainers, "admin1") - error_message = "admin1 should be a maintainer of the admin team" + condition = output.team_count == 2 + error_message = "Should have 2 teams configured" } } -# Test 4: Validate security settings -run "security_settings_validation" { +# Test 4: Test invalid organization name +run "invalid_organization_name" { command = plan - - assert { - condition = alltrue([ - var.organization_settings.dependabot_alerts_enabled_for_new_repositories, - var.organization_settings.dependabot_security_updates_enabled_for_new_repositories, - var.organization_settings.dependency_graph_enabled_for_new_repositories - ]) - error_message = "All Dependabot security features should be enabled" - } - - assert { - condition = alltrue([ - var.organization_settings.secret_scanning_enabled_for_new_repositories, - var.organization_settings.secret_scanning_push_protection_enabled_for_new_repositories - ]) - error_message = "Secret scanning and push protection should both be enabled" - } - - assert { - condition = !var.organization_settings.members_can_fork_private_repositories - error_message = "Members should not be able to fork private repositories by default for security" + + variables { + organization_settings = { + name = "Test_Org_Invalid" # Invalid: contains uppercase and underscores + billing_email = "billing@example.com" + default_repository_permission = "read" + web_commit_signoff_required = true + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true + } + public_repositories = {} + private_repositories = {} + teams = {} } + + expect_failures = [ + var.organization_settings, + ] } -# Test 5: Validate naming conventions -run "naming_conventions_validation" { +# Test 5: Test invalid email format +run "invalid_email_format" { command = plan - - assert { - condition = can(regex("^[a-z0-9-]+$", var.test_organization_name)) - error_message = "Organization name should only contain lowercase letters, numbers, and hyphens" + + variables { + organization_settings = { + name = "test-org" + billing_email = "invalid-email" # Invalid: not a valid email + default_repository_permission = "read" + web_commit_signoff_required = true + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true + } + public_repositories = {} + private_repositories = {} + teams = {} } - - assert { - condition = can(regex("^[a-z0-9-]+$", var.test_project_name)) - error_message = "Project name should only contain lowercase letters, numbers, and hyphens" + + expect_failures = [ + var.organization_settings, + ] +} + +# Test 6: Test invalid repository permission +run "invalid_repository_permission" { + command = plan + + variables { + organization_settings = { + name = "test-org" + billing_email = "billing@example.com" + default_repository_permission = "super-admin" # Invalid: not a valid permission + web_commit_signoff_required = true + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true + } + public_repositories = {} + private_repositories = {} + teams = {} } + + expect_failures = [ + var.organization_settings, + ] } -# Test 6: Validate email format in organization settings -run "email_format_validation" { +# Test 7: Test invalid team privacy +run "invalid_team_privacy" { command = plan - - assert { - condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.organization_settings.billing_email)) - error_message = "Billing email should be in valid email format" + + variables { + organization_settings = { + name = "test-org" + billing_email = "billing@example.com" + default_repository_permission = "read" + web_commit_signoff_required = true + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true + } + public_repositories = {} + private_repositories = {} + teams = { + "test-team" = { + name = "Test Team" + description = "Test team" + privacy = "open" # Invalid: must be closed or secret + members = ["user1"] + maintainers = ["user1"] + } + } } - - assert { - condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.organization_settings.email)) - error_message = "Contact email should be in valid email format" + + expect_failures = [ + var.teams, + ] +} + +# Test 8: Test team without maintainers +run "team_without_maintainers" { + command = plan + + variables { + organization_settings = { + name = "test-org" + billing_email = "billing@example.com" + default_repository_permission = "read" + web_commit_signoff_required = true + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true + } + public_repositories = {} + private_repositories = {} + teams = { + "test-team" = { + name = "Test Team" + description = "Test team" + privacy = "closed" + members = ["user1", "user2"] + maintainers = [] # Invalid: must have at least one maintainer + } + } } + + expect_failures = [ + var.teams, + ] } diff --git a/organizations/organizations/example-org/terragrunt.hcl b/organizations/organizations/example-org/terragrunt.hcl index b1e44e2..ab42573 100644 --- a/organizations/organizations/example-org/terragrunt.hcl +++ b/organizations/organizations/example-org/terragrunt.hcl @@ -30,21 +30,21 @@ inputs = { has_repository_projects = true # Repository creation permissions - default_repository_permission = "read" - members_can_create_repositories = false - members_can_create_public_repositories = false - members_can_create_private_repositories = false - members_can_create_pages = false - members_can_fork_private_repositories = false - + default_repository_permission = "read" + members_can_create_repositories = false + members_can_create_public_repositories = false + members_can_create_private_repositories = false + members_can_create_pages = false + members_can_fork_private_repositories = false + # Commit signoff requirement web_commit_signoff_required = true # Security settings for new repositories - advanced_security_enabled_for_new_repositories = true - dependabot_alerts_enabled_for_new_repositories = true - dependabot_security_updates_enabled_for_new_repositories = true - dependency_graph_enabled_for_new_repositories = true - secret_scanning_enabled_for_new_repositories = true - secret_scanning_push_protection_enabled_for_new_repositories = true + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true } diff --git a/organizations/projects/example-project/example-org/repositories/terragrunt.hcl b/organizations/projects/example-project/example-org/repositories/terragrunt.hcl index 03ef192..0807016 100644 --- a/organizations/projects/example-project/example-org/repositories/terragrunt.hcl +++ b/organizations/projects/example-project/example-org/repositories/terragrunt.hcl @@ -23,20 +23,20 @@ inputs = { # Public repositories configuration public_repositories = { "example-public-repo" = { - description = "Example public repository" - default_branch = "main" + description = "Example public repository" + default_branch = "main" repository_team_permissions_override = { # Reference teams from dependency # "team-slug" = "permission-level" (pull, push, admin, maintain, triage) } - advance_security = false - has_vulnerability_alerts = true - topics = ["example", "public", "opensource"] - homepage = "https://example.com" - delete_head_on_merge = true - allow_auto_merge = true - dependabot_security_updates = true - + advance_security = false + has_vulnerability_alerts = true + topics = ["example", "public", "opensource"] + homepage = "https://example.com" + delete_head_on_merge = true + allow_auto_merge = true + dependabot_security_updates = true + # Template for new repository (optional) # template = { # owner = "template-owner" @@ -48,26 +48,26 @@ inputs = { # Private repositories configuration private_repositories = { "example-private-repo" = { - description = "Example private repository" - default_branch = "main" + description = "Example private repository" + default_branch = "main" repository_team_permissions_override = { # Example: Give specific teams custom permissions # "developers" = "push" # "maintainers" = "admin" } - protected_branches = [] # Explicitly disable branch protection or configure as needed - advance_security = false - has_vulnerability_alerts = true - topics = ["example", "private", "internal"] - homepage = "" - delete_head_on_merge = true - allow_auto_merge = true - dependabot_security_updates = true - + protected_branches = [] # Explicitly disable branch protection or configure as needed + advance_security = false + has_vulnerability_alerts = true + topics = ["example", "private", "internal"] + homepage = "" + delete_head_on_merge = true + allow_auto_merge = true + dependabot_security_updates = true + # Archive settings (optional) # archived = false } - + "example-app-repo" = { description = "Example application repository with advanced features" default_branch = "main" @@ -81,7 +81,7 @@ inputs = { allow_force_pushes = false allow_deletions = false require_conversation_resolution = true - + required_pull_request_reviews = { dismiss_stale_reviews = true restrict_dismissals = false @@ -90,20 +90,20 @@ inputs = { required_approving_review_count = 2 require_last_push_approval = true } - + required_status_checks = { strict = true contexts = ["ci/test", "ci/build"] } } ] - advance_security = true - has_vulnerability_alerts = true - topics = ["example", "application", "production"] - homepage = "https://app.example.com" - delete_head_on_merge = true - allow_auto_merge = false # Disabled for production repos - dependabot_security_updates = true + advance_security = true + has_vulnerability_alerts = true + topics = ["example", "application", "production"] + homepage = "https://app.example.com" + delete_head_on_merge = true + allow_auto_merge = false # Disabled for production repos + dependabot_security_updates = true } } } diff --git a/organizations/projects/example-project/example-org/teams/terragrunt.hcl b/organizations/projects/example-project/example-org/teams/terragrunt.hcl index 288a260..09524f9 100644 --- a/organizations/projects/example-project/example-org/teams/terragrunt.hcl +++ b/organizations/projects/example-project/example-org/teams/terragrunt.hcl @@ -22,7 +22,7 @@ inputs = { name = "Example Admins" description = "Administrative team for the example project" privacy = "closed" # Options: closed, secret - members = [ + members = [ "admin1", "admin2" ] @@ -30,13 +30,13 @@ inputs = { "admin1" ] } - + # Developer team "example-developers" = { name = "Example Developers" description = "Developer team for the example project" privacy = "closed" - members = [ + members = [ "developer1", "developer2", "developer3", @@ -47,13 +47,13 @@ inputs = { "developer2" ] } - + # Read-only team "example-viewers" = { name = "Example Viewers" description = "Read-only access team for the example project" privacy = "closed" - members = [ + members = [ "viewer1", "viewer2" ] @@ -61,13 +61,13 @@ inputs = { "viewer1" ] } - + # Security team "example-security" = { name = "Example Security" description = "Security team responsible for code reviews and security updates" privacy = "secret" # Secret teams are only visible to organization owners and team members - members = [ + members = [ "security1", "security2" ] diff --git a/organizations/validation.tf b/organizations/validation.tf new file mode 100644 index 0000000..2b96306 --- /dev/null +++ b/organizations/validation.tf @@ -0,0 +1,180 @@ +# Validation module for organizations layer +# This module provides input validation and type checking for configurations +# It doesn't create any real resources, just validates inputs + +variable "organization_settings" { + description = "Organization settings configuration" + type = object({ + name = string + billing_email = string + company = optional(string, "") + email = optional(string, "") + location = optional(string, "") + description = optional(string, "") + has_organization_projects = optional(bool, true) + has_repository_projects = optional(bool, true) + default_repository_permission = optional(string, "read") + members_can_create_repositories = optional(bool, false) + members_can_create_public_repositories = optional(bool, false) + members_can_create_private_repositories = optional(bool, false) + members_can_create_pages = optional(bool, false) + members_can_fork_private_repositories = optional(bool, false) + web_commit_signoff_required = optional(bool, true) + advanced_security_enabled_for_new_repositories = optional(bool, true) + dependabot_alerts_enabled_for_new_repositories = optional(bool, true) + dependabot_security_updates_enabled_for_new_repositories = optional(bool, true) + dependency_graph_enabled_for_new_repositories = optional(bool, true) + secret_scanning_enabled_for_new_repositories = optional(bool, true) + secret_scanning_push_protection_enabled_for_new_repositories = optional(bool, true) + }) + + validation { + condition = can(regex("^[a-z0-9-]+$", var.organization_settings.name)) + error_message = "Organization name must contain only lowercase letters, numbers, and hyphens." + } + + validation { + condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.organization_settings.billing_email)) + error_message = "Billing email must be a valid email address." + } + + validation { + condition = contains(["read", "write", "admin", "none"], var.organization_settings.default_repository_permission) + error_message = "Default repository permission must be one of: read, write, admin, none." + } +} + +variable "public_repositories" { + description = "Public repository configurations" + type = map(object({ + description = string + default_branch = optional(string, "main") + repository_team_permissions_override = optional(map(string), {}) + advance_security = optional(bool, false) + has_vulnerability_alerts = optional(bool, true) + topics = optional(list(string), []) + homepage = optional(string, "") + delete_head_on_merge = optional(bool, true) + allow_auto_merge = optional(bool, true) + dependabot_security_updates = optional(bool, true) + })) + default = {} + + validation { + condition = alltrue([ + for name, repo in var.public_repositories : + can(regex("^[a-z0-9._-]+$", name)) + ]) + error_message = "Repository names must contain only lowercase letters, numbers, dots, underscores, and hyphens." + } + + validation { + condition = alltrue([ + for name, repo in var.public_repositories : + contains(["main", "master", "develop"], repo.default_branch) + ]) + error_message = "Default branch should be main, master, or develop." + } +} + +variable "private_repositories" { + description = "Private repository configurations" + type = map(object({ + description = string + default_branch = optional(string, "main") + repository_team_permissions_override = optional(map(string), {}) + protected_branches = optional(list(any), []) + advance_security = optional(bool, false) + has_vulnerability_alerts = optional(bool, true) + topics = optional(list(string), []) + homepage = optional(string, "") + delete_head_on_merge = optional(bool, true) + allow_auto_merge = optional(bool, true) + dependabot_security_updates = optional(bool, true) + })) + default = {} + + validation { + condition = alltrue([ + for name, repo in var.private_repositories : + can(regex("^[a-z0-9._-]+$", name)) + ]) + error_message = "Repository names must contain only lowercase letters, numbers, dots, underscores, and hyphens." + } +} + +variable "teams" { + description = "Team configurations" + type = map(object({ + name = string + description = string + privacy = optional(string, "closed") + members = list(string) + maintainers = list(string) + })) + default = {} + + validation { + condition = alltrue([ + for team_slug, team in var.teams : + can(regex("^[a-z0-9-]+$", team_slug)) + ]) + error_message = "Team slugs must contain only lowercase letters, numbers, and hyphens." + } + + validation { + condition = alltrue([ + for team_slug, team in var.teams : + contains(["closed", "secret"], team.privacy) + ]) + error_message = "Team privacy must be either 'closed' or 'secret'." + } + + validation { + condition = alltrue([ + for team_slug, team in var.teams : + length(team.maintainers) > 0 + ]) + error_message = "Each team must have at least one maintainer." + } + + validation { + condition = alltrue([ + for team_slug, team in var.teams : + alltrue([for maintainer in team.maintainers : contains(team.members, maintainer)]) + ]) + error_message = "All team maintainers must also be team members." + } +} + +# Output validation results +output "validation_passed" { + description = "Indicates if all validations passed" + value = true +} + +output "organization_name" { + description = "Validated organization name" + value = var.organization_settings.name +} + +output "repository_count" { + description = "Total number of repositories configured" + value = length(var.public_repositories) + length(var.private_repositories) +} + +output "team_count" { + description = "Total number of teams configured" + value = length(var.teams) +} + +output "security_settings" { + description = "Organization security settings summary" + value = { + advanced_security_enabled = var.organization_settings.advanced_security_enabled_for_new_repositories + secret_scanning_enabled = var.organization_settings.secret_scanning_enabled_for_new_repositories + secret_push_protection_enabled = var.organization_settings.secret_scanning_push_protection_enabled_for_new_repositories + dependabot_alerts_enabled = var.organization_settings.dependabot_alerts_enabled_for_new_repositories + dependabot_updates_enabled = var.organization_settings.dependabot_security_updates_enabled_for_new_repositories + } +} From ba97917f41ad8b5773cf9fb46f89b0387280cd55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:28:59 +0000 Subject: [PATCH 4/8] Add Makefile and update documentation with Make commands Co-authored-by: bzarboni1 <99673202+bzarboni1@users.noreply.github.com> --- Makefile | 85 ++++++++++++++++++++++++++++++++++++++++ organizations/README.md | 13 ++++++ organizations/TESTING.md | 72 ++++++++++++++++++++++++++-------- 3 files changed, 153 insertions(+), 17 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bb206ab --- /dev/null +++ b/Makefile @@ -0,0 +1,85 @@ +# Makefile for GitHub Foundations Organizations Layer +# This provides convenient commands for testing, formatting, and validation + +.PHONY: help init test format format-check lint validate security clean all + +help: ## Show this help message + @echo "GitHub Foundations Organizations Layer - Development Commands" + @echo "" + @echo "Usage: make [target]" + @echo "" + @echo "Targets:" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' + +init: ## Initialize Terraform + @echo "Initializing Terraform..." + cd organizations && terraform init + +test: init ## Run Terraform tests + @echo "Running Terraform tests..." + cd organizations && terraform test -verbose + +format: ## Format Terraform and Terragrunt files + @echo "Formatting Terraform files..." + cd organizations && terraform fmt -recursive + @echo "Formatting Terragrunt files..." + cd organizations && terragrunt hclfmt + +format-check: ## Check if Terraform and Terragrunt files are formatted + @echo "Checking Terraform formatting..." + cd organizations && terraform fmt -check -diff -recursive + @echo "Checking Terragrunt formatting..." + cd organizations && terragrunt hclfmt --terragrunt-check --terragrunt-diff + +validate: init ## Validate Terraform configuration + @echo "Validating Terraform configuration..." + cd organizations && terraform validate + +lint: ## Run TFLint on Terraform files (requires tflint) + @echo "Running TFLint..." + @if command -v tflint >/dev/null 2>&1; then \ + cd organizations && tflint --init && tflint --recursive; \ + else \ + echo "TFLint not installed. Install from: https://github.com/terraform-linters/tflint"; \ + exit 1; \ + fi + +security: ## Run security scan with Trivy (requires trivy) + @echo "Running security scan..." + @if command -v trivy >/dev/null 2>&1; then \ + trivy config organizations/; \ + else \ + echo "Trivy not installed. Install from: https://aquasecurity.github.io/trivy/"; \ + exit 1; \ + fi + +clean: ## Clean Terraform artifacts + @echo "Cleaning Terraform artifacts..." + find organizations -type d -name ".terraform" -exec rm -rf {} + 2>/dev/null || true + find organizations -type d -name ".terragrunt-cache" -exec rm -rf {} + 2>/dev/null || true + find organizations -type f -name ".terraform.lock.hcl" -delete 2>/dev/null || true + +pre-commit-install: ## Install pre-commit hooks + @echo "Installing pre-commit hooks..." + @if command -v pre-commit >/dev/null 2>&1; then \ + pre-commit install; \ + echo "Pre-commit hooks installed successfully!"; \ + else \ + echo "pre-commit not installed. Install with: pip install pre-commit"; \ + exit 1; \ + fi + +pre-commit-run: ## Run pre-commit hooks on all files + @echo "Running pre-commit hooks..." + @if command -v pre-commit >/dev/null 2>&1; then \ + pre-commit run --all-files; \ + else \ + echo "pre-commit not installed. Install with: pip install pre-commit"; \ + exit 1; \ + fi + +all: format validate test ## Run format, validate, and test + +check: format-check validate test ## Run format check, validate, and test (for CI) + +.DEFAULT_GOAL := help diff --git a/organizations/README.md b/organizations/README.md index 4499bbe..f0fb81d 100644 --- a/organizations/README.md +++ b/organizations/README.md @@ -146,6 +146,10 @@ The organizations layer includes comprehensive testing capabilities to validate Run tests with: ```bash +# Using Make (recommended) +make test + +# Or using Terraform directly cd organizations/ terraform init terraform test -verbose @@ -153,10 +157,19 @@ terraform test -verbose Check formatting: ```bash +# Using Make (recommended) +make format-check + +# Or manually terraform fmt -check -recursive terragrunt hclfmt --terragrunt-check --terragrunt-diff ``` +Run all checks: +```bash +make check # Runs format-check, validate, and test +``` + ### Pre-commit Hooks Install pre-commit hooks to automatically validate changes: diff --git a/organizations/TESTING.md b/organizations/TESTING.md index cd90711..006f15a 100644 --- a/organizations/TESTING.md +++ b/organizations/TESTING.md @@ -33,7 +33,33 @@ The organizations layer includes a comprehensive test file (`main.tftest.hcl`) t - Naming conventions - Email format validation -To run the tests: +#### Using Make (Recommended) + +The easiest way to run tests is using the provided Makefile: + +```bash +# Show all available commands +make help + +# Run all checks (format, validate, test) +make check + +# Run tests only +make test + +# Format code +make format + +# Check formatting without modifying files +make format-check + +# Validate configuration +make validate +``` + +#### Using Terraform Directly + +To run the tests directly with Terraform: ```bash cd organizations/ @@ -61,7 +87,17 @@ main.tftest.hcl... pass ## Format Checking -### Check Terraform Formatting +### Using Make (Recommended) + +```bash +# Check formatting without modifying files +make format-check + +# Automatically format all files +make format +``` + +### Check Terraform Formatting Manually Check if your Terraform files are properly formatted: @@ -208,29 +244,31 @@ Follow this workflow before committing changes: 1. **Make your changes** to Terraform/Terragrunt files -2. **Format your code**: - ```bash - terraform fmt -recursive - terragrunt hclfmt - ``` - -3. **Validate syntax**: +2. **Run all checks** using Make: ```bash - terraform validate + make check ``` - -4. **Run tests**: + + Or run steps individually: + ```bash - terraform test -verbose + # Format your code + make format + + # Validate syntax + make validate + + # Run tests + make test ``` -5. **Run security scans**: +3. **Optional: Run security scans**: ```bash - tflint --recursive - trivy config . + make lint # Requires tflint + make security # Requires trivy ``` -6. **Commit your changes**: +4. **Commit your changes**: ```bash git add . git commit -m "feat: your commit message" From 9e88b1b627b2e7f1673bf878922375fa3b9b977c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:30:21 +0000 Subject: [PATCH 5/8] Remove overly restrictive default branch validation Co-authored-by: bzarboni1 <99673202+bzarboni1@users.noreply.github.com> --- organizations/validation.tf | 8 -------- 1 file changed, 8 deletions(-) diff --git a/organizations/validation.tf b/organizations/validation.tf index 2b96306..c926fca 100644 --- a/organizations/validation.tf +++ b/organizations/validation.tf @@ -67,14 +67,6 @@ variable "public_repositories" { ]) error_message = "Repository names must contain only lowercase letters, numbers, dots, underscores, and hyphens." } - - validation { - condition = alltrue([ - for name, repo in var.public_repositories : - contains(["main", "master", "develop"], repo.default_branch) - ]) - error_message = "Default branch should be main, master, or develop." - } } variable "private_repositories" { From cd698d87e48d5077af39ecda10b600f649ae4243 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:31:27 +0000 Subject: [PATCH 6/8] Fix typo: advance_security -> advanced_security Co-authored-by: bzarboni1 <99673202+bzarboni1@users.noreply.github.com> --- organizations/main.tftest.hcl | 4 ++-- .../example-project/example-org/repositories/terragrunt.hcl | 6 +++--- organizations/validation.tf | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/organizations/main.tftest.hcl b/organizations/main.tftest.hcl index 988c189..0732fa8 100644 --- a/organizations/main.tftest.hcl +++ b/organizations/main.tftest.hcl @@ -48,7 +48,7 @@ variables { description = "Test public repository" default_branch = "main" repository_team_permissions_override = {} - advance_security = false + advanced_security = false has_vulnerability_alerts = true topics = ["test", "public"] homepage = "https://example.com" @@ -65,7 +65,7 @@ variables { default_branch = "main" repository_team_permissions_override = {} protected_branches = [] - advance_security = false + advanced_security = false has_vulnerability_alerts = true topics = ["test", "private"] homepage = "" diff --git a/organizations/projects/example-project/example-org/repositories/terragrunt.hcl b/organizations/projects/example-project/example-org/repositories/terragrunt.hcl index 0807016..dc00734 100644 --- a/organizations/projects/example-project/example-org/repositories/terragrunt.hcl +++ b/organizations/projects/example-project/example-org/repositories/terragrunt.hcl @@ -29,7 +29,7 @@ inputs = { # Reference teams from dependency # "team-slug" = "permission-level" (pull, push, admin, maintain, triage) } - advance_security = false + advanced_security = false has_vulnerability_alerts = true topics = ["example", "public", "opensource"] homepage = "https://example.com" @@ -56,7 +56,7 @@ inputs = { # "maintainers" = "admin" } protected_branches = [] # Explicitly disable branch protection or configure as needed - advance_security = false + advanced_security = false has_vulnerability_alerts = true topics = ["example", "private", "internal"] homepage = "" @@ -97,7 +97,7 @@ inputs = { } } ] - advance_security = true + advanced_security = true has_vulnerability_alerts = true topics = ["example", "application", "production"] homepage = "https://app.example.com" diff --git a/organizations/validation.tf b/organizations/validation.tf index c926fca..ffd144e 100644 --- a/organizations/validation.tf +++ b/organizations/validation.tf @@ -50,7 +50,7 @@ variable "public_repositories" { description = string default_branch = optional(string, "main") repository_team_permissions_override = optional(map(string), {}) - advance_security = optional(bool, false) + advanced_security = optional(bool, false) has_vulnerability_alerts = optional(bool, true) topics = optional(list(string), []) homepage = optional(string, "") @@ -76,7 +76,7 @@ variable "private_repositories" { default_branch = optional(string, "main") repository_team_permissions_override = optional(map(string), {}) protected_branches = optional(list(any), []) - advance_security = optional(bool, false) + advanced_security = optional(bool, false) has_vulnerability_alerts = optional(bool, true) topics = optional(list(string), []) homepage = optional(string, "") From bc4a6266648d50522a3c9455e81a760931fe2401 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:32:36 +0000 Subject: [PATCH 7/8] Revert to advance_security to match existing documentation Co-authored-by: bzarboni1 <99673202+bzarboni1@users.noreply.github.com> --- organizations/main.tftest.hcl | 18 +++++++++--------- .../organizations/example-org/terragrunt.hcl | 2 +- .../example-org/repositories/terragrunt.hcl | 6 +++--- organizations/validation.tf | 8 ++++---- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/organizations/main.tftest.hcl b/organizations/main.tftest.hcl index 0732fa8..56d9847 100644 --- a/organizations/main.tftest.hcl +++ b/organizations/main.tftest.hcl @@ -34,7 +34,7 @@ variables { members_can_create_pages = false members_can_fork_private_repositories = false web_commit_signoff_required = true - advanced_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -48,7 +48,7 @@ variables { description = "Test public repository" default_branch = "main" repository_team_permissions_override = {} - advanced_security = false + advance_security = false has_vulnerability_alerts = true topics = ["test", "public"] homepage = "https://example.com" @@ -65,7 +65,7 @@ variables { default_branch = "main" repository_team_permissions_override = {} protected_branches = [] - advanced_security = false + advance_security = false has_vulnerability_alerts = true topics = ["test", "private"] homepage = "" @@ -104,7 +104,7 @@ run "organization_settings_validation" { } assert { - condition = output.security_settings.advanced_security_enabled == true + condition = output.security_settings.advance_security_enabled == true error_message = "Advanced security should be enabled" } @@ -164,7 +164,7 @@ run "invalid_organization_name" { billing_email = "billing@example.com" default_repository_permission = "read" web_commit_signoff_required = true - advanced_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -191,7 +191,7 @@ run "invalid_email_format" { billing_email = "invalid-email" # Invalid: not a valid email default_repository_permission = "read" web_commit_signoff_required = true - advanced_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -218,7 +218,7 @@ run "invalid_repository_permission" { billing_email = "billing@example.com" default_repository_permission = "super-admin" # Invalid: not a valid permission web_commit_signoff_required = true - advanced_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -245,7 +245,7 @@ run "invalid_team_privacy" { billing_email = "billing@example.com" default_repository_permission = "read" web_commit_signoff_required = true - advanced_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -280,7 +280,7 @@ run "team_without_maintainers" { billing_email = "billing@example.com" default_repository_permission = "read" web_commit_signoff_required = true - advanced_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true diff --git a/organizations/organizations/example-org/terragrunt.hcl b/organizations/organizations/example-org/terragrunt.hcl index ab42573..0ecf2a8 100644 --- a/organizations/organizations/example-org/terragrunt.hcl +++ b/organizations/organizations/example-org/terragrunt.hcl @@ -41,7 +41,7 @@ inputs = { web_commit_signoff_required = true # Security settings for new repositories - advanced_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true diff --git a/organizations/projects/example-project/example-org/repositories/terragrunt.hcl b/organizations/projects/example-project/example-org/repositories/terragrunt.hcl index dc00734..0807016 100644 --- a/organizations/projects/example-project/example-org/repositories/terragrunt.hcl +++ b/organizations/projects/example-project/example-org/repositories/terragrunt.hcl @@ -29,7 +29,7 @@ inputs = { # Reference teams from dependency # "team-slug" = "permission-level" (pull, push, admin, maintain, triage) } - advanced_security = false + advance_security = false has_vulnerability_alerts = true topics = ["example", "public", "opensource"] homepage = "https://example.com" @@ -56,7 +56,7 @@ inputs = { # "maintainers" = "admin" } protected_branches = [] # Explicitly disable branch protection or configure as needed - advanced_security = false + advance_security = false has_vulnerability_alerts = true topics = ["example", "private", "internal"] homepage = "" @@ -97,7 +97,7 @@ inputs = { } } ] - advanced_security = true + advance_security = true has_vulnerability_alerts = true topics = ["example", "application", "production"] homepage = "https://app.example.com" diff --git a/organizations/validation.tf b/organizations/validation.tf index ffd144e..b092882 100644 --- a/organizations/validation.tf +++ b/organizations/validation.tf @@ -20,7 +20,7 @@ variable "organization_settings" { members_can_create_pages = optional(bool, false) members_can_fork_private_repositories = optional(bool, false) web_commit_signoff_required = optional(bool, true) - advanced_security_enabled_for_new_repositories = optional(bool, true) + advance_security_enabled_for_new_repositories = optional(bool, true) dependabot_alerts_enabled_for_new_repositories = optional(bool, true) dependabot_security_updates_enabled_for_new_repositories = optional(bool, true) dependency_graph_enabled_for_new_repositories = optional(bool, true) @@ -50,7 +50,7 @@ variable "public_repositories" { description = string default_branch = optional(string, "main") repository_team_permissions_override = optional(map(string), {}) - advanced_security = optional(bool, false) + advance_security = optional(bool, false) has_vulnerability_alerts = optional(bool, true) topics = optional(list(string), []) homepage = optional(string, "") @@ -76,7 +76,7 @@ variable "private_repositories" { default_branch = optional(string, "main") repository_team_permissions_override = optional(map(string), {}) protected_branches = optional(list(any), []) - advanced_security = optional(bool, false) + advance_security = optional(bool, false) has_vulnerability_alerts = optional(bool, true) topics = optional(list(string), []) homepage = optional(string, "") @@ -163,7 +163,7 @@ output "team_count" { output "security_settings" { description = "Organization security settings summary" value = { - advanced_security_enabled = var.organization_settings.advanced_security_enabled_for_new_repositories + advance_security_enabled = var.organization_settings.advance_security_enabled_for_new_repositories secret_scanning_enabled = var.organization_settings.secret_scanning_enabled_for_new_repositories secret_push_protection_enabled = var.organization_settings.secret_scanning_push_protection_enabled_for_new_repositories dependabot_alerts_enabled = var.organization_settings.dependabot_alerts_enabled_for_new_repositories From 15b96a388cffcd5daf35df90384514233b8fb7d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:33:09 +0000 Subject: [PATCH 8/8] Format all files to pass format checks Co-authored-by: bzarboni1 <99673202+bzarboni1@users.noreply.github.com> --- organizations/main.tftest.hcl | 12 ++++++------ .../organizations/example-org/terragrunt.hcl | 2 +- organizations/validation.tf | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/organizations/main.tftest.hcl b/organizations/main.tftest.hcl index 56d9847..9f018b1 100644 --- a/organizations/main.tftest.hcl +++ b/organizations/main.tftest.hcl @@ -34,7 +34,7 @@ variables { members_can_create_pages = false members_can_fork_private_repositories = false web_commit_signoff_required = true - advance_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -164,7 +164,7 @@ run "invalid_organization_name" { billing_email = "billing@example.com" default_repository_permission = "read" web_commit_signoff_required = true - advance_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -191,7 +191,7 @@ run "invalid_email_format" { billing_email = "invalid-email" # Invalid: not a valid email default_repository_permission = "read" web_commit_signoff_required = true - advance_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -218,7 +218,7 @@ run "invalid_repository_permission" { billing_email = "billing@example.com" default_repository_permission = "super-admin" # Invalid: not a valid permission web_commit_signoff_required = true - advance_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -245,7 +245,7 @@ run "invalid_team_privacy" { billing_email = "billing@example.com" default_repository_permission = "read" web_commit_signoff_required = true - advance_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true @@ -280,7 +280,7 @@ run "team_without_maintainers" { billing_email = "billing@example.com" default_repository_permission = "read" web_commit_signoff_required = true - advance_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true diff --git a/organizations/organizations/example-org/terragrunt.hcl b/organizations/organizations/example-org/terragrunt.hcl index 0ecf2a8..03c708f 100644 --- a/organizations/organizations/example-org/terragrunt.hcl +++ b/organizations/organizations/example-org/terragrunt.hcl @@ -41,7 +41,7 @@ inputs = { web_commit_signoff_required = true # Security settings for new repositories - advance_security_enabled_for_new_repositories = true + advance_security_enabled_for_new_repositories = true dependabot_alerts_enabled_for_new_repositories = true dependabot_security_updates_enabled_for_new_repositories = true dependency_graph_enabled_for_new_repositories = true diff --git a/organizations/validation.tf b/organizations/validation.tf index b092882..5d7fff8 100644 --- a/organizations/validation.tf +++ b/organizations/validation.tf @@ -20,7 +20,7 @@ variable "organization_settings" { members_can_create_pages = optional(bool, false) members_can_fork_private_repositories = optional(bool, false) web_commit_signoff_required = optional(bool, true) - advance_security_enabled_for_new_repositories = optional(bool, true) + advance_security_enabled_for_new_repositories = optional(bool, true) dependabot_alerts_enabled_for_new_repositories = optional(bool, true) dependabot_security_updates_enabled_for_new_repositories = optional(bool, true) dependency_graph_enabled_for_new_repositories = optional(bool, true) @@ -163,7 +163,7 @@ output "team_count" { output "security_settings" { description = "Organization security settings summary" value = { - advance_security_enabled = var.organization_settings.advance_security_enabled_for_new_repositories + advance_security_enabled = var.organization_settings.advance_security_enabled_for_new_repositories secret_scanning_enabled = var.organization_settings.secret_scanning_enabled_for_new_repositories secret_push_protection_enabled = var.organization_settings.secret_scanning_push_protection_enabled_for_new_repositories dependabot_alerts_enabled = var.organization_settings.dependabot_alerts_enabled_for_new_repositories