Skip to content

Commit 70559d2

Browse files
authored
Add terraform test coverage for vpc + quilt modules (#116)
Plan-only, provider-mocked terraform tests for the vpc and quilt modules (no AWS credentials, no infrastructure), wired into CI via a new test job; Terraform bumped 1.5.0 -> 1.10.0. Test-only, no module behavior changes.
1 parent 984239c commit 70559d2

7 files changed

Lines changed: 408 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- uses: actions/checkout@v6
1616
- uses: hashicorp/setup-terraform@v4
1717
with:
18-
terraform_version: "1.5.0"
18+
terraform_version: "1.10.0"
1919
terraform_wrapper: false
2020
- run: terraform fmt -check -recursive -diff
2121

@@ -37,8 +37,29 @@ jobs:
3737
- uses: actions/checkout@v6
3838
- uses: hashicorp/setup-terraform@v4
3939
with:
40-
terraform_version: "1.5.0"
40+
terraform_version: "1.10.0"
4141
terraform_wrapper: false
4242
# -backend=false: validate needs init for provider/module schemas but no backend or cloud credentials.
4343
- run: terraform init -backend=false
4444
- run: terraform validate -no-color
45+
46+
test:
47+
runs-on: ubuntu-latest
48+
strategy:
49+
fail-fast: false
50+
matrix:
51+
dir:
52+
- modules/vpc
53+
- modules/quilt/tests/smoke
54+
defaults:
55+
run:
56+
working-directory: ${{ matrix.dir }}
57+
steps:
58+
- uses: actions/checkout@v6
59+
- uses: hashicorp/setup-terraform@v4
60+
with:
61+
terraform_version: "1.10.0"
62+
terraform_wrapper: false
63+
# The tests mock the AWS provider, so no backend or cloud credentials are needed.
64+
- run: terraform init -backend=false
65+
- run: terraform test -no-color

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.DS_Store
22
.terraform
3+
.terraform.lock.hcl
34
tfplan

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,18 @@ terraform fmt
917917
terraform validate
918918
```
919919

920+
## Test
921+
922+
Module tests are plan-only and mock the AWS provider, so they need no AWS
923+
credentials and create no infrastructure. Requires Terraform >= 1.7 (for
924+
`mock_provider`). Run from a module or test-wrapper directory, e.g.
925+
`modules/vpc` or `modules/quilt/tests/smoke`:
926+
927+
```
928+
terraform init -backend=false
929+
terraform test
930+
```
931+
920932
## Plan
921933
```
922934
terraform plan -out tfplan
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
AWSTemplateFormatVersion: "2010-09-09"
2+
Description: >-
3+
Test fixture for terraform test smoke runs. Stands in for the real Quilt
4+
CloudFormation template so plan-time references (template_file / filemd5)
5+
resolve. Not a deployable Quilt stack.
6+
Resources:
7+
Placeholder:
8+
Type: AWS::CloudFormation::WaitConditionHandle

modules/quilt/tests/smoke/main.tf

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Wrapper root for the `quilt` module smoke tests.
2+
#
3+
# The smoke tests run against this wrapper rather than modules/quilt directly
4+
# because the quilt module's `stack` output embeds sensitive values (DB + admin
5+
# passwords); as a root module under test that trips a sensitive-output error.
6+
# The wrapper re-exposes only the non-sensitive stack name.
7+
#
8+
# The required_providers block below is also load-bearing for the tests:
9+
# declaring the provider requirement at the root is what lets the test's
10+
# mock_provider engage for the whole module tree. Without a direct provider
11+
# reference at the root, the mock never attaches and the child modules fall
12+
# back to real AWS credentials.
13+
terraform {
14+
required_providers {
15+
aws = {
16+
source = "hashicorp/aws"
17+
# Match what the modules under test transitively require: the
18+
# terraform-aws-modules/vpc ~> 6.0 module needs aws >= 6.28. Pinning keeps
19+
# CI deterministic and off a future major. (Note: examples/main.tf and
20+
# modules/cnames still pin ~> 5.0, which is incompatible with that floor.)
21+
version = "~> 6.0"
22+
}
23+
}
24+
}
25+
26+
variable "create_new_vpc" {
27+
type = bool
28+
}
29+
30+
variable "internal" {
31+
type = bool
32+
default = false
33+
}
34+
35+
variable "vpc_id" {
36+
type = string
37+
default = null
38+
}
39+
40+
variable "api_endpoint" {
41+
type = string
42+
default = null
43+
}
44+
45+
variable "intra_subnets" {
46+
type = list(string)
47+
default = null
48+
}
49+
50+
variable "private_subnets" {
51+
type = list(string)
52+
default = null
53+
}
54+
55+
variable "public_subnets" {
56+
type = list(string)
57+
default = null
58+
}
59+
60+
variable "user_security_group" {
61+
type = string
62+
default = null
63+
}
64+
65+
variable "user_subnets" {
66+
type = list(string)
67+
default = null
68+
}
69+
70+
# New inputs added to the quilt module must be threaded through here, or the
71+
# smoke coverage silently narrows (the new input is never exercised).
72+
module "quilt" {
73+
source = "../../"
74+
75+
name = "quilt-test"
76+
parameters = {}
77+
template_file = "${path.module}/fixtures/quilt.yaml"
78+
79+
create_new_vpc = var.create_new_vpc
80+
internal = var.internal
81+
vpc_id = var.vpc_id
82+
api_endpoint = var.api_endpoint
83+
intra_subnets = var.intra_subnets
84+
private_subnets = var.private_subnets
85+
public_subnets = var.public_subnets
86+
user_security_group = var.user_security_group
87+
user_subnets = var.user_subnets
88+
}
89+
90+
# Re-expose ONLY the non-sensitive stack name. Do not output module.quilt.stack
91+
# (it embeds the DB URL + admin password) or any *_password value — a sensitive
92+
# root output makes `terraform test` fail, which is the whole reason this
93+
# wrapper exists.
94+
output "stack_name" {
95+
value = module.quilt.stack.name
96+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Smoke tests for the public `quilt` module, run against the wrapper root in
2+
# this directory (see main.tf).
3+
#
4+
# Plan-only with the AWS provider mocked: no credentials, no infrastructure.
5+
# Exercises the full wiring (vpc + db + search + the CloudFormation stack), so
6+
# a change that breaks the public boundary or the vpc pass-through fails in CI.
7+
#
8+
# Assertions reference known inputs (the stack name), not mocked computed
9+
# attributes, whose generated values are intentionally arbitrary.
10+
11+
mock_provider "aws" {
12+
# slice(..., 0, 2) and the cidrsubnet math in the vpc submodule need at
13+
# least two AZ names; mocked collections are otherwise empty.
14+
mock_data "aws_availability_zones" {
15+
defaults = {
16+
names = ["us-east-1a", "us-east-1b", "us-east-1c"]
17+
}
18+
}
19+
}
20+
21+
run "new_vpc_plans" {
22+
command = plan
23+
variables {
24+
create_new_vpc = true
25+
internal = false
26+
}
27+
assert {
28+
condition = output.stack_name == "quilt-test"
29+
error_message = "The CloudFormation stack must be named after var.name"
30+
}
31+
}
32+
33+
run "new_vpc_internal_plans" {
34+
command = plan
35+
variables {
36+
create_new_vpc = true
37+
internal = true
38+
}
39+
# internal = true exercises the most conditional wiring in quilt/main.tf:
40+
# the PublicSubnets/UserSubnets null-coalescing and the internal-gated api
41+
# endpoint.
42+
assert {
43+
condition = output.stack_name == "quilt-test"
44+
error_message = "The CloudFormation stack must be named after var.name"
45+
}
46+
}
47+
48+
run "existing_vpc_plans" {
49+
command = plan
50+
variables {
51+
create_new_vpc = false
52+
internal = false
53+
vpc_id = "vpc-00000000000000000"
54+
intra_subnets = ["subnet-intra-a", "subnet-intra-b"]
55+
private_subnets = ["subnet-priv-a", "subnet-priv-b"]
56+
public_subnets = ["subnet-pub-a", "subnet-pub-b"]
57+
user_security_group = "sg-00000000000000000"
58+
}
59+
assert {
60+
condition = output.stack_name == "quilt-test"
61+
error_message = "The CloudFormation stack must be named after var.name"
62+
}
63+
}
64+
65+
run "existing_vpc_internal_plans" {
66+
command = plan
67+
variables {
68+
create_new_vpc = false
69+
internal = true
70+
vpc_id = "vpc-00000000000000000"
71+
api_endpoint = "vpce-00000000000000000"
72+
intra_subnets = ["subnet-intra-a", "subnet-intra-b"]
73+
private_subnets = ["subnet-priv-a", "subnet-priv-b"]
74+
public_subnets = null
75+
user_security_group = "sg-00000000000000000"
76+
user_subnets = ["subnet-user-a", "subnet-user-b"]
77+
}
78+
# internal = true on the existing-VPC path exercises quilt's pass-through of
79+
# api_endpoint + user_subnets and the internal-gated CFN wiring.
80+
assert {
81+
condition = output.stack_name == "quilt-test"
82+
error_message = "The CloudFormation stack must be named after var.name"
83+
}
84+
}

0 commit comments

Comments
 (0)