Skip to content
Merged
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ include backend/Makefile
include cspell/Makefile
include docs/Makefile
include frontend/Makefile
include infrastructure/Makefile

MAKEFLAGS += --no-print-directory

Expand Down
8 changes: 8 additions & 0 deletions infrastructure/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
test-infrastructure:
@for dir in infrastructure/modules/*/; do \
if [ -d "$$dir/tests" ]; then \
echo "Testing $$dir..."; \
terraform -chdir="$$dir" init -backend=false -input=false && \
terraform -chdir="$$dir" test || exit 1; \
fi \
done
45 changes: 45 additions & 0 deletions infrastructure/modules/cache/.terraform.lock.hcl
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always avoided committing lock files inside modules, as they served no purpose since the main (staging or production) lock file would take precedence.
Committing them seems to cause no problems but improves test speed (terraform init is faster).

https://discuss.hashicorp.com/t/what-is-the-best-practise-with-terraform-lock-hcl-in-modules-commit-or-not/28648

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it seems it's fine to have them in git

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 129 additions & 0 deletions infrastructure/modules/cache/tests/cache.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
variables {
common_tags = { Environment = "test", Project = "nest" }
environment = "test"
log_retention_in_days = 30
project_name = "nest"
redis_engine_version = "7.0"
redis_node_type = "cache.t3.micro"
redis_num_cache_nodes = 1
redis_port = 6379
security_group_ids = ["sg-12345678"]
subnet_ids = ["subnet-12345678"]
}

run "test_auth_token_generated" {
command = plan

assert {
condition = length(random_password.redis_auth_token) == 1
error_message = "Redis auth token must be generated."
}
}

run "test_auth_token_length" {
command = plan

assert {
condition = random_password.redis_auth_token[0].length == 32
error_message = "Redis auth token must be 32 characters."
}
}

run "test_encryption_enabled_at_rest" {
command = plan

assert {
condition = aws_elasticache_replication_group.main.at_rest_encryption_enabled
error_message = "At-rest encryption must be enabled."
}
}

run "test_encryption_enabled_in_transit" {
command = plan

assert {
condition = aws_elasticache_replication_group.main.transit_encryption_enabled == true
error_message = "Transit encryption must be enabled."
}
}

run "test_engine_is_redis" {
command = plan

assert {
condition = aws_elasticache_replication_group.main.engine == "redis"
error_message = "Engine must be Redis."
}
}

run "test_failover_disabled_for_single_node" {
command = plan

assert {
condition = aws_elasticache_replication_group.main.automatic_failover_enabled == false
error_message = "Automatic failover must be disabled for single-node clusters."
}
}

run "test_failover_enabled_for_multi_node" {
command = plan

variables {
redis_num_cache_nodes = 2
}

assert {
condition = aws_elasticache_replication_group.main.automatic_failover_enabled == true
error_message = "Automatic failover must be enabled for multi-node clusters."
}
}

run "test_log_groups_created" {
command = plan

assert {
condition = aws_cloudwatch_log_group.engine_log.retention_in_days == var.log_retention_in_days
error_message = "Engine log group must be created with correct retention."
}

assert {
condition = aws_cloudwatch_log_group.slow_log.retention_in_days == var.log_retention_in_days
error_message = "Slow log group must be created with correct retention."
}
}

run "test_replication_group_id_format" {
command = plan

assert {
condition = aws_elasticache_replication_group.main.replication_group_id == "${var.project_name}-${var.environment}-cache"
error_message = "Replication group ID must follow naming convention: {project}-{environment}-cache."
}
}

run "test_ssm_parameter_is_secure_string" {
command = plan

assert {
condition = aws_ssm_parameter.django_redis_password.type == "SecureString"
error_message = "Redis password must be stored as SecureString."
}
}

run "test_ssm_parameter_path_format" {
command = plan

assert {
condition = aws_ssm_parameter.django_redis_password.name == "/${var.project_name}/${var.environment}/DJANGO_REDIS_PASSWORD"
error_message = "SSM parameter must follow path: /{project}/{environment}/DJANGO_REDIS_PASSWORD."
}
}

run "test_subnet_group_uses_provided_subnets" {
command = plan

assert {
condition = toset(aws_elasticache_subnet_group.main.subnet_ids) == toset(var.subnet_ids)
error_message = "Subnet group must use the provided subnet IDs."
}
}
30 changes: 30 additions & 0 deletions infrastructure/modules/cache/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,52 @@ variable "redis_engine_version" {
variable "redis_node_type" {
description = "The node type for the Redis cache."
type = string

validation {
condition = can(regex("^cache\\.", var.redis_node_type))
error_message = "redis_node_type must start with 'cache.' (e.g., cache.t3.micro, cache.r5.large)."
}
}

variable "redis_num_cache_nodes" {
description = "The number of cache nodes in the Redis cluster."
type = number

validation {
condition = var.redis_num_cache_nodes >= 1
error_message = "redis_num_cache_nodes must be at least 1."
}
}

variable "redis_port" {
description = "The port for the Redis cache."
type = number

validation {
condition = var.redis_port > 0 && var.redis_port < 65536
error_message = "redis_port must be between 1 and 65535."
}
}

variable "security_group_ids" {
description = "A list of security group IDs to associate with the Redis cache."
type = list(string)

validation {
condition = length(var.security_group_ids) > 0
error_message = "security_group_ids must contain at least one security group."
}
}

variable "snapshot_retention_limit" {
description = "The number of days for which automatic snapshots are retained."
type = number
default = 5

validation {
condition = var.snapshot_retention_limit >= 0 && var.snapshot_retention_limit <= 30
error_message = "snapshot_retention_limit must be between 0 and 30."
}
}

variable "snapshot_window" {
Expand All @@ -72,4 +97,9 @@ variable "snapshot_window" {
variable "subnet_ids" {
description = "A list of subnet IDs for the cache subnet group."
type = list(string)

validation {
condition = length(var.subnet_ids) > 0
error_message = "subnet_ids must contain at least one subnet."
}
}
45 changes: 45 additions & 0 deletions infrastructure/modules/database/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions infrastructure/modules/database/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ resource "aws_db_proxy" "main" {
})
vpc_security_group_ids = var.proxy_security_group_ids
vpc_subnet_ids = var.db_subnet_ids

lifecycle {
precondition {
condition = length(var.proxy_security_group_ids) > 0
error_message = "proxy_security_group_ids must be provided when create_rds_proxy is true."
}
}
}

resource "aws_db_proxy_default_target_group" "main" {
Expand Down
Loading
Loading