diff --git a/.github/workflows/cicd-1-pull-request.yaml b/.github/workflows/cicd-1-pull-request.yaml
index 004b11a5..1af7da19 100644
--- a/.github/workflows/cicd-1-pull-request.yaml
+++ b/.github/workflows/cicd-1-pull-request.yaml
@@ -22,6 +22,7 @@ jobs:
nodejs_version: ${{ steps.variables.outputs.nodejs_version }}
python_version: ${{ steps.variables.outputs.python_version }}
terraform_version: ${{ steps.variables.outputs.terraform_version }}
+ golang_version: ${{ steps.variables.outputs.golang_version }}
version: ${{ steps.variables.outputs.version }}
does_pull_request_exist: ${{ steps.pr_exists.outputs.does_pull_request_exist }}
docker_file_exists: ${{ steps.check_compose.outputs.docker_file_exists }}
@@ -37,9 +38,10 @@ jobs:
echo "build_datetime=$datetime" >> $GITHUB_OUTPUT
echo "build_timestamp=$(date --date=$datetime -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT
echo "build_epoch=$(date --date=$datetime -u +'%s')" >> $GITHUB_OUTPUT
- echo "nodejs_version=$(grep "^nodejs" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT
- echo "python_version=$(grep "^nodejs" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT
- echo "terraform_version=$(grep "^terraform" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT
+ echo "nodejs_version=$(grep "^nodejs" .tool-versions | cut -f2 -d' ' || echo 20.11.0)" >> $GITHUB_OUTPUT
+ echo "python_version=$(grep "^python" .tool-versions | cut -f2 -d' ' || echo 3.12.1)" >> $GITHUB_OUTPUT
+ echo "terraform_version=$(grep "^terraform" .tool-versions | cut -f2 -d' ' || echo 1.13.0)" >> $GITHUB_OUTPUT
+ echo "golang_version=$(grep "^golang" .tool-versions | cut -f2 -d' ' || echo 1.22.5)" >> $GITHUB_OUTPUT
echo "version=$(head -n 1 .version 2> /dev/null || echo unknown)" >> $GITHUB_OUTPUT
- name: "Check if pull request exists for this branch"
id: pr_exists
@@ -87,6 +89,7 @@ jobs:
nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}"
python_version: "${{ needs.metadata.outputs.python_version }}"
terraform_version: "${{ needs.metadata.outputs.terraform_version }}"
+ golang_version: "${{ needs.metadata.outputs.golang_version }}"
version: "${{ needs.metadata.outputs.version }}"
secrets: inherit
test-stage: # Recommended maximum execution time is 5 minutes
@@ -100,6 +103,7 @@ jobs:
nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}"
python_version: "${{ needs.metadata.outputs.python_version }}"
terraform_version: "${{ needs.metadata.outputs.terraform_version }}"
+ golang_version: "${{ needs.metadata.outputs.golang_version }}"
version: "${{ needs.metadata.outputs.version }}"
secrets: inherit
build-stage: # Recommended maximum execution time is 3 minutes
@@ -126,5 +130,6 @@ jobs:
nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}"
python_version: "${{ needs.metadata.outputs.python_version }}"
terraform_version: "${{ needs.metadata.outputs.terraform_version }}"
+ golang_version: "${{ needs.metadata.outputs.golang_version }}"
version: "${{ needs.metadata.outputs.version }}"
secrets: inherit
diff --git a/.github/workflows/stage-1-commit.yaml b/.github/workflows/stage-1-commit.yaml
index a97d49ee..e56eb93d 100644
--- a/.github/workflows/stage-1-commit.yaml
+++ b/.github/workflows/stage-1-commit.yaml
@@ -27,6 +27,10 @@ on:
description: "Terraform version, set by the CI/CD pipeline workflow"
required: true
type: string
+ golang_version:
+ description: "Go version, set by the CI/CD pipeline workflow"
+ required: true
+ type: string
version:
description: "Version of the software, set by the CI/CD pipeline workflow"
required: true
diff --git a/.github/workflows/stage-2-test.yaml b/.github/workflows/stage-2-test.yaml
index b10c159f..099850d9 100644
--- a/.github/workflows/stage-2-test.yaml
+++ b/.github/workflows/stage-2-test.yaml
@@ -27,71 +27,136 @@ on:
description: "Terraform version, set by the CI/CD pipeline workflow"
required: true
type: string
+ golang_version:
+ description: "Go version, set by the CI/CD pipeline workflow"
+ required: true
+ type: string
version:
description: "Version of the software, set by the CI/CD pipeline workflow"
required: true
type: string
jobs:
- test-unit:
- name: "Unit tests"
- runs-on: ubuntu-latest
- timeout-minutes: 5
- steps:
- - name: "Checkout code"
- uses: actions/checkout@v4
- - name: "Run unit test suite"
- run: |
- make test-unit
- - name: "Save the result of fast test suite"
- run: |
- echo "Nothing to save"
- test-lint:
- name: "Linting"
+ terraform-lint:
+ name: "Terraform lint (tflint)"
runs-on: ubuntu-latest
- timeout-minutes: 5
+ timeout-minutes: 10
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- - name: "Run linting"
- run: |
- make test-lint
- - name: "Save the linting result"
+ - name: Setup TFLint
+ uses: terraform-linters/setup-tflint@v4
+ with:
+ tflint_version: latest
+ - name: "Run TFLint on modules"
+ id: tflint
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- echo "Nothing to save"
- test-coverage:
- name: "Test coverage"
- needs: [test-unit]
+ echo "## TFLint Results" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ total_issues=0
+ for module_dir in $(find infrastructure/modules -mindepth 1 -maxdepth 1 -type d); do
+ module_name=$(basename "$module_dir")
+ # Only lint directories containing .tf files
+ if ls "$module_dir"/*.tf > /dev/null 2>&1; then
+ echo "=== Linting $module_dir ==="
+ # Init tflint for the module (downloads plugins if needed)
+ tflint --init --chdir="$module_dir" > /dev/null 2>&1 || true
+
+ # Capture output and count issues
+ output=$(tflint --chdir="$module_dir" --format=compact 2>&1 || true)
+ issue_count=$(echo "$output" | grep -c ":" || echo "0")
+
+ if [ "$issue_count" -gt 0 ] && [ -n "$output" ]; then
+ total_issues=$((total_issues + issue_count))
+ echo "### ⚠️ $module_name ($issue_count issues)" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "$output" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ else
+ echo "### ✅ $module_name" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "" >> $GITHUB_STEP_SUMMARY
+ fi
+ done
+ echo "---" >> $GITHUB_STEP_SUMMARY
+ echo "**Total issues: $total_issues**" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [ $total_issues -gt 0 ]; then
+ echo "> **Note:** TFLint issues are advisory only. Please address these issues to improve code quality." >> $GITHUB_STEP_SUMMARY
+ fi
+ # Always exit 0 - this job is advisory only
+ exit 0
+ terraform-security:
+ name: "Terraform security scan"
runs-on: ubuntu-latest
- timeout-minutes: 5
+ timeout-minutes: 10
+ permissions:
+ contents: read
+ security-events: write
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- - name: "Run test coverage check"
+ - name: "Run tfsec with SARIF output"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- make test-coverage
- - name: "Save the coverage check result"
+ # Install tfsec
+ curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash
+ # Run tfsec and output SARIF format
+ tfsec infrastructure/ --format sarif --out tfsec-results.sarif --soft-fail
+ - name: "Upload SARIF to GitHub Code Scanning"
+ uses: github/codeql-action/upload-sarif@v4
+ if: always()
+ with:
+ sarif_file: tfsec-results.sarif
+ category: terraform-security
+ - name: "Generate summary"
+ if: always()
run: |
- echo "Nothing to save"
+ echo "## Terraform Security Scan" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "Security findings are uploaded to the **Security** tab → **Code scanning alerts**." >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "> **Note:** Findings are informational and do not block merges." >> $GITHUB_STEP_SUMMARY
+ echo "> To make blocking, enable 'Require code scanning results' in branch protection rules." >> $GITHUB_STEP_SUMMARY
unit-test-terraform-modules:
name: "Unit test terraform modules"
- needs: [test-unit]
runs-on: ubuntu-latest
- timeout-minutes: 5
+ timeout-minutes: 10
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- name: Install Terraform
uses: hashicorp/setup-terraform@v3
with:
- terraform_version: 1.12.2
- - name: "run the tests"
+ terraform_version: ${{ inputs.terraform_version }}
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: ${{ inputs.golang_version }}
+ cache: false # Disable cache to avoid tar restore errors
+ - name: "Run module tests"
run: |
- cd tests/modules
- go test -v
+ # Find all module test directories and run tests
+ failed=0
+ for test_dir in $(find infrastructure/modules -type d -name "tests"); do
+ if ls "$test_dir"/*_test.go 1> /dev/null 2>&1; then
+ echo "=== Running tests in $test_dir ==="
+ cd "$test_dir"
+ go mod tidy
+ if ! go test -v ./...; then
+ failed=1
+ fi
+ cd - > /dev/null
+ fi
+ done
+ if [ $failed -eq 1 ]; then
+ exit 1
+ fi
perform-static-analysis:
name: "Perform static analysis"
- needs: [test-unit]
runs-on: ubuntu-latest
permissions:
id-token: write
diff --git a/.github/workflows/stage-4-acceptance.yaml b/.github/workflows/stage-4-acceptance.yaml
index d554f98a..f182af4d 100644
--- a/.github/workflows/stage-4-acceptance.yaml
+++ b/.github/workflows/stage-4-acceptance.yaml
@@ -27,6 +27,10 @@ on:
description: "Terraform version, set by the CI/CD pipeline workflow"
required: true
type: string
+ golang_version:
+ description: "Go version, set by the CI/CD pipeline workflow"
+ required: true
+ type: string
version:
description: "Version of the software, set by the CI/CD pipeline workflow"
required: true
diff --git a/.tool-versions b/.tool-versions
index b850c185..9b07693d 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,7 +1,10 @@
# This file is for you! Please, updated to the versions agreed by your team.
-terraform 1.9.2
-pre-commit 3.6.0
+terraform 1.13.0
+pre-commit 4.5.1
+tflint 0.60.0
+tfsec 1.28.13
+golang 1.22.5
# ==============================================================================
# The section below is reserved for Docker image versions.
diff --git a/infrastructure/modules/key-vault/tfdocs.md b/infrastructure/modules/key-vault/tfdocs.md
index 0bb170a1..16917d22 100644
--- a/infrastructure/modules/key-vault/tfdocs.md
+++ b/infrastructure/modules/key-vault/tfdocs.md
@@ -60,6 +60,62 @@ Type: `string`
The following input variables are optional (have default values):
+### [action\_group\_id](#input\_action\_group\_id)
+
+Description: The ID of the Action Group to use for alerts.
+
+Type: `string`
+
+Default: `null`
+
+### [certificate\_expired\_alert](#input\_certificate\_expired\_alert)
+
+Description: n/a
+
+Type:
+
+```hcl
+object({
+ evaluation_frequency = string
+ window_duration = string
+ threshold = number
+ })
+```
+
+Default:
+
+```json
+{
+ "evaluation_frequency": "PT15M",
+ "threshold": 1,
+ "window_duration": "PT1H"
+}
+```
+
+### [certificate\_near\_expiry\_alert](#input\_certificate\_near\_expiry\_alert)
+
+Description: n/a
+
+Type:
+
+```hcl
+object({
+ evaluation_frequency = string
+ window_duration = string
+ threshold = number
+ })
+```
+
+Default:
+
+```json
+{
+ "evaluation_frequency": "P1D",
+ "threshold": 1,
+ "window_duration": "P1D"
+}
+```
+
### [disk\_encryption](#input\_disk\_encryption)
Description: Should the disk encryption be enabled
@@ -68,6 +124,14 @@ Type: `bool`
Default: `true`
+### [enable\_alerting](#input\_enable\_alerting)
+
+Description: Whether monitoring and alerting is enabled for the Key Vault.
+
+Type: `bool`
+
+Default: `false`
+
### [enable\_rbac\_authorization](#input\_enable\_rbac\_authorization)
Description: n/a
@@ -108,6 +172,62 @@ Type: `list(string)`
Default: `[]`
+### [resource\_group\_name\_monitoring](#input\_resource\_group\_name\_monitoring)
+
+Description: The name of the resource group in which to create the Monitoring resources for the Key Vault. Changing this forces a new resource to be created.
+
+Type: `string`
+
+Default: `null`
+
+### [secret\_expired\_alert](#input\_secret\_expired\_alert)
+
+Description: n/a
+
+Type:
+
+```hcl
+object({
+ evaluation_frequency = string
+ window_duration = string
+ threshold = number
+ })
+```
+
+Default:
+
+```json
+{
+ "evaluation_frequency": "PT15M",
+ "threshold": 1,
+ "window_duration": "PT1H"
+}
+```
+
+### [secret\_near\_expiry\_alert](#input\_secret\_near\_expiry\_alert)
+
+Description: n/a
+
+Type:
+
+```hcl
+object({
+ evaluation_frequency = string
+ window_duration = string
+ threshold = number
+ })
+```
+
+Default:
+
+```json
+{
+ "evaluation_frequency": "P1D",
+ "threshold": 1,
+ "window_duration": "P1D"
+}
+```
+
### [sku\_name](#input\_sku\_name)
Description: Type of the Key Vault's SKU.
@@ -253,4 +373,8 @@ Description: n/a
The following resources are used by this module:
- [azurerm_key_vault.keyvault](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault) (resource)
+- [azurerm_monitor_scheduled_query_rules_alert_v2.kv_certificate_expired](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_scheduled_query_rules_alert_v2) (resource)
+- [azurerm_monitor_scheduled_query_rules_alert_v2.kv_certificate_near_expiry](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_scheduled_query_rules_alert_v2) (resource)
+- [azurerm_monitor_scheduled_query_rules_alert_v2.kv_secret_expired](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_scheduled_query_rules_alert_v2) (resource)
+- [azurerm_monitor_scheduled_query_rules_alert_v2.kv_secret_near_expiry](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_scheduled_query_rules_alert_v2) (resource)
- [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) (data source)
diff --git a/infrastructure/modules/managed-identity/README.md b/infrastructure/modules/managed-identity/README.md
new file mode 100644
index 00000000..5d6ffb8b
--- /dev/null
+++ b/infrastructure/modules/managed-identity/README.md
@@ -0,0 +1,109 @@
+# Managed Identity Module
+
+Creates an Azure User-Assigned Managed Identity.
+
+## Usage
+
+```hcl
+module "managed_identity" {
+ source = "../../modules/managed-identity"
+
+ resource_group_name = "rg-myapp-prod"
+ location = "uksouth"
+ uai_name = "mi-myapp-prod"
+
+ tags = {
+ environment = "production"
+ managed_by = "terraform"
+ }
+}
+```
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| terraform | >= 1.0 |
+| azurerm | >= 3.0 |
+
+## Inputs
+
+| Name | Description | Type | Required | Default |
+|------|-------------|------|----------|---------|
+| `resource_group_name` | The name of the resource group in which to create the Identity | `string` | Yes | - |
+| `location` | Azure region for the resource | `string` | Yes | - |
+| `uai_name` | The name of the user assigned identity (3-128 chars, alphanumeric, hyphens, underscores) | `string` | Yes | - |
+| `tags` | Resource tags to be applied | `map(string)` | No | `{}` |
+
+### Naming Constraints
+
+The `uai_name` must:
+- Be between 3 and 128 characters
+- Start with an alphanumeric character
+- End with an alphanumeric character or underscore
+- Contain only alphanumeric characters, hyphens, and underscores
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| `id` | The resource ID of the User Assigned Identity |
+| `name` | The name of the User Assigned Identity |
+| `principal_id` | The Principal ID (Object ID) for the Service Principal associated with this Identity |
+| `client_id` | The Client ID (Application ID) for the Service Principal associated with this Identity |
+
+## Example: Assigning to a Function App
+
+```hcl
+module "managed_identity" {
+ source = "../../modules/managed-identity"
+
+ resource_group_name = azurerm_resource_group.main.name
+ location = azurerm_resource_group.main.location
+ uai_name = "mi-funcapp-prod"
+}
+
+resource "azurerm_linux_function_app" "example" {
+ # ... other config ...
+
+ identity {
+ type = "UserAssigned"
+ identity_ids = [module.managed_identity.id]
+ }
+}
+```
+
+## Example: Granting Key Vault Access (RBAC)
+
+```hcl
+module "managed_identity" {
+ source = "../../modules/managed-identity"
+
+ resource_group_name = azurerm_resource_group.main.name
+ location = azurerm_resource_group.main.location
+ uai_name = "mi-myapp-prod"
+}
+
+resource "azurerm_role_assignment" "keyvault_secrets" {
+ scope = azurerm_key_vault.main.id
+ role_definition_name = "Key Vault Secrets User"
+ principal_id = module.managed_identity.principal_id
+}
+```
+
+## Testing
+
+This module includes automated tests using Terratest.
+
+```bash
+cd tests
+go test -v ./...
+```
+
+Tests validate:
+- Valid input configurations are accepted
+- Invalid identity names are rejected by validation rules
+
+## Documentation
+
+Auto-generated documentation is available in [tfdocs.md](./tfdocs.md).
diff --git a/infrastructure/modules/managed-identity/tests/go.mod b/infrastructure/modules/managed-identity/tests/go.mod
new file mode 100644
index 00000000..037948a6
--- /dev/null
+++ b/infrastructure/modules/managed-identity/tests/go.mod
@@ -0,0 +1,40 @@
+module managed-identity-tests
+
+go 1.25.6
+
+require (
+ github.com/gruntwork-io/terratest v0.55.0
+ github.com/stretchr/testify v1.11.1
+)
+
+require (
+ github.com/agext/levenshtein v1.2.3 // indirect
+ github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
+ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/hashicorp/errwrap v1.0.0 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-getter/v2 v2.2.3 // indirect
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
+ github.com/hashicorp/go-safetemp v1.0.0 // indirect
+ github.com/hashicorp/go-version v1.7.0 // indirect
+ github.com/hashicorp/hcl/v2 v2.22.0 // indirect
+ github.com/hashicorp/terraform-json v0.23.0 // indirect
+ github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect
+ github.com/klauspost/compress v1.16.5 // indirect
+ github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/go-testing-interface v1.14.1 // indirect
+ github.com/mitchellh/go-wordwrap v1.0.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/tmccombs/hcl2json v0.6.4 // indirect
+ github.com/ulikunitz/xz v0.5.10 // indirect
+ github.com/zclconf/go-cty v1.15.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
+ golang.org/x/mod v0.29.0 // indirect
+ golang.org/x/sync v0.18.0 // indirect
+ golang.org/x/sys v0.38.0 // indirect
+ golang.org/x/text v0.31.0 // indirect
+ golang.org/x/tools v0.38.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/infrastructure/modules/managed-identity/tests/go.sum b/infrastructure/modules/managed-identity/tests/go.sum
new file mode 100644
index 00000000..bca54390
--- /dev/null
+++ b/infrastructure/modules/managed-identity/tests/go.sum
@@ -0,0 +1,72 @@
+github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
+github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
+github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
+github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
+github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
+github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
+github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/gruntwork-io/terratest v0.55.0 h1:NgG6lm2dArdQ3KcOofw6PTfVRK1Flt7L3NNhFSBo72A=
+github.com/gruntwork-io/terratest v0.55.0/go.mod h1:OE0Jsc8Wn5kw/QySLbBd53g9Gt+xfDyDKChwRHwkKvI=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-getter/v2 v2.2.3 h1:6CVzhT0KJQHqd9b0pK3xSP0CM/Cv+bVhk+jcaRJ2pGk=
+github.com/hashicorp/go-getter/v2 v2.2.3/go.mod h1:hp5Yy0GMQvwWVUmwLs3ygivz1JSLI323hdIE9J9m7TY=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
+github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
+github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
+github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
+github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
+github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
+github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
+github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
+github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
+github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
+github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg=
+github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
+github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
+github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
+github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tmccombs/hcl2json v0.6.4 h1:/FWnzS9JCuyZ4MNwrG4vMrFrzRgsWEOVi+1AyYUVLGw=
+github.com/tmccombs/hcl2json v0.6.4/go.mod h1:+ppKlIW3H5nsAsZddXPy2iMyvld3SHxyjswOZhavRDk=
+github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
+github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
+github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
+github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
+github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/infrastructure/modules/managed-identity/tests/managed_identity_test.go b/infrastructure/modules/managed-identity/tests/managed_identity_test.go
new file mode 100644
index 00000000..b766e7e4
--- /dev/null
+++ b/infrastructure/modules/managed-identity/tests/managed_identity_test.go
@@ -0,0 +1,202 @@
+package test
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/gruntwork-io/terratest/modules/files"
+ "github.com/gruntwork-io/terratest/modules/terraform"
+ "github.com/stretchr/testify/assert"
+)
+
+func setupTestModule(t *testing.T, vars map[string]any) string {
+ moduleDir := "../"
+
+ tempDir, err := files.CopyTerraformFolderToTemp(moduleDir, t.Name())
+ if err != nil {
+ tempDir = t.TempDir()
+ copyTerraformFiles(t, moduleDir, tempDir)
+ }
+
+ // We only need to inject the provider block, as required_providers is now in versions.tf
+ providerContent := "provider \"azurerm\" {\nfeatures {}\n}\n"
+ providerPath := filepath.Join(tempDir, "provider_test.tf")
+ err = os.WriteFile(providerPath, []byte(providerContent), 0644)
+ if err != nil {
+ t.Fatalf("failed to write provider file: %v", err)
+ }
+
+ writeVarsFile(t, tempDir, vars)
+
+ return tempDir
+}
+
+func writeVarsFile(t *testing.T, dir string, vars map[string]any) {
+ var content string
+ for k, v := range vars {
+ switch val := v.(type) {
+ case string:
+ content += fmt.Sprintf("%s = %q\n", k, val)
+ case map[string]string:
+ content += fmt.Sprintf("%s = {\n", k)
+ for mk, mv := range val {
+ content += fmt.Sprintf(" %s = %q\n", mk, mv)
+ }
+ content += "}\n"
+ case nil:
+ content += fmt.Sprintf("%s = {}\n", k)
+ default:
+ content += fmt.Sprintf("%s = %v\n", k, val)
+ }
+ }
+
+ varsPath := filepath.Join(dir, "terraform.tfvars")
+ err := os.WriteFile(varsPath, []byte(content), 0644)
+ if err != nil {
+ t.Fatalf("failed to write tfvars file: %v", err)
+ }
+}
+
+func copyTerraformFiles(t *testing.T, src, dst string) {
+ entries, err := os.ReadDir(src)
+ if err != nil {
+ t.Fatalf("failed to read source directory: %v", err)
+ }
+
+ for _, entry := range entries {
+ if entry.IsDir() || filepath.Ext(entry.Name()) != ".tf" {
+ continue
+ }
+
+ srcPath := filepath.Join(src, entry.Name())
+ dstPath := filepath.Join(dst, entry.Name())
+
+ content, err := os.ReadFile(srcPath)
+ if err != nil {
+ t.Fatalf("failed to read file %s: %v", srcPath, err)
+ }
+
+ err = os.WriteFile(dstPath, content, 0644)
+ if err != nil {
+ t.Fatalf("failed to write file %s: %v", dstPath, err)
+ }
+ }
+}
+
+func TestManagedIdentity_ValidInputs(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ uaiName string
+ tags map[string]string
+ }{
+ {
+ name: "standard_name",
+ uaiName: "mi-test-identity",
+ tags: map[string]string{"environment": "test"},
+ },
+ {
+ name: "minimum_length_name",
+ uaiName: "abc",
+ tags: nil,
+ },
+ {
+ name: "name_with_underscores",
+ uaiName: "mi_test_identity",
+ tags: map[string]string{},
+ },
+ {
+ name: "name_with_mixed_separators",
+ uaiName: "mi-test_identity-01",
+ tags: map[string]string{"team": "platform", "cost_center": "12345"},
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ vars := map[string]any{
+ "resource_group_name": "rg-test",
+ "location": "uksouth",
+ "uai_name": tc.uaiName,
+ "tags": tc.tags,
+ }
+ tempDir := setupTestModule(t, vars)
+
+ terraformOptions := &terraform.Options{
+ TerraformDir: tempDir,
+ NoColor: true,
+ }
+
+ _, err := terraform.InitAndValidateE(t, terraformOptions)
+ assert.NoError(t, err, "Module should validate successfully with valid inputs")
+ })
+ }
+}
+
+// Variable validation rules are only evaluated during 'plan' or 'apply', not 'validate'.
+func TestManagedIdentity_InvalidName(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ uaiName string
+ description string
+ }{
+ {
+ name: "too_short_one_char",
+ uaiName: "a",
+ description: "single character should be rejected",
+ },
+ {
+ name: "too_short_two_chars",
+ uaiName: "ab",
+ description: "two characters should be rejected",
+ },
+ {
+ name: "starts_with_hyphen",
+ uaiName: "-mi-identity",
+ description: "name starting with hyphen should be rejected",
+ },
+ {
+ name: "starts_with_underscore",
+ uaiName: "_mi-identity",
+ description: "name starting with underscore should be rejected",
+ },
+ {
+ name: "contains_invalid_chars",
+ uaiName: "mi.test.identity",
+ description: "name containing dots should be rejected",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ vars := map[string]any{
+ "resource_group_name": "rg-test",
+ "location": "uksouth",
+ "uai_name": tc.uaiName,
+ }
+ tempDir := setupTestModule(t, vars)
+
+ terraformOptions := &terraform.Options{
+ TerraformDir: tempDir,
+ NoColor: true,
+ }
+
+ _, err := terraform.InitAndPlanE(t, terraformOptions)
+
+ assert.Error(t, err, tc.description)
+ if err != nil {
+ assert.Contains(t, err.Error(), "User-Assigned Managed Identity name",
+ "Error message should mention the identity name validation")
+ }
+ })
+ }
+}
diff --git a/infrastructure/modules/managed-identity/versions.tf b/infrastructure/modules/managed-identity/versions.tf
new file mode 100644
index 00000000..58dfc8e6
--- /dev/null
+++ b/infrastructure/modules/managed-identity/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = ">= 3.0"
+ }
+ }
+}
diff --git a/scripts/init.mk b/scripts/init.mk
index 373f8a4f..8df7e3f6 100644
--- a/scripts/init.mk
+++ b/scripts/init.mk
@@ -1,6 +1,6 @@
# WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead.
-include scripts/docker/docker.mk
+# include scripts/docker/docker.mk
include scripts/tests/test.mk
-include scripts/terraform/terraform.mk
diff --git a/scripts/terraform/terraform.mk b/scripts/terraform/terraform.mk
index 5cfc4b5c..f798d3cc 100644
--- a/scripts/terraform/terraform.mk
+++ b/scripts/terraform/terraform.mk
@@ -1,3 +1,4 @@
+
# This file is for you! Edit it to implement your own Terraform make targets.
# ==============================================================================
@@ -43,6 +44,95 @@ terraform-validate: # Validate Terraform configuration - optional: terraform_dir
dir=$(or ${terraform_dir}, ${dir}) \
opts=$(or ${terraform_opts}, ${opts})
+terraform-lint: # Lint Terraform modules using tflint - optional: module=[name of the module to lint, e.g. 'managed-identity'] @Quality
+ echo "Running TFLint..."
+ make _install-dependency name="tflint"
+ if [ -n "${module}" ]; then \
+ module_dir="infrastructure/modules/${module}"; \
+ if [ ! -d "$$module_dir" ]; then echo "Error: Module directory $$module_dir not found"; exit 1; fi; \
+ if ls "$$module_dir"/*.tf > /dev/null 2>&1; then \
+ echo "=== Linting $$module_dir ==="; \
+ tflint --init --chdir="$$module_dir" > /dev/null 2>&1 || true; \
+ tflint --chdir="$$module_dir" --format=compact; \
+ else \
+ echo "Skipping $$module_dir (no .tf files)"; \
+ fi; \
+ else \
+ total_issues=0; \
+ for module_dir in $$(find infrastructure/modules -mindepth 1 -maxdepth 1 -type d); do \
+ module_name=$$(basename "$$module_dir"); \
+ if ls "$$module_dir"/*.tf > /dev/null 2>&1; then \
+ echo "=== Linting $$module_dir ==="; \
+ tflint --init --chdir="$$module_dir" > /dev/null 2>&1 || true; \
+ output=$$(tflint --chdir="$$module_dir" --format=compact 2>&1 || true); \
+ issue_count=$$(echo "$$output" | grep -c ":" || echo "0"); \
+ if [ "$$issue_count" -gt 0 ] && [ -n "$$output" ]; then \
+ total_issues=$$((total_issues + issue_count)); \
+ echo "### ⚠️ $$module_name ($$issue_count issues)"; \
+ echo "$$output"; \
+ else \
+ echo "### ✅ $$module_name"; \
+ fi; \
+ echo ""; \
+ fi; \
+ done; \
+ echo "Total issues: $$total_issues"; \
+ if [ $$total_issues -gt 0 ]; then \
+ echo "> **Note:** TFLint issues are advisory only."; \
+ fi; \
+ fi
+
+terraform-security: # Run security scan using tfsec - optional: module=[name of the module to scan, e.g. 'managed-identity'] @Quality
+ echo "Running tfsec..."
+ make _install-dependency name="tfsec"
+ if [ -n "${module}" ]; then \
+ module_dir="infrastructure/modules/${module}"; \
+ if [ ! -d "$$module_dir" ]; then echo "Error: Module directory $$module_dir not found"; exit 1; fi; \
+ echo "=== Scanning $$module_dir ==="; \
+ tfsec "$$module_dir" --soft-fail; \
+ else \
+ tfsec infrastructure/ --soft-fail; \
+ fi
+
+terraform-static-analysis: # Run static analysis using SonarScanner @Quality
+ echo "Running Static Analysis..."
+ ./scripts/reports/perform-static-analysis.sh
+
+terraform-test-modules: # Run Go unit tests for Terraform modules - optional: module=[name of the module to test, e.g. 'managed-identity'] @Testing
+ echo "Running Module Tests..."
+ make _install-dependency name="golang"
+ failed=0
+ if [ -n "${module}" ]; then \
+ test_dir="infrastructure/modules/${module}/tests"; \
+ if [ ! -d "$$test_dir" ]; then echo "Error: Test directory $$test_dir not found"; exit 1; fi; \
+ echo "=== Running tests in $$test_dir ==="; \
+ cd "$$test_dir"; \
+ go mod tidy; \
+ if ! go test -v ./...; then failed=1; fi; \
+ else \
+ for test_dir in $$(find infrastructure/modules -type d -name "tests"); do \
+ if ls "$$test_dir"/*_test.go 1> /dev/null 2>&1; then \
+ echo "=== Running tests in $$test_dir ==="; \
+ cd "$$test_dir"; \
+ go mod tidy; \
+ if ! go test -v ./...; then failed=1; fi; \
+ cd - > /dev/null; \
+ fi; \
+ done; \
+ fi; \
+ if [ $$failed -eq 1 ]; then exit 1; fi
+
+terraform-test: # Run all Terraform quality checks and tests @Testing
+ make terraform-lint
+ make terraform-security
+ make terraform-test-modules
+
+terraform-install-tools: # Install all terraform related tools @Installation
+ make _install-dependency name="terraform"
+ make _install-dependency name="tflint"
+ make _install-dependency name="tfsec"
+ make _install-dependency name="golang"
+
clean:: # Remove Terraform files (terraform) - optional: terraform_dir|dir=[path to a directory where the command will be executed, relative to the project's top-level directory, default is one of the module variables or the example directory, if not set] @Operations
make _terraform cmd="clean" \
dir=$(or ${terraform_dir}, ${dir}) \
@@ -77,6 +167,12 @@ ${VERBOSE}.SILENT: \
terraform-fmt \
terraform-init \
terraform-install \
+ terraform-install-tools \
+ terraform-lint \
terraform-plan \
+ terraform-security \
terraform-shellscript-lint \
+ terraform-static-analysis \
+ terraform-test \
+ terraform-test-modules \
terraform-validate \
diff --git a/scripts/tests/lint.sh b/scripts/tests/lint.sh
new file mode 100755
index 00000000..989364b8
--- /dev/null
+++ b/scripts/tests/lint.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+set -euo pipefail
+
+# Delegate to the Terraform-specific lint target
+make terraform-lint
diff --git a/scripts/tests/security.sh b/scripts/tests/security.sh
new file mode 100755
index 00000000..4d1c6b51
--- /dev/null
+++ b/scripts/tests/security.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+set -euo pipefail
+
+# Delegate to the Terraform-specific security target
+make terraform-security
diff --git a/scripts/tests/test.mk b/scripts/tests/test.mk
index aab47c62..24fd2fb5 100644
--- a/scripts/tests/test.mk
+++ b/scripts/tests/test.mk
@@ -14,55 +14,14 @@ test-unit: # Run your unit tests from scripts/test/unit @Testing
test-lint: # Lint your code from scripts/test/lint @Testing
make _test name="lint"
-test-coverage: # Evaluate code coverage from scripts/test/coverage @Testing
- make _test name="coverage"
-
-test-accessibility: # Run your accessibility tests from scripts/test/accessibility @Testing
- make _test name="accessibility"
-
-test-contract: # Run your contract tests from scripts/test/contract @Testing
- make _test name="contract"
-
-test-integration: # Run your integration tests from scripts/test/integration @Testing
- make _test name="integration"
-
-test-load: # Run all your load tests @Testing
- make \
- test-capacity \
- test-soak \
- test-response-time
- # You may wish to add more here, depending on your app
-
-test-capacity: # Test what load level your app fails at from scripts/test/capacity @Testing
- make _test name="capacity"
-
-test-soak: # Test that resources don't get exhausted over time from scripts/test/soak @Testing
- make _test name="soak"
-
-test-response-time: # Test your API response times from scripts/test/response-time @Testing
- make _test name="response-time"
-
test-security: # Run your security tests from scripts/test/security @Testing
make _test name="security"
-test-ui: # Run your UI tests from scripts/test/ui @Testing
- make _test name="ui"
-
-test-ui-performance: # Run UI render tests from scripts/test/ui-performance @Testing
- make _test name="ui-performance"
-
test: # Run all the test tasks @Testing
make \
test-unit \
test-lint \
- test-coverage \
- test-contract \
- test-security \
- test-ui \
- test-ui-performance \
- test-integration \
- test-accessibility \
- test-load
+ test-security
_test:
set -e
@@ -76,16 +35,6 @@ _test:
${VERBOSE}.SILENT: \
_test \
test \
- test-accessibility \
- test-capacity \
- test-contract \
- test-coverage \
- test-soak \
- test-integration \
test-lint \
- test-load \
- test-response-time \
test-security \
- test-ui \
- test-ui-performance \
test-unit \
diff --git a/scripts/tests/unit.sh b/scripts/tests/unit.sh
index c589be5b..ee6f37e4 100755
--- a/scripts/tests/unit.sh
+++ b/scripts/tests/unit.sh
@@ -1,20 +1,5 @@
#!/bin/bash
-
set -euo pipefail
-cd "$(git rev-parse --show-toplevel)"
-
-# This file is for you! Edit it to call your unit test suite. Note that the same
-# file will be called if you run it locally as if you run it on CI.
-
-# Replace the following line with something like:
-#
-# rails test:unit
-# python manage.py test
-# npm run test
-#
-# or whatever is appropriate to your project. You should *only* run your fast
-# tests from here. If you want to run other test suites, see the predefined
-# tasks in scripts/test.mk.
-
-echo "Unit tests are not yet implemented. See scripts/tests/unit.sh for more."
+# Delegate to the Terraform-specific unit test target
+make terraform-test-modules