# Variables ==================================================================== variable "name_prefix" { description = "Prefix prepended to resource names created by this module" type = string validation { condition = length(trimspace(var.name_prefix)) > 0 error_message = "name_prefix must not be empty." } } variable "tags" { description = "Tags applied to all resources in this module." type = map(string) default = {} } variable "key_pairs" { description = <<-EOT Map of key pairs to register in AWS EC2. Each entry requires a name and an OpenSSH-format public key string which may be sourced from a secret store or a TLS provider. EOT type = map(object({ name = string public_key = string })) default = {} } variable "instances" { description = <<-EOT Map of EC2 instance definitions. - ami_id is passed in explicitly to ensure deterministic builds. - name_suffix is appended to resources to ensure standardized names. EOT type = map(object({ name_suffix = string ami_id = string instance_type = string subnet_id = string security_group_ids = list(string) iam_instance_profile = optional(string) key_name = optional(string) associate_public_ip = optional(bool, false) ebs_optimized = optional(bool, true) monitoring = optional(bool, false) placement_group = optional(string) tenancy = optional(string, "default") primary_private_ip = optional(string) secondary_private_ips = optional(list(string), []) # User data — mutually exclusive user_data = optional(string) user_data_base64 = optional(string) # IMDSv2 — tokens required by default, hop limit 1 unless overridden (e.g. ECS needs 2) metadata_http_tokens = optional(string, "required") metadata_hop_limit = optional(number, 1) # Root volume root_volume = object({ size = number type = optional(string, "gp3") kms_key_id = optional(string) delete_on_termination = optional(bool, true) tags = optional(map(string), {}) }) # Additional EBS data volumes data_volumes = optional(list(object({ device_name = string volume_size = number volume_type = optional(string, "gp3") kms_key_id = optional(string) delete_on_termination = optional(bool, true) iops = optional(number) throughput = optional(number) tags = optional(map(string), {}) })), []) # Per-instance tags — overrides module level tags instance_tags = optional(map(string), {}) })) default = {} validation { condition = alltrue([ for k, v in var.instances : v.primary_private_ip == null || can(cidrhost("${v.primary_private_ip}/32", 0)) ]) error_message = "primary_private_ip must be a valid IPv4 address." } validation { condition = alltrue([ for k, v in var.instances : alltrue([for ip in v.secondary_private_ips : can(cidrhost("${ip}/32", 0))]) ]) error_message = "secondary_private_ips must contain valid IPv4 addresses." } validation { condition = alltrue([ for k, v in var.instances : !(v.user_data != null && v.user_data_base64 != null) ]) error_message = "Instances may not set both user_data and user_data_base64. Use one or the other." } validation { condition = alltrue([ for k, v in var.instances : contains(["required", "optional"], v.metadata_http_tokens) ]) error_message = "metadata_http_tokens must be 'required' or 'optional'." } }