Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,30 @@ jobs:
run: |
tflint --init -c ${GITHUB_WORKSPACE}/.tflint.hcl --chdir "examples/${EXAMPLE_NAME}"
tflint -f compact -c ${GITHUB_WORKSPACE}/.tflint.hcl --var-file ${GITHUB_WORKSPACE}/.github/lint/tflint.tfvars --chdir "examples/${EXAMPLE_NAME}"

terraform_test:
name: Terraform test
strategy:
fail-fast: false
matrix:
module:
- modules/runners
defaults:
run:
working-directory: ${{ matrix.module }}
runs-on: ubuntu-latest
container:
image: hashicorp/terraform@sha256:1d10ec4073f4ddbdf34a28540a3b9250852ab500cb1c53f68c8bd17d82f474d8 # 1.14
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit

- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: terraform init
run: terraform init -backend=false -input=false
- name: terraform test
run: terraform test -test-directory=tests
2 changes: 1 addition & 1 deletion modules/runners/pool.tf
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module "pool" {
group_name = var.runner_group_name
name_prefix = var.runner_name_prefix
pool_owner = var.pool_runner_owner
role = var.iam_overrides["override_runner_role"] ? { arn = var.iam_overrides["runner_role_arn"] } : aws_iam_role.runner[0]
role = { arn = var.iam_overrides["override_runner_role"] ? var.iam_overrides["runner_role_arn"] : aws_iam_role.runner[0].arn }
use_dedicated_host = var.use_dedicated_host
}
subnet_ids = var.subnet_ids
Expand Down
72 changes: 72 additions & 0 deletions modules/runners/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Terraform Tests

This directory contains [Terraform test files](https://developer.hashicorp.com/terraform/language/tests) (`.tftest.hcl`) for the runners module.

## Why `terraform test` instead of `terraform validate`?

`terraform validate` only checks syntax and basic type correctness of the configuration. It **cannot** detect:

- Conditional expressions with inconsistent result types (e.g., one branch returns an object with 1 attribute, the other returns 16)
- Runtime type mismatches that only surface during `plan`
- Invalid cross-module references that depend on resource attribute shapes

`terraform test` with `mock_provider` runs a full plan without needing real cloud credentials, catching these classes of bugs in CI.

## Requirements

- Terraform >= 1.7 (for `mock_provider` and `mock_data` support)
- No AWS credentials required — all providers are mocked

## Running locally

```bash
cd modules/runners
terraform test -test-directory=tests
```

Expected output:

```
tests/pool.tftest.hcl... in progress
run "plan_with_pool_enabled"... pass
tests/pool.tftest.hcl... pass

Success! 1 passed, 0 failed.
```

## Writing new tests

1. Create a `.tftest.hcl` file in this directory
2. Use `mock_provider "aws" {}` to avoid needing credentials
3. Use `mock_data` blocks to provide realistic values for data sources that perform validation (e.g., `aws_iam_policy_document` validates JSON)
4. Set all required variables in a `variables {}` block
5. Use `run` blocks with `command = plan` and `assert` conditions

### Example template

```hcl
mock_provider "aws" {
mock_data "aws_iam_policy_document" {
defaults = {
json = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
}
}

variables {
# ... required variables ...
}

run "descriptive_test_name" {
command = plan

assert {
condition = <expression>
error_message = "Explanation of what failed"
}
}
```

## CI integration

These tests run automatically in the `terraform_test` job of `.github/workflows/terraform.yml` on every PR that touches `*.tf` or `*.hcl` files.
60 changes: 60 additions & 0 deletions modules/runners/tests/pool.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
mock_provider "aws" {
mock_data "aws_iam_policy_document" {
defaults = {
json = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
}
}

variables {
aws_region = "eu-west-1"
vpc_id = "vpc-12345678"
subnet_ids = ["subnet-12345678"]

instance_types = ["m5.large"]

s3_runner_binaries = {
arn = "arn:aws:s3:::my-bucket"
id = "my-bucket"
key = "runners/linux/actions-runner.tar.gz"
}

sqs_build_queue = {
arn = "arn:aws:sqs:eu-west-1:123456789012:build-queue"
url = "https://sqs.eu-west-1.amazonaws.com/123456789012/build-queue"
}

enable_organization_runners = true
enable_ssm_on_runners = true
runner_labels = ["self-hosted", "linux", "x64"]

# Use S3 bucket to avoid filebase64sha256 needing local zip files
lambda_s3_bucket = "my-lambda-bucket"
runners_lambda_s3_key = "runners.zip"

github_app_parameters = {
key_base64 = { name = "/github-runner/key-base64", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64" }
id = { name = "/github-runner/app-id", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id" }
}

ssm_paths = {
root = "/github-runner"
tokens = "tokens"
config = "config"
}

# Enable pool to exercise the pool module and its role type
pool_config = [{
schedule_expression = "cron(0 8 * * ? *)"
size = 1
}]
}

run "plan_with_pool_enabled" {
command = plan

assert {
condition = length(module.pool) == 1
error_message = "Pool module should be enabled when pool_config is non-empty"
}
}
Loading