Skip to content

Instantly share code, notes, and snippets.

@AlexAtkinson
Last active December 24, 2023 12:16
Show Gist options
  • Save AlexAtkinson/11f2a22511b09b0c241d59e9ebfe66ec to your computer and use it in GitHub Desktop.
Save AlexAtkinson/11f2a22511b09b0c241d59e9ebfe66ec to your computer and use it in GitHub Desktop.
Terraform Variables - Ensure default values are changed, plus some other validations
# A collection of variable validation examples for Terraform.
#
# Test with:
# tf plan -var="environment=Development" -var="aws_region=us-east-2" -var="rds_password=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzAZaz98><" -var="subnet_public_a=10.222.16.0/20" -var="subnet_database_a=10.10.10.0/28" -var="application=testing" -var="orchestration=linktourl" -var="department=department_foo" -var="company=coolcorp"
variable "company" {
description = "The name of the company."
type = string
nullable = false
default = "CHANGEME"
validation {
condition = var.company != "CHANGEME" && lower(var.company) == var.company && !can(regex(" ", var.company))
error_message = "Must be lowercase and not contain spaces."
}
}
variable "environment" {
description = "The execution environment: [DevOps|Development|QA|Staging|Production]"
type = string
default = "CHANGEME"
nullable = false
validation {
condition = var.environment != "CHANGEME" && contains(["DevOps", "Development", "QA", "Staging", "Production"], var.environment) && !can(regex(" ", var.environment))
error_message = "The variable 'environment' must match the long form references as defined here: <link to docks>"
}
}
variable "department" {
description = "The department responsible for the resource. (lowercase)"
type = string
nullable = false
default = "CHANGEME"
validation {
condition = var.department != "CHANGEME" && lower(var.department) == var.department && !can(regex(" ", var.department))
error_message = "Must be lowercase and not contain spaces."
}
}
variable "application" {
description = "The application this resouce is is supporting. (lowercase)"
type = string
nullable = false
default = "CHANGEME"
validation {
condition = var.application != "CHANGEME" && lower(var.application) == var.application && !can(regex(" ", var.application))
error_message = "Must be lowercase and not contain spaces."
}
}
variable "orchestration" {
description = "The repository for this infrastructure (IaC)"
type = string
nullable = false
default = "CHANGEME"
validation {
condition = var.orchestration != "CHANGEME" && lower(var.orchestration) == var.orchestration && !can(regex(" ", var.orchestration))
error_message = "Must be lowercase and not contain spaces."
}
}
variable "aws_region" {
description = "The region in which to execute."
type = string
nullable = false
default = "CHANGEME"
validation {
condition = (
var.aws_region != "CHANGEME" &&
contains(["us-west-1", "us-east-2", "eu-west-1", "eu-west-3"], var.aws_region) &&
!can(regex(" ", var.aws_region))
)
error_message = "The variable 'aws_region' must be one of the operating regions as defined here: <link to docs>"
}
}
# Between two numbers
variable "alb_tg_traffic_port" {
description = <<EOT
The target group protocol.
EOT
type = number
nullable = false
default = 443
validation {
condition = (
var.alb_tg_traffic_port >= 0 &&
var.alb_tg_traffic_port <= 65535
)
error_message = "Must be a valid port number."
}
}
# Matches one of a specified set of numbers.
variable "ecs_task_cpu_allocation" {
description = <<EOT
The cpu units for the task.
EOT
type = number
nullable = false
default = 256
validation {
condition = (
var.ecs_task_cpu_allocation == 256 ||
var.ecs_task_cpu_allocation == 512 ||
var.ecs_task_cpu_allocation == 1024 ||
var.ecs_task_cpu_allocation == 2048 ||
var.ecs_task_cpu_allocation == 4096 ||
var.ecs_task_cpu_allocation == 8192 ||
var.ecs_task_cpu_allocation == 16384
)
error_message = "Must be a valid task size! See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#task_size"
}
}
# Use modulo operator to see if number is divisable by to simplify some validations...
# This is valid for ECS memory allocation
variable "n" {
description = <<EOT
The cpu units for the task.
EOT
type = number
nullable = false
validation {
condition = (
var.n == 512 ||
var.n >= 1024 && try(var.n % 1024 == 0) && var.n <= 30720
)
error_message = "Must be a valid task size!"
}
}
# Validate HTTP Response Codes
variable "alb_tg_healthcheck_matcher" {
description = <<EOT
The target group healthcheck valid response code(s).
May be a comma separated list. Ranges are valid. Eg: 200,201,400-499.
REFs:
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group#health_check
- https://httpwg.org/specs/rfc9110.html#overview.of.status.codes
EOT
type = string
nullable = false
default = "200"
validation {
condition = (
!can(regex("[[:space:]]", var.alb_tg_healthcheck_matcher)) &&
!can(regex("[[:alpha:]]+", var.alb_tg_healthcheck_matcher))
)
error_message = "Variable may only contain numbers and commas."
}
validation {
condition = (
alltrue([
for i in regexall("[[:digit:]]+", var.alb_tg_healthcheck_matcher) :
tonumber(i) >= 100 &&
tonumber(i) <= 599
])
)
error_message = "HTTP Response codes must be between 100 and 599. See: https://httpwg.org/specs/rfc9110.html#overview.of.status.codes"
}
validation {
condition = (
length(split(",", var.alb_tg_healthcheck_matcher)) == length(distinct(split(",", var.alb_tg_healthcheck_matcher)))
)
error_message = "Duplicate response codes detected. Entries must be distcint!"
}
}
# String as boolien validatidation.
# Useful for satisfying templating scenarios.
variable "bool" {
description = "Enter a bool."
type = string
nullable = false
default = "CHANGEME"
validation {
condition = (
var.bool != "CHANGEME" &&
var.bool == lower(var.bool) &&
contains(["true", "false"], var.bool) &&
!can(regex(" ", var.bool))
)
error_message = "The variable 'bool' must be a valid bool when converted from a string."
}
}
# This is an outmoded approach. TF has solid bool validation built in now.
variable "real_bool" {
type = bool
description = "DANGER!!! SET TO FALSE ONLY."
default = false
validation {
condition = (
can(regex("^([t][r][u][e]|[f][a][l][s][e])$",var.real_bool)) &&
!can(regex(" ", var.real_bool))
)
error_message = "Bool must be true or false."
}
}
# Compound Key Map lookup based on two or more variables/values.
# NOTE: locals do not support such map lookup mechanisms.
# WARN: everything assembled like this will have GLOBAL scope.
variable "compound_lookup_map" {
default = {
Development_us-east-2 = "dev_us-east-2_value"
Development_us-west-1 = "dev_us-west-1_value"
Production_us-east-2 = "prod_us-east-2_value"
Production_us-west-1 = "prod_us-west-1_value"
}
}
output compound_lookup_result {
value = var.compound_lookup_map["${var.environment}_${var.aws_region}"]
}
# NOTE: Terraform Regex DOES NOT support lookarounds, lookaheads, or quantifiers... AFAICT
variable "rds_password" {
description = "The rds password."
type = string
nullable = false
default = "CHANGEME"
validation {
condition = (
length(var.rds_password) >= 18 &&
length(var.rds_password) <= 64
)
error_message = "RDS password must be between 18 and 64 characters long."
}
validation {
condition = length(regexall("[a-z]", var.rds_password)) >= 2
error_message = "RDS password must contain at least 2 lower case letters!"
}
validation {
condition = length(regexall("[A-Z]", var.rds_password)) >= 2
error_message = "RDS password must contain at least 2 upper case letters!"
}
validation {
condition = length(regexall("[0-9]", var.rds_password)) >= 2
error_message = "RDS password must contain at least 2 digits!"
}
validation {
condition = length(regexall("[!#$%^&*()<>;?]", var.rds_password)) >= 2
error_message = "RDS password must contain at least 2 of the following special characters: !#$%^&*()<>;?"
}
validation {
condition = length(regexall("[\\/@'\" ]", var.rds_password)) == 0
error_message = "RDS password must NOT contain spaces or any of the following special characters: /@'\""
}
validation {
condition = var.rds_password != "CHANGEME"
error_message = "RDS password must not be \"CHANGEME\"!"
}
validation {
condition = !can(regex(" ", var.rds_password))
error_message = "RDS password must not contain spaces!"
}
}
variable "subnet_public_a" {
description = "The CIDR for public subnet in AZ a"
type = string
nullable = false
validation {
condition = can(cidrhost(var.subnet_public_a, 4094))
error_message = "Must be valid /20 IPv4 CIDR with a maximum of 4094 hosts (eg: 10.10.0.0/20)."
}
}
variable "subnet_database_a" {
description = "The CIDR for database subnet in AZ a"
type = string
nullable = false
validation {
condition = can(cidrhost(var.subnet_database_a, 14))
error_message = "Must be valid /28 IPv4 CIDR with a maximum of 14 hosts (eg: 10.10.110.0/28)."
}
}
# Just to make this example runable
locals {
terraform-git-repo = "terraform-aws-infra"
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
Application = var.application
Department = var.department
Orchestration = var.orchestration
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment