Skip to content

Commit

Permalink
test: Add terraform test GitHub Action (and make tests pass) (#254)
Browse files Browse the repository at this point in the history
  • Loading branch information
WillGibson authored Oct 28, 2024
1 parent 13e0572 commit 9b5de3f
Show file tree
Hide file tree
Showing 20 changed files with 351 additions and 214 deletions.
62 changes: 62 additions & 0 deletions .github/workflows/terraform-unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Terraform Unit Tests

on:
push:
schedule:
- cron: '30 5 * * *'

permissions: read-all

jobs:
find-modules-to-test:
runs-on: ubuntu-latest

outputs:
modules: ${{ steps.find-modules.outputs.modules }}

steps:
- uses: actions/checkout@v4
- name: Find modules
id: find-modules
run: |
unit_test_files=$(find . -name "*tftest.hcl" | grep -v e2e-tests | sort)
modules=""
IFS=$'\n'
for file in $unit_test_files
do
# Lose leading ./ and select the part before the tests directory
module=$(echo "${file#./}" | awk -F "/tests/" '{print $1}')
# In case we separate the test files, only include each module once
if [[ "${modules}" != *"${module}"* ]]; then
echo "Found module ${module}"
modules+="\"${module}\","
fi
done
echo "modules=[${modules%,}]" >> "$GITHUB_OUTPUT"
terraform-unit-tests:
name: ${{ matrix.module }} tf-${{ matrix.terraform-version }}
runs-on: ubuntu-latest
needs: find-modules-to-test
strategy:
matrix:
terraform-version:
- 1.8
- 1.9
module: ${{ fromJSON(needs.find-modules-to-test.outputs.modules) }}
fail-fast: false

steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ matrix.terraform-version }}
- run: terraform --version
- name: terraform init
run: |
cd ${{ matrix.module }}
terraform init
- name: terraform test
run: |
cd ${{ matrix.module }}
terraform test
29 changes: 15 additions & 14 deletions application-load-balancer/tests/unit.tftest.hcl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mock_provider "aws" {}

mock_provider "aws" {
alias = "sandbox"
}
Expand Down Expand Up @@ -40,8 +42,11 @@ variables {
environment = "env"
vpc_name = "vpc-name"
config = {
domain_prefix = "dom-prefix",
cdn_domains_list = { "dev.my-application.uktrade.digital" : ["internal", "my-application.uktrade.digital"] }
domain_prefix = "dom-prefix",
cdn_domains_list = {
"web.dev.my-application.uktrade.digital" : ["internal.web", "my-application.uktrade.digital"]
"api.dev.my-application.uktrade.digital" : ["internal.api", "my-application.uktrade.digital"]
}
}
}

Expand Down Expand Up @@ -131,10 +136,8 @@ run "aws_security_group_http_unit_test" {
error_message = "Should be: app-env-alb-http"
}

assert {
condition = aws_security_group.alb-security-group["http"].revoke_rules_on_delete == false
error_message = "Should be: false"
}
# Cannot test for the default on a plan
# aws_security_group.alb-security-group["http"].revoke_rules_on_delete == false

assert {
condition = aws_security_group.alb-security-group["http"].vpc_id == "vpc-00112233aabbccdef"
Expand All @@ -150,10 +153,8 @@ run "aws_security_group_https_unit_test" {
error_message = "Should be: app-env-alb-https"
}

assert {
condition = aws_security_group.alb-security-group["https"].revoke_rules_on_delete == false
error_message = "Should be: false"
}
# Cannot test for the default on a plan
# aws_security_group.alb-security-group["https"].revoke_rules_on_delete == false

assert {
condition = aws_security_group.alb-security-group["https"].vpc_id == "vpc-00112233aabbccdef"
Expand Down Expand Up @@ -204,13 +205,13 @@ run "aws_acm_certificate_unit_test" {
}

assert {
condition = [for el in aws_acm_certificate.certificate.subject_alternative_names : true if el == "dev.my-application.uktrade.digital"][0] == true
error_message = "Should be: either: dev.my-application.uktrade.digital or dom-prefix.env.app.uktrade.digital"
condition = [for el in aws_acm_certificate.certificate.subject_alternative_names : true if el == "web.dev.my-application.uktrade.digital"][0] == true
error_message = "Should be: web.dev.my-application.uktrade.digital"
}

assert {
condition = [for el in aws_acm_certificate.certificate.subject_alternative_names : true if el == "dom-prefix.env.app.uktrade.digital"][0] == true
error_message = "Should be: either: dev.my-application.uktrade.digital or dom-prefix.env.app.uktrade.digital"
condition = [for el in aws_acm_certificate.certificate.subject_alternative_names : true if el == "api.dev.my-application.uktrade.digital"][0] == true
error_message = "Should be: api.dev.my-application.uktrade.digital"
}

assert {
Expand Down
4 changes: 2 additions & 2 deletions data-migration/main.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
resource "aws_iam_role" "s3_migration_role" {
name = local.role_name
assume_role_policy = data.aws_iam_policy_document.allow_assume_role.json
assume_role_policy = jsonencode(data.aws_iam_policy_document.allow_assume_role)
}

data "aws_iam_policy_document" "allow_assume_role" {
Expand Down Expand Up @@ -89,5 +89,5 @@ data "aws_iam_policy_document" "s3_migration_policy_document" {
resource "aws_iam_role_policy" "s3_migration_policy" {
name = local.policy_name
role = aws_iam_role.s3_migration_role.name
policy = data.aws_iam_policy_document.s3_migration_policy_document.json
policy = jsonencode(data.aws_iam_policy_document.s3_migration_policy_document)
}
2 changes: 2 additions & 0 deletions data-migration/tests/unit.tftest.hcl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mock_provider "aws" {}

variables {
config = {
"source_bucket_arn" = "test-source-bucket-arn"
Expand Down
2 changes: 1 addition & 1 deletion elasticache-redis/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ resource "aws_kms_key" "ssm_redis_endpoint" {

resource "aws_iam_role" "conduit_ecs_task_role" {
name = "${var.name}-${var.application}-${var.environment}-conduitEcsTask"
assume_role_policy = data.aws_iam_policy_document.assume_ecstask_role.json
assume_role_policy = jsonencode(data.aws_iam_policy_document.assume_ecstask_role)

inline_policy {
name = "AllowReadingofCMKSecrets"
Expand Down
64 changes: 38 additions & 26 deletions elasticache-redis/tests/unit.tftest.hcl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mock_provider "aws" {}

variables {
vpc_name = "sandbox-elasticache-redis"
application = "test-application"
Expand All @@ -16,6 +18,13 @@ variables {
}
}

override_data {
target = data.aws_caller_identity.current
values = {
account_id = "001122334455"
}
}

override_data {
target = data.aws_vpc.vpc
values = {
Expand Down Expand Up @@ -97,10 +106,8 @@ run "aws_elasticache_replication_group_unit_test" {
error_message = "Invalid config for aws_elasticache_replication_group multi_az_enabled"
}

assert {
condition = aws_elasticache_replication_group.redis.auth_token_update_strategy == "ROTATE"
error_message = "Invalid config for aws_elasticache_replication_group auth_token_update_strategy"
}
# Cannot test for the default on a plan
# aws_elasticache_replication_group.redis.auth_token_update_strategy == "ROTATE"

assert {
condition = [for el in aws_elasticache_replication_group.redis.log_delivery_configuration : el.destination if el.log_type == "engine-log"][0] == "/aws/elasticache/test-redis/test-environment/test-redisRedis/engine"
Expand Down Expand Up @@ -189,10 +196,8 @@ run "aws_security_group_unit_test" {
error_message = "Invalid config for aws_security_group name"
}

assert {
condition = aws_security_group.redis.revoke_rules_on_delete == false
error_message = "Invalid config for aws_security_group revoke_rules_on_delete"
}
# Cannot test for the default on a plan
# aws_security_group.redis.revoke_rules_on_delete == false

assert {
condition = jsonencode(aws_security_group.redis.tags) == jsonencode(var.expected_tags)
Expand Down Expand Up @@ -318,10 +323,8 @@ run "aws_cloudwatch_log_group_unit_test" {
error_message = "Invalid config for aws_cloudwatch_log_group retention_in_days"
}

assert {
condition = aws_cloudwatch_log_group.redis-slow-log-group.skip_destroy == false
error_message = "Invalid config for aws_cloudwatch_log_group skip_destroy"
}
# Cannot test for the default on a plan
# aws_cloudwatch_log_group.redis-slow-log-group.skip_destroy == false

### Test aws_cloudwatch_log_group engine resource ###
assert {
Expand All @@ -334,10 +337,8 @@ run "aws_cloudwatch_log_group_unit_test" {
error_message = "Invalid config for aws_cloudwatch_log_group retention_in_days"
}

assert {
condition = aws_cloudwatch_log_group.redis-engine-log-group.skip_destroy == false
error_message = "Invalid config for aws_cloudwatch_log_group skip_destroy"
}
# Cannot test for the default on a plan
# aws_cloudwatch_log_group.redis-engine-log-group.skip_destroy == false

assert {
condition = jsonencode(aws_cloudwatch_log_group.redis-engine-log-group.tags) == jsonencode(var.expected_tags)
Expand All @@ -359,10 +360,8 @@ run "aws_cloudwatch_log_subscription_filter_unit_test" {
error_message = "Invalid config for aws_cloudwatch_log_subscription_filter destination_arn"
}

assert {
condition = aws_cloudwatch_log_subscription_filter.redis-subscription-filter-engine.distribution == "ByLogStream"
error_message = "Invalid config for aws_cloudwatch_log_subscription_filter distribution"
}
# Cannot test for the default on a plan
# aws_cloudwatch_log_subscription_filter.redis-subscription-filter-engine.distribution == "ByLogStream"

assert {
condition = aws_cloudwatch_log_subscription_filter.redis-subscription-filter-engine.role_arn == "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/CWLtoSubscriptionFilterRole"
Expand All @@ -380,10 +379,8 @@ run "aws_cloudwatch_log_subscription_filter_unit_test" {
error_message = "Invalid config for aws_cloudwatch_log_subscription_filter destination_arn"
}

assert {
condition = aws_cloudwatch_log_subscription_filter.redis-subscription-filter-slow.distribution == "ByLogStream"
error_message = "Invalid config for aws_cloudwatch_log_subscription_filter distribution"
}
# Cannot test for the default on a plan
# aws_cloudwatch_log_subscription_filter.redis-subscription-filter-slow.distribution == "ByLogStream"

assert {
condition = aws_cloudwatch_log_subscription_filter.redis-subscription-filter-slow.role_arn == "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/CWLtoSubscriptionFilterRole"
Expand Down Expand Up @@ -418,7 +415,22 @@ run "test_create_conduit_iam_role" {
}

assert {
condition = aws_iam_role.conduit_ecs_task_role.assume_role_policy == "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ecs-tasks.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}"
error_message = "Should be: \"{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ecs-tasks.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}\""
condition = jsondecode(aws_iam_role.conduit_ecs_task_role.assume_role_policy).statement[0].actions[0] == "sts:AssumeRole"
error_message = "Should be: sts:AssumeRole"
}

assert {
condition = jsondecode(aws_iam_role.conduit_ecs_task_role.assume_role_policy).statement[0].effect == "Allow"
error_message = "Should be: Allow"
}

assert {
condition = [
for el in jsondecode(aws_iam_role.conduit_ecs_task_role.assume_role_policy).statement[0].principals :
true if el.type == "Service" && [
for identifier in el.identifiers : true if identifier == "ecs-tasks.amazonaws.com"
][0] == true
][0] == true
error_message = "Should be: Service ecs-tasks.amazonaws.com"
}
}
26 changes: 20 additions & 6 deletions extensions/tests/unit.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ mock_provider "aws" {
alias = "domain-cdn"
}

mock_provider "aws" {}

override_data {
target = module.opensearch["test-opensearch"].data.aws_caller_identity.current
values = {
account_id = "001122334455"
}
}

override_data {
target = module.opensearch["test-opensearch"].data.aws_ssm_parameter.log-destination-arn
values = {
value = "{\"dev\":\"arn:aws:logs:eu-west-2:763451185160:log-group:/copilot/tools/central_log_groups_dev\",\"prod\":\"arn:aws:logs:eu-west-2:763451185160:log-group:/copilot/tools/central_log_groups_prod\"}"
}
}

override_data {
target = module.opensearch["test-opensearch"].data.aws_vpc.vpc
values = {
Expand All @@ -61,41 +77,39 @@ override_data {
run "aws_ssm_parameter_unit_test" {
command = plan

# Configuration
assert {
condition = aws_ssm_parameter.addons.name == "/copilot/applications/test-application/environments/test-environment/addons"
error_message = "Invalid config for aws_ssm_parameter name"
}

assert {
condition = aws_ssm_parameter.addons.tier == "Intelligent-Tiering"
error_message = "Intelligent-Tiering not enabled, parameters > 4096 characters will be rejected"
}

assert {
condition = aws_ssm_parameter.addons.type == "String"
error_message = "Invalid config for aws_ssm_parameter type"
}

# Value

# Tags
assert {
condition = aws_ssm_parameter.addons.tags["application"] == "test-application"
error_message = ""
}

assert {
condition = aws_ssm_parameter.addons.tags["copilot-application"] == "test-application"
error_message = ""
}

assert {
condition = aws_ssm_parameter.addons.tags["environment"] == "test-environment"
error_message = ""
}

assert {
condition = aws_ssm_parameter.addons.tags["copilot-environment"] == "test-environment"
error_message = ""
}

assert {
condition = aws_ssm_parameter.addons.tags["managed-by"] == "DBT Platform - Terraform"
error_message = ""
Expand Down
2 changes: 1 addition & 1 deletion logs/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,6 @@ data "aws_iam_policy_document" "log-resource-policy" {
}

resource "aws_cloudwatch_log_resource_policy" "log-resource-policy" {
policy_document = data.aws_iam_policy_document.log-resource-policy.json
policy_document = jsonencode(data.aws_iam_policy_document.log-resource-policy)
policy_name = "${var.name_prefix}-LogResourcePolicy"
}
Loading

0 comments on commit 9b5de3f

Please sign in to comment.