From 6efc52bbc3b3893e0a60b42bcf2bbcbdf23d0588 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 10 Jun 2026 14:25:00 +0100 Subject: [PATCH 01/30] fix .tool-versions --- .tool-versions | 2 -- 1 file changed, 2 deletions(-) diff --git a/.tool-versions b/.tool-versions index f7161f09..3cf37859 100644 --- a/.tool-versions +++ b/.tool-versions @@ -8,7 +8,6 @@ vale 3.6.0 gitleaks 8.30.1 nodejs 24.16.0 - # ============================================================================== # The section below is reserved for Docker image versions. @@ -25,4 +24,3 @@ nodejs 24.16.0 # docker/koalaman/shellcheck latest@sha256:e40388688bae0fcffdddb7e4dea49b900c18933b452add0930654b2dea3e7d5c # SEE: https://hub.docker.com/r/koalaman/shellcheck/tags # docker/mstruebing/editorconfig-checker 2.7.1@sha256:dd3ca9ea50ef4518efe9be018d669ef9cf937f6bb5cfe2ef84ff2a620b5ddc24 # SEE: https://hub.docker.com/r/mstruebing/editorconfig-checker/tags # docker/sonarsource/sonar-scanner-cli 5.0.1@sha256:494ecc3b5b1ee1625bd377b3905c4284e4f0cc155cff397805a244dee1c7d575 # SEE: https://hub.docker.com/r/sonarsource/sonar-scanner-cli/tags - From 67b6f0fe6b9b6149e485a628bdda0098cfa88190 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 10 Jun 2026 14:25:54 +0100 Subject: [PATCH 02/30] add context --- infrastructure/modules/ec2/context.tf | 374 ++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 infrastructure/modules/ec2/context.tf diff --git a/infrastructure/modules/ec2/context.tf b/infrastructure/modules/ec2/context.tf new file mode 100644 index 00000000..e1afea79 --- /dev/null +++ b/infrastructure/modules/ec2/context.tf @@ -0,0 +1,374 @@ +# +# ONLY EDIT THIS FILE IN github.com/NHSDigital/screening-terraform-modules-aws/infrastructure/modules/tags +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/NHSDigital/screening-terraform-modules-aws/blob/master/infrastructure/modules/tags/exports/context.tf +# and then place it in your Terraform module to automatically get +# tag module standard configuration inputs suitable for passing +# to other modules. +# +# curl -sL https://raw.githubusercontent.com/NHSDigital/screening-terraform-modules-aws/master/infrastructure/modules/tags/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "../tags" + + service = var.service + project = var.project + region = var.region + environment = var.environment + stack = var.stack + workspace = var.workspace + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + terraform_source = coalesce(var.terraform_source, path.module) + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of screening-terraform-modules-aws/tags/variables.tf here +# tflint-ignore: terraform_unused_declarations +variable "aws_region" { + type = string + description = "The AWS region" + default = "eu-west-2" + validation { + condition = contains(["eu-west-1", "eu-west-2", "us-east-1"], var.aws_region) + error_message = "AWS Region must be one of eu-west-1, eu-west-2, us-east-1" + } +} + +variable "context" { + type = any + default = { + enabled = true + service = null + project = null + region = null + environment = null + stack = null + workspace = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + terraform_source = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "terraform_source" { + type = string + default = null + description = "Source location to record in the Terraform_source tag. Defaults to the caller module path when not set." +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "service" { + type = string + default = null + description = "ID element. Usually an abbreviation of your service directorate name, e.g. 'bcss' or 'csms', to help ensure generated IDs are globally unique" +} + +variable "region" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. Usually an abbreviation of the selected AWS region e.g. 'uw2', 'ew2' or 'gbl' for resources like IAM roles that have no region" +} + +variable "project" { + type = string + default = null + description = "ID element. A project identifier, indicating the name or role of the project the resource is for, such as `website` or `api`" +} +variable "stack" { + type = string + default = null + description = "ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks`" +} +variable "workspace" { + type = string + default = null + description = "ID element. The Terraform workspace, to help ensure generated IDs are unique across workspaces" +} +variable "environment" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prd', 'dev', 'test', 'preprod', 'prod', 'uat'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +variable "owner" { + type = string + description = "The name and or NHS.net email address of the service owner" + default = "None" +} + +variable "tag_version" { + type = string + description = "Used to identify the tagging version in use" + default = "1.0" +} + +variable "data_classification" { + type = string + description = "Used to identify the data classification of the resource, e.g 1-5" + default = "n/a" + validation { + condition = contains(["n/a", "1", "2", "3", "4", "5"], var.data_classification) + error_message = "Data Classification must be \"n/a\" or between 1-5" + } +} + +variable "data_type" { + type = string + description = "The tag data_type" + default = "None" + validation { + condition = contains(["None", "PCD", "PID", "Anonymised", "UserAccount", "Audit"], var.data_type) + error_message = "Data Type must be one of None, PCD, PID, Anonymised, UserAccount, Audit" + } +} + + +variable "public_facing" { + type = bool + description = "Whether this resource is public facing" + default = false +} + +variable "service_category" { + type = string + description = "The tag service_category" + default = "n/a" + validation { + condition = contains(["n/a", "Bronze", "Silver", "Gold", "Platinum"], var.service_category) + error_message = "The Service Category must be one of n/a, Bronze, Silver, Gold, Platinum" + } +} +variable "on_off_pattern" { + type = string + description = "Used to turn resources on and off based on a time pattern" + default = "n/a" +} + +variable "application_role" { + type = string + description = "The role the application is performing" + default = "General" +} + +variable "tool" { + type = string + description = "The tool used to deploy the resource" + default = "Terraform" +} + +#### End of copy of screening-terraform-modules-aws/tags/variables.tf From 4fb4cd417e54eb6d2d75a079b0f3ce262fe0e823 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 10 Jun 2026 14:27:21 +0100 Subject: [PATCH 03/30] add placeholder files --- infrastructure/modules/ec2/README.md | 1 + infrastructure/modules/ec2/main.tf | 1 + infrastructure/modules/ec2/outputs.tf | 1 + infrastructure/modules/ec2/variables.tf | 1 + 4 files changed, 4 insertions(+) create mode 100644 infrastructure/modules/ec2/README.md create mode 100644 infrastructure/modules/ec2/main.tf create mode 100644 infrastructure/modules/ec2/outputs.tf create mode 100644 infrastructure/modules/ec2/variables.tf diff --git a/infrastructure/modules/ec2/README.md b/infrastructure/modules/ec2/README.md new file mode 100644 index 00000000..c9fb5240 --- /dev/null +++ b/infrastructure/modules/ec2/README.md @@ -0,0 +1 @@ +# DAVEH diff --git a/infrastructure/modules/ec2/main.tf b/infrastructure/modules/ec2/main.tf new file mode 100644 index 00000000..c9fb5240 --- /dev/null +++ b/infrastructure/modules/ec2/main.tf @@ -0,0 +1 @@ +# DAVEH diff --git a/infrastructure/modules/ec2/outputs.tf b/infrastructure/modules/ec2/outputs.tf new file mode 100644 index 00000000..c9fb5240 --- /dev/null +++ b/infrastructure/modules/ec2/outputs.tf @@ -0,0 +1 @@ +# DAVEH diff --git a/infrastructure/modules/ec2/variables.tf b/infrastructure/modules/ec2/variables.tf new file mode 100644 index 00000000..c9fb5240 --- /dev/null +++ b/infrastructure/modules/ec2/variables.tf @@ -0,0 +1 @@ +# DAVEH From 2264133998cf74e4ee812ad4d355d4421d8efc8a Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 10 Jun 2026 14:34:00 +0100 Subject: [PATCH 04/30] module bare bones --- infrastructure/modules/ec2/main.tf | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/ec2/main.tf b/infrastructure/modules/ec2/main.tf index c9fb5240..c9de3bb6 100644 --- a/infrastructure/modules/ec2/main.tf +++ b/infrastructure/modules/ec2/main.tf @@ -1 +1,10 @@ -# DAVEH +module "ec2_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "6.4.0" + + create = module.this.enabled + name = module.this.id + tags = module.this.tags + + # DAVEH +} From 4a276f34c647d30344cfc397cd51dfcfdc00ca38 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 10 Jun 2026 15:36:36 +0100 Subject: [PATCH 05/30] rename module ec2 -> ec2-instance --- infrastructure/modules/{ec2 => ec2-instance}/README.md | 0 infrastructure/modules/{ec2 => ec2-instance}/context.tf | 0 infrastructure/modules/{ec2 => ec2-instance}/main.tf | 0 infrastructure/modules/{ec2 => ec2-instance}/outputs.tf | 0 infrastructure/modules/{ec2 => ec2-instance}/variables.tf | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename infrastructure/modules/{ec2 => ec2-instance}/README.md (100%) rename infrastructure/modules/{ec2 => ec2-instance}/context.tf (100%) rename infrastructure/modules/{ec2 => ec2-instance}/main.tf (100%) rename infrastructure/modules/{ec2 => ec2-instance}/outputs.tf (100%) rename infrastructure/modules/{ec2 => ec2-instance}/variables.tf (100%) diff --git a/infrastructure/modules/ec2/README.md b/infrastructure/modules/ec2-instance/README.md similarity index 100% rename from infrastructure/modules/ec2/README.md rename to infrastructure/modules/ec2-instance/README.md diff --git a/infrastructure/modules/ec2/context.tf b/infrastructure/modules/ec2-instance/context.tf similarity index 100% rename from infrastructure/modules/ec2/context.tf rename to infrastructure/modules/ec2-instance/context.tf diff --git a/infrastructure/modules/ec2/main.tf b/infrastructure/modules/ec2-instance/main.tf similarity index 100% rename from infrastructure/modules/ec2/main.tf rename to infrastructure/modules/ec2-instance/main.tf diff --git a/infrastructure/modules/ec2/outputs.tf b/infrastructure/modules/ec2-instance/outputs.tf similarity index 100% rename from infrastructure/modules/ec2/outputs.tf rename to infrastructure/modules/ec2-instance/outputs.tf diff --git a/infrastructure/modules/ec2/variables.tf b/infrastructure/modules/ec2-instance/variables.tf similarity index 100% rename from infrastructure/modules/ec2/variables.tf rename to infrastructure/modules/ec2-instance/variables.tf From 9a9fe9473eb1d63c73baa419af1dcd9c01f61a1a Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 10 Jun 2026 15:41:53 +0100 Subject: [PATCH 06/30] note underlying ec2_instance fields we currently set --- infrastructure/modules/ec2-instance/main.tf | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index c9de3bb6..b22fd732 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -6,5 +6,38 @@ module "ec2_instance" { name = module.this.id tags = module.this.tags + ami = DAVEH + instance_type = DAVEH + key_name = DAVEH + disable_api_termination = DAVEH + ebs_optimized = DAVEH + monitoring = true + subnet_id = DAVEH + + vpc_security_group_ids = DAVEH + + iam_instance_profile = DAVEH + + # root_block_device { + # encrypted = true + # kms_key_id = data.terraform_remote_state.shared_resources.outputs.oracle_ebs_snapshot_kms_key_arn + # volume_size = var.oracle19_root_volume_size + # volume_type = "gp3" + # } + + # metadata_options { + # http_tokens = "required" + # http_endpoint = "enabled" + # http_put_response_hop_limit = 1 + # instance_metadata_tags = "disabled" + # } + + user_data = DAVEH + user_data_replace_on_change = false + + # lifecycle { + # ignore_changes = [user_data] + # } + # DAVEH } From 9fc14289ac1ff367e745254f348d00054ac6e58e Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 10 Jun 2026 16:36:20 +0100 Subject: [PATCH 07/30] add some variables --- infrastructure/modules/ec2-instance/main.tf | 12 +++--- .../modules/ec2-instance/variables.tf | 43 ++++++++++++++++++- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index b22fd732..08bb4cd3 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -6,13 +6,13 @@ module "ec2_instance" { name = module.this.id tags = module.this.tags - ami = DAVEH - instance_type = DAVEH - key_name = DAVEH - disable_api_termination = DAVEH - ebs_optimized = DAVEH + ami = var.ami + instance_type = var.instance_type + key_name = var.key_name + disable_api_termination = var.disable_api_termination + ebs_optimized = var.ebs_optimized monitoring = true - subnet_id = DAVEH + subnet_id = var.subnet_id vpc_security_group_ids = DAVEH diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index c9fb5240..b91d2f6a 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -1 +1,42 @@ -# DAVEH +################################################################ +# EC2 instance-specific inputs. +# +# Naming, tagging and the master `enabled` switch come from +# context.tf via `module.this`. +################################################################ + +variable "ami" { + description = "ID of AMI to use for the instance" + type = string + default = null +} + +variable "instance_type" { + description = "The type of instance to start" + type = string + default = "t3.micro" +} + +variable "key_name" { + description = "Name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" + type = string + default = null +} + +variable "disable_api_termination" { + description = "If true, enables EC2 Instance Termination Protection" + type = bool + default = null +} + +variable "ebs_optimized" { + description = "If true, the launched EC2 instance will be EBS-optimized" + type = bool + default = null +} + +variable "subnet_id" { + description = "The VPC Subnet ID to launch in" + type = string + default = null +} From c6ca0dfa22a2b9aa983891c8d1345d8597b7d840 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 10 Jun 2026 16:53:12 +0100 Subject: [PATCH 08/30] add more variables --- infrastructure/modules/ec2-instance/main.tf | 6 +++--- .../modules/ec2-instance/variables.tf | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 08bb4cd3..70220dbd 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -14,9 +14,9 @@ module "ec2_instance" { monitoring = true subnet_id = var.subnet_id - vpc_security_group_ids = DAVEH + vpc_security_group_ids = var.vpc_security_group_ids - iam_instance_profile = DAVEH + iam_instance_profile = var.iam_instance_profile # root_block_device { # encrypted = true @@ -32,7 +32,7 @@ module "ec2_instance" { # instance_metadata_tags = "disabled" # } - user_data = DAVEH + user_data = var.user_data user_data_replace_on_change = false # lifecycle { diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index b91d2f6a..668bbd82 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -40,3 +40,21 @@ variable "subnet_id" { type = string default = null } + +variable "vpc_security_group_ids" { + description = "List of VPC Security Group IDs to associate with" + type = list(string) + default = [] +} + +variable "iam_instance_profile" { + description = "IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile" + type = string + default = null +} + +variable "user_data" { + description = "The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument" + type = string + default = null +} From faeb34c2992854622c0f1c099e26707ef109f902 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 11 Jun 2026 09:00:08 +0100 Subject: [PATCH 09/30] root_block_device --- infrastructure/modules/ec2-instance/main.tf | 7 +------ infrastructure/modules/ec2-instance/variables.tf | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 70220dbd..7d97604a 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -18,12 +18,7 @@ module "ec2_instance" { iam_instance_profile = var.iam_instance_profile - # root_block_device { - # encrypted = true - # kms_key_id = data.terraform_remote_state.shared_resources.outputs.oracle_ebs_snapshot_kms_key_arn - # volume_size = var.oracle19_root_volume_size - # volume_type = "gp3" - # } + root_block_device = var.root_block_device # metadata_options { # http_tokens = "required" diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index 668bbd82..d6823e0f 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -58,3 +58,18 @@ variable "user_data" { type = string default = null } + +variable "root_block_device" { + description = "Customize details about the root block device of the instance" + type = object({ + delete_on_termination = optional(bool) + encrypted = optional(bool) + iops = optional(number) + kms_key_id = optional(string) + tags = optional(map(string)) + throughput = optional(number) + size = optional(number) + type = optional(string) + }) + default = null +} From 3f8aa864f316cc17488059204f2800f1fd8020fb Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 11 Jun 2026 09:15:34 +0100 Subject: [PATCH 10/30] metadata_options --- infrastructure/modules/ec2-instance/main.tf | 7 +------ infrastructure/modules/ec2-instance/variables.tf | 12 ++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 7d97604a..7af70c55 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -20,12 +20,7 @@ module "ec2_instance" { root_block_device = var.root_block_device - # metadata_options { - # http_tokens = "required" - # http_endpoint = "enabled" - # http_put_response_hop_limit = 1 - # instance_metadata_tags = "disabled" - # } + metadata_options = var.metadata_options user_data = var.user_data user_data_replace_on_change = false diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index d6823e0f..3e2cfac2 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -73,3 +73,15 @@ variable "root_block_device" { }) default = null } + +variable "metadata_options" { + description = "Customize the metadata options of the instance" + type = object({ + http_endpoint = optional(string, "enabled") + http_protocol_ipv6 = optional(string) + http_put_response_hop_limit = optional(number, 1) + http_tokens = optional(string, "required") + instance_metadata_tags = optional(string) + }) + default = {} +} From 450e7ac0ec1f6a1f7092e6ddf001b1db1f834569 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 11 Jun 2026 15:19:48 +0100 Subject: [PATCH 11/30] add outputs used in existing stack --- infrastructure/modules/ec2-instance/outputs.tf | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/ec2-instance/outputs.tf b/infrastructure/modules/ec2-instance/outputs.tf index c9fb5240..d9095bfc 100644 --- a/infrastructure/modules/ec2-instance/outputs.tf +++ b/infrastructure/modules/ec2-instance/outputs.tf @@ -1 +1,9 @@ -# DAVEH +output "ec2_instance_id" { + description = "The ID of the EC2 instance" + value = module.ec2_instance.id +} + +output "private_ip" { + description = "The private IP address of the EC2 instance" + value = module.ec2_instance.private_ip +} From 15a90166d7deb08a87c6b2ca17f13396cafcaf01 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Fri, 12 Jun 2026 07:58:49 +0100 Subject: [PATCH 12/30] explain blocker --- infrastructure/modules/ec2-instance/main.tf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 7af70c55..b364d654 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -25,9 +25,10 @@ module "ec2_instance" { user_data = var.user_data user_data_replace_on_change = false + # DAVEH: can't set lifecycle hooks on module calls, want it set on the + # underlying resource anyway. vague plan is to fork the module, but + # not there yet # lifecycle { # ignore_changes = [user_data] # } - - # DAVEH } From 0dde744de31b452f0e7e3f91ffb2a97a59fd3025 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Fri, 12 Jun 2026 08:09:09 +0100 Subject: [PATCH 13/30] add tags for autogenerated docs --- infrastructure/modules/ec2-instance/README.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/infrastructure/modules/ec2-instance/README.md b/infrastructure/modules/ec2-instance/README.md index c9fb5240..efc36a3c 100644 --- a/infrastructure/modules/ec2-instance/README.md +++ b/infrastructure/modules/ec2-instance/README.md @@ -1 +1,80 @@ # DAVEH + + + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +| ---- | ------ | ------- | +| [ec2\_instance](#module\_ec2\_instance) | terraform-aws-modules/ec2-instance/aws | 6.4.0 | +| [this](#module\_this) | ../tags | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [ami](#input\_ami) | ID of AMI to use for the instance | `string` | `null` | no | +| [application\_role](#input\_application\_role) | The role the application is performing | `string` | `"General"` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [aws\_region](#input\_aws\_region) | The AWS region | `string` | `"eu-west-2"` | no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"project": null,
"regex_replace_chars": null,
"region": null,
"service": null,
"stack": null,
"tags": {},
"terraform_source": null,
"workspace": null
}
| no | +| [data\_classification](#input\_data\_classification) | Used to identify the data classification of the resource, e.g 1-5 | `string` | `"n/a"` | no | +| [data\_type](#input\_data\_type) | The tag data\_type | `string` | `"None"` | no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 Instance Termination Protection | `bool` | `null` | no | +| [ebs\_optimized](#input\_ebs\_optimized) | If true, the launched EC2 instance will be EBS-optimized | `bool` | `null` | no | +| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [environment](#input\_environment) | ID element. Usually used to indicate role, e.g. 'prd', 'dev', 'test', 'preprod', 'prod', 'uat' | `string` | `null` | no | +| [iam\_instance\_profile](#input\_iam\_instance\_profile) | IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile | `string` | `null` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [instance\_type](#input\_instance\_type) | The type of instance to start | `string` | `"t3.micro"` | no | +| [key\_name](#input\_key\_name) | Name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource | `string` | `null` | no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [metadata\_options](#input\_metadata\_options) | Customize the metadata options of the instance |
object({
http_endpoint = optional(string, "enabled")
http_protocol_ipv6 = optional(string)
http_put_response_hop_limit = optional(number, 1)
http_tokens = optional(string, "required")
instance_metadata_tags = optional(string)
})
| `{}` | no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [on\_off\_pattern](#input\_on\_off\_pattern) | Used to turn resources on and off based on a time pattern | `string` | `"n/a"` | no | +| [owner](#input\_owner) | The name and or NHS.net email address of the service owner | `string` | `"None"` | no | +| [project](#input\_project) | ID element. A project identifier, indicating the name or role of the project the resource is for, such as `website` or `api` | `string` | `null` | no | +| [public\_facing](#input\_public\_facing) | Whether this resource is public facing | `bool` | `false` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [region](#input\_region) | ID element \_(Rarely used, not included by default)\_. Usually an abbreviation of the selected AWS region e.g. 'uw2', 'ew2' or 'gbl' for resources like IAM roles that have no region | `string` | `null` | no | +| [root\_block\_device](#input\_root\_block\_device) | Customize details about the root block device of the instance |
object({
delete_on_termination = optional(bool)
encrypted = optional(bool)
iops = optional(number)
kms_key_id = optional(string)
tags = optional(map(string))
throughput = optional(number)
size = optional(number)
type = optional(string)
})
| `null` | no | +| [service](#input\_service) | ID element. Usually an abbreviation of your service directorate name, e.g. 'bcss' or 'csms', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [service\_category](#input\_service\_category) | The tag service\_category | `string` | `"n/a"` | no | +| [stack](#input\_stack) | ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks` | `string` | `null` | no | +| [subnet\_id](#input\_subnet\_id) | The VPC Subnet ID to launch in | `string` | `null` | no | +| [tag\_version](#input\_tag\_version) | Used to identify the tagging version in use | `string` | `"1.0"` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [terraform\_source](#input\_terraform\_source) | Source location to record in the Terraform\_source tag. Defaults to the caller module path when not set. | `string` | `null` | no | +| [tool](#input\_tool) | The tool used to deploy the resource | `string` | `"Terraform"` | no | +| [user\_data](#input\_user\_data) | The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument | `string` | `null` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of VPC Security Group IDs to associate with | `list(string)` | `[]` | no | +| [workspace](#input\_workspace) | ID element. The Terraform workspace, to help ensure generated IDs are unique across workspaces | `string` | `null` | no | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| [ec2\_instance\_id](#output\_ec2\_instance\_id) | The ID of the EC2 instance | +| [private\_ip](#output\_private\_ip) | The private IP address of the EC2 instance | + + + From b2f7bc7da05068e9cb6d1755ec62d39350227684 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 14:31:40 +0100 Subject: [PATCH 14/30] add all missing outputs --- .../modules/ec2-instance/outputs.tf | 142 +++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/ec2-instance/outputs.tf b/infrastructure/modules/ec2-instance/outputs.tf index d9095bfc..4b1c60a0 100644 --- a/infrastructure/modules/ec2-instance/outputs.tf +++ b/infrastructure/modules/ec2-instance/outputs.tf @@ -1,9 +1,149 @@ +output "ami" { + description = "AMI ID that was used to create the instance" + value = module.ec2_instance.ami +} + +output "ec2_instance_arn" { + description = "The ARN of the EC2 instance" + value = module.ec2_instance.arn +} + +output "availability_zone" { + description = "The availability zone of the created instance" + value = module.ec2_instance.availability_zone +} + +output "capacity_reservation_specification" { + description = "Capacity reservation specification of the instance" + value = module.ec2_instance.capacity_reservation_specification +} + +output "ebs_block_device" { + description = "EBS block device information" + value = module.ec2_instance.ebs_block_device +} + +output "ebs_volumes" { + description = "Map of EBS volumes created and their attributes" + value = module.ec2_instance.ebs_volumes +} + +output "ephemeral_block_device" { + description = "Ephemeral block device information" + value = module.ec2_instance.ephemeral_block_device +} + +output "iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = module.ec2_instance.iam_instance_profile_arn +} + +output "iam_instance_profile_id" { + description = "Instance profile's ID" + value = module.ec2_instance.iam_instance_profile_id +} + +output "iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = module.ec2_instance.iam_instance_profile_unique +} + +output "iam_role_arn" { + description = "The ARN specifying the IAM role" + value = module.ec2_instance.iam_role_arn +} + +output "iam_role_name" { + description = "The name of the IAM role" + value = module.ec2_instance.iam_role_name +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ec2_instance.iam_role_unique_id +} + output "ec2_instance_id" { description = "The ID of the EC2 instance" value = module.ec2_instance.id } +output "instance_state" { + description = "The state of the instance" + value = module.ec2_instance.instance_state +} + +output "ipv6_addresses" { + description = "The IPv6 address assigned to the instance, if applicable" + value = module.ec2_instance.ipv6_addresses +} + +output "outpost_arn" { + description = "The ARN of the Outpost the instance is assigned to" + value = module.ec2_instance.outpost_arn +} + +output "password_data" { + description = "Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true" + value = module.ec2_instance.password_data +} + +output "primary_network_interface_id" { + description = "The ID of the instance's primary network interface" + value = module.ec2_instance.primary_network_interface_id +} + +output "private_dns" { + description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_instance.private_dns +} + output "private_ip" { - description = "The private IP address of the EC2 instance" + description = "The private IP address assigned to the instance" value = module.ec2_instance.private_ip } + +output "public_dns" { + description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC" + value = module.ec2_instance.public_dns +} + +output "public_ip" { + description = "The public IP address assigned to the instance, if applicable." + value = module.ec2_instance.public_ip +} + +output "root_block_device" { + description = "Root block device information" + value = module.ec2_instance.root_block_device +} + +output "security_group_arn" { + description = "The ARN of the security group" + value = module.ec2_instance.security_group_arn +} + +output "security_group_id" { + description = "The ID of the security group" + value = module.ec2_instance.security_group_id +} + +output "spot_bid_status" { + description = "The current bid status of the Spot Instance Request" + value = module.ec2_instance.spot_bid_status +} + +output "spot_instance_id" { + description = "The Instance ID (if any) that is currently fulfilling the Spot Instance request" + value = module.ec2_instance.spot_instance_id +} + +output "spot_request_state" { + description = "The current request state of the Spot Instance Request" + value = module.ec2_instance.spot_request_state +} + +output "tags_all" { + description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" + value = module.ec2_instance.tags_all +} From 975ae5fd1e1f550560ec9e47a83f263c77edf073 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 14:38:06 +0100 Subject: [PATCH 15/30] reorder existing input variables alphabetically --- infrastructure/modules/ec2-instance/main.tf | 34 +++------- .../modules/ec2-instance/variables.tf | 68 +++++++++---------- 2 files changed, 45 insertions(+), 57 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index b364d654..80c7527e 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -6,29 +6,17 @@ module "ec2_instance" { name = module.this.id tags = module.this.tags - ami = var.ami - instance_type = var.instance_type - key_name = var.key_name - disable_api_termination = var.disable_api_termination - ebs_optimized = var.ebs_optimized - monitoring = true - subnet_id = var.subnet_id - - vpc_security_group_ids = var.vpc_security_group_ids - - iam_instance_profile = var.iam_instance_profile - - root_block_device = var.root_block_device - - metadata_options = var.metadata_options - + ami = var.ami + disable_api_termination = var.disable_api_termination + ebs_optimized = var.ebs_optimized + iam_instance_profile = var.iam_instance_profile + instance_type = var.instance_type + key_name = var.key_name + metadata_options = var.metadata_options + monitoring = true + root_block_device = var.root_block_device + subnet_id = var.subnet_id user_data = var.user_data user_data_replace_on_change = false - - # DAVEH: can't set lifecycle hooks on module calls, want it set on the - # underlying resource anyway. vague plan is to fork the module, but - # not there yet - # lifecycle { - # ignore_changes = [user_data] - # } + vpc_security_group_ids = var.vpc_security_group_ids } diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index 3e2cfac2..318c274a 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -11,18 +11,6 @@ variable "ami" { default = null } -variable "instance_type" { - description = "The type of instance to start" - type = string - default = "t3.micro" -} - -variable "key_name" { - description = "Name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" - type = string - default = null -} - variable "disable_api_termination" { description = "If true, enables EC2 Instance Termination Protection" type = bool @@ -35,28 +23,34 @@ variable "ebs_optimized" { default = null } -variable "subnet_id" { - description = "The VPC Subnet ID to launch in" +variable "iam_instance_profile" { + description = "IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile" type = string default = null } -variable "vpc_security_group_ids" { - description = "List of VPC Security Group IDs to associate with" - type = list(string) - default = [] +variable "instance_type" { + description = "The type of instance to start" + type = string + default = "t3.micro" } -variable "iam_instance_profile" { - description = "IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile" +variable "key_name" { + description = "Name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" type = string default = null } -variable "user_data" { - description = "The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument" - type = string - default = null +variable "metadata_options" { + description = "Customize the metadata options of the instance" + type = object({ + http_endpoint = optional(string, "enabled") + http_protocol_ipv6 = optional(string) + http_put_response_hop_limit = optional(number, 1) + http_tokens = optional(string, "required") + instance_metadata_tags = optional(string) + }) + default = {} } variable "root_block_device" { @@ -74,14 +68,20 @@ variable "root_block_device" { default = null } -variable "metadata_options" { - description = "Customize the metadata options of the instance" - type = object({ - http_endpoint = optional(string, "enabled") - http_protocol_ipv6 = optional(string) - http_put_response_hop_limit = optional(number, 1) - http_tokens = optional(string, "required") - instance_metadata_tags = optional(string) - }) - default = {} +variable "subnet_id" { + description = "The VPC Subnet ID to launch in" + type = string + default = null +} + +variable "user_data" { + description = "The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument" + type = string + default = null +} + +variable "vpc_security_group_ids" { + description = "List of VPC Security Group IDs to associate with" + type = list(string) + default = [] } From f148d802690e7df29065d56a5c64867b5caa4591 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 15:27:57 +0100 Subject: [PATCH 16/30] add inputs a-d --- infrastructure/modules/ec2-instance/main.tf | 37 +++++---- .../modules/ec2-instance/variables.tf | 77 +++++++++++++++++++ 2 files changed, 101 insertions(+), 13 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 80c7527e..2c1cf474 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -6,17 +6,28 @@ module "ec2_instance" { name = module.this.id tags = module.this.tags - ami = var.ami - disable_api_termination = var.disable_api_termination - ebs_optimized = var.ebs_optimized - iam_instance_profile = var.iam_instance_profile - instance_type = var.instance_type - key_name = var.key_name - metadata_options = var.metadata_options - monitoring = true - root_block_device = var.root_block_device - subnet_id = var.subnet_id - user_data = var.user_data - user_data_replace_on_change = false - vpc_security_group_ids = var.vpc_security_group_ids + ami = var.ami + ami_ssm_parameter = var.ami_ssm_parameter + associate_public_ip_address = var.associate_public_ip_address + availability_zone = var.availability_zone + capacity_reservation_specification = var.capacity_reservation_specification + cpu_credits = var.cpu_credits + cpu_options = var.cpu_options + create_eip = var.create_eip + create_iam_instance_profile = var.create_iam_instance_profile + create_security_group = var.create_security_group + create_spot_instance = var.create_spot_instance + disable_api_stop = var.disable_api_stop + disable_api_termination = var.disable_api_termination + ebs_optimized = var.ebs_optimized + iam_instance_profile = var.iam_instance_profile + instance_type = var.instance_type + key_name = var.key_name + metadata_options = var.metadata_options + monitoring = true + root_block_device = var.root_block_device + subnet_id = var.subnet_id + user_data = var.user_data + user_data_replace_on_change = false + vpc_security_group_ids = var.vpc_security_group_ids } diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index 318c274a..ea3b9860 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -11,6 +11,83 @@ variable "ami" { default = null } +variable "ami_ssm_parameter" { + description = "SSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html" + type = string + default = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64" +} + +variable "associate_public_ip_address" { + description = "Whether to associate a public IP address with an instance in a VPC" + type = bool + default = null +} + +variable "availability_zone" { + description = "The AZ to start the instance in" + type = string + default = null +} + +variable "capacity_reservation_specification" { + description = "Describes an instance's Capacity Reservation targeting option" + type = object({ + capacity_reservation_preference = optional(string) + capacity_reservation_target = optional(object({ + capacity_reservation_id = optional(string) + capacity_reservation_resource_group_arn = optional(string) + })) + }) + default = null +} + +variable "cpu_credits" { + description = "The credit option for CPU usage (unlimited or standard)" + type = string + default = null +} + +variable "cpu_options" { + description = "Defines CPU options to apply to the instance at launch time." + type = object({ + amd_sev_snp = optional(string) + core_count = optional(number) + nested_virtualization = optional(string) + threads_per_core = optional(number) + }) + default = null +} + +variable "create_eip" { + description = "Determines whether a public EIP will be created and associated with the instance." + type = bool + default = false +} + +variable "create_iam_instance_profile" { + description = "Determines whether an IAM instance profile is created or to use an existing IAM instance profile" + type = bool + default = false +} + +variable "create_security_group" { + description = "Determines whether a security group will be created" + type = bool + default = true +} + +variable "create_spot_instance" { + description = "Depicts if the instance is a spot instance" + type = bool + default = false +} + +variable "disable_api_stop" { + description = "If true, enables EC2 Instance Stop Protection" + type = bool + default = null +} + variable "disable_api_termination" { description = "If true, enables EC2 Instance Termination Protection" type = bool From 80e62cd80909ca2d8f9fe488c2863fc7dfc1a745 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 15:37:36 +0100 Subject: [PATCH 17/30] add inputs e-h --- infrastructure/modules/ec2-instance/main.tf | 12 +++ .../modules/ec2-instance/variables.tf | 94 +++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 2c1cf474..5a6be9d3 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -20,6 +20,18 @@ module "ec2_instance" { disable_api_stop = var.disable_api_stop disable_api_termination = var.disable_api_termination ebs_optimized = var.ebs_optimized + ebs_volumes = var.ebs_volumes + eip_domain = var.eip_domain + eip_tags = var.eip_tags + enable_primary_ipv6 = var.enable_primary_ipv6 + enable_volume_tags = var.enable_volume_tags + enclave_options_enabled = var.enclave_options_enabled + ephemeral_block_device = var.ephemeral_block_device + force_destroy = var.force_destroy + get_password_data = var.get_password_data + hibernation = var.hibernation + host_id = var.host_id + host_resource_group_arn = var.host_resource_group_arn iam_instance_profile = var.iam_instance_profile instance_type = var.instance_type key_name = var.key_name diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index ea3b9860..c23e932c 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -100,6 +100,100 @@ variable "ebs_optimized" { default = null } +variable "ebs_volumes" { + description = "Map of EBS volumes to attach to the instance" + type = map(object({ + encrypted = optional(bool) + final_snapshot = optional(bool) + iops = optional(number) + kms_key_id = optional(string) + multi_attach_enabled = optional(bool) + outpost_arn = optional(string) + size = optional(number) + snapshot_id = optional(string) + tags = optional(map(string), {}) + throughput = optional(number) + type = optional(string, "gp3") + volume_initialization_rate = optional(number) + # Attachment + device_name = optional(string) # Will fall back to use map key as device name + force_detach = optional(bool) + skip_destroy = optional(bool) + stop_instance_before_detaching = optional(bool) + })) + default = null +} + +variable "eip_domain" { + description = "Indicates if this EIP is for use in VPC" + type = string + default = "vpc" +} + +variable "eip_tags" { + description = "A map of additional tags to add to the EIP" + type = map(string) + default = {} +} + +variable "enable_primary_ipv6" { + description = "Whether to assign a primary IPv6 Global Unicast Address (GUA) to the instance when launched in a dual-stack or IPv6-only subnet" + type = bool + default = null +} + +variable "enable_volume_tags" { + description = "Whether to enable volume tags (if enabled it conflicts with root_block_device tags)" + type = bool + default = true +} + +variable "enclave_options_enabled" { + description = "Whether Nitro Enclaves will be enabled on the instance. Defaults to `false`" + type = bool + default = null +} + +variable "ephemeral_block_device" { + description = "Customize Ephemeral (also known as Instance Store) volumes on the instance" + type = map(object({ + device_name = optional(string) + no_device = optional(bool) + virtual_name = optional(string) + })) + default = null +} + +variable "force_destroy" { + description = "Destroys instance even if `disable_api_termination` or `disable_api_stop` is set to true. Once this parameter is set to true, a successful terraform apply run before a destroy is required to update this value in the resource state. Without a successful terraform apply after this parameter is set, this flag will have no effect. If setting this field in the same operation that would require replacing the instance or destroying the instance, this flag will not work. Additionally when importing an instance, a successful terraform apply is required to set this value in state before it will take effect on a destroy operation." + type = bool + default = null +} + +variable "get_password_data" { + description = "If true, wait for password data to become available and retrieve it" + type = bool + default = null +} + +variable "hibernation" { + description = "If true, the launched EC2 instance will support hibernation" + type = bool + default = null +} + +variable "host_id" { + description = "ID of a dedicated host that the instance will be assigned to. Use when an instance is to be launched on a specific dedicated host" + type = string + default = null +} + +variable "host_resource_group_arn" { + description = "ARN of the host resource group in which to launch the instances. If you specify an ARN, omit the `tenancy` parameter or set it to `host`" + type = string + default = null +} + variable "iam_instance_profile" { description = "IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile" type = string From bcbbc20797755eb0142ad33253da333a9c4032f5 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 15:46:46 +0100 Subject: [PATCH 18/30] add inputs i --- infrastructure/modules/ec2-instance/main.tf | 85 ++++++++++-------- .../modules/ec2-instance/variables.tf | 86 +++++++++++++++++++ 2 files changed, 135 insertions(+), 36 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 5a6be9d3..5ee92859 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -6,40 +6,53 @@ module "ec2_instance" { name = module.this.id tags = module.this.tags - ami = var.ami - ami_ssm_parameter = var.ami_ssm_parameter - associate_public_ip_address = var.associate_public_ip_address - availability_zone = var.availability_zone - capacity_reservation_specification = var.capacity_reservation_specification - cpu_credits = var.cpu_credits - cpu_options = var.cpu_options - create_eip = var.create_eip - create_iam_instance_profile = var.create_iam_instance_profile - create_security_group = var.create_security_group - create_spot_instance = var.create_spot_instance - disable_api_stop = var.disable_api_stop - disable_api_termination = var.disable_api_termination - ebs_optimized = var.ebs_optimized - ebs_volumes = var.ebs_volumes - eip_domain = var.eip_domain - eip_tags = var.eip_tags - enable_primary_ipv6 = var.enable_primary_ipv6 - enable_volume_tags = var.enable_volume_tags - enclave_options_enabled = var.enclave_options_enabled - ephemeral_block_device = var.ephemeral_block_device - force_destroy = var.force_destroy - get_password_data = var.get_password_data - hibernation = var.hibernation - host_id = var.host_id - host_resource_group_arn = var.host_resource_group_arn - iam_instance_profile = var.iam_instance_profile - instance_type = var.instance_type - key_name = var.key_name - metadata_options = var.metadata_options - monitoring = true - root_block_device = var.root_block_device - subnet_id = var.subnet_id - user_data = var.user_data - user_data_replace_on_change = false - vpc_security_group_ids = var.vpc_security_group_ids + ami = var.ami + ami_ssm_parameter = var.ami_ssm_parameter + associate_public_ip_address = var.associate_public_ip_address + availability_zone = var.availability_zone + capacity_reservation_specification = var.capacity_reservation_specification + cpu_credits = var.cpu_credits + cpu_options = var.cpu_options + create_eip = var.create_eip + create_iam_instance_profile = var.create_iam_instance_profile + create_security_group = var.create_security_group + create_spot_instance = var.create_spot_instance + disable_api_stop = var.disable_api_stop + disable_api_termination = var.disable_api_termination + ebs_optimized = var.ebs_optimized + ebs_volumes = var.ebs_volumes + eip_domain = var.eip_domain + eip_tags = var.eip_tags + enable_primary_ipv6 = var.enable_primary_ipv6 + enable_volume_tags = var.enable_volume_tags + enclave_options_enabled = var.enclave_options_enabled + ephemeral_block_device = var.ephemeral_block_device + force_destroy = var.force_destroy + get_password_data = var.get_password_data + hibernation = var.hibernation + host_id = var.host_id + host_resource_group_arn = var.host_resource_group_arn + iam_instance_profile = var.iam_instance_profile + iam_role_description = var.iam_role_description + iam_role_name = var.iam_role_name + iam_role_path = var.iam_role_path + iam_role_permissions_boundary = var.iam_role_permissions_boundary + iam_role_policies = var.iam_role_policies + iam_role_tags = var.iam_role_tags + iam_role_use_name_prefix = var.iam_role_use_name_prefix + ignore_ami_changes = var.ignore_ami_changes + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + instance_market_options = var.instance_market_options + instance_tags = var.instance_tags + instance_type = var.instance_type + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + key_name = var.key_name + metadata_options = var.metadata_options + monitoring = true + root_block_device = var.root_block_device + subnet_id = var.subnet_id + user_data = var.user_data + user_data_replace_on_change = false + vpc_security_group_ids = var.vpc_security_group_ids } diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index c23e932c..888811db 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -200,12 +200,98 @@ variable "iam_instance_profile" { default = null } +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_policies" { + description = "Policies attached to the IAM role" + type = map(string) + default = {} +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role/profile created" + type = map(string) + default = {} +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name` or `name`) is used as a prefix" + type = bool + default = true +} + +variable "ignore_ami_changes" { + description = "Whether changes to the AMI ID changes should be ignored by Terraform. Note - changing this value will result in the replacement of the instance" + type = bool + default = false +} + +variable "instance_initiated_shutdown_behavior" { + description = "Shutdown behavior for the instance. Amazon defaults this to stop for EBS-backed instances and terminate for instance-store instances. Cannot be set on instance-store instance" + type = string + default = null +} + +variable "instance_market_options" { + description = "The market (purchasing) option for the instance. If set, overrides the `create_spot_instance` variable" + type = object({ + market_type = optional(string) + spot_options = optional(object({ + instance_interruption_behavior = optional(string) + max_price = optional(string) + spot_instance_type = optional(string) + valid_until = optional(string) + })) + }) + default = null +} + +variable "instance_tags" { + description = "Additional tags for the instance" + type = map(string) + default = {} +} + variable "instance_type" { description = "The type of instance to start" type = string default = "t3.micro" } +variable "ipv6_address_count" { + description = "A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet" + type = number + default = null +} + +variable "ipv6_addresses" { + description = "Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface" + type = list(string) + default = null +} + variable "key_name" { description = "Name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource" type = string From 45df0dd6c277919586fd6980707a5b5085f44cef Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 15:53:47 +0100 Subject: [PATCH 19/30] add inputs.l-n --- infrastructure/modules/ec2-instance/main.tf | 5 ++- .../modules/ec2-instance/variables.tf | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 5ee92859..3c5e75d8 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -48,8 +48,11 @@ module "ec2_instance" { ipv6_address_count = var.ipv6_address_count ipv6_addresses = var.ipv6_addresses key_name = var.key_name + launch_template = var.launch_template + maintenance_options = var.maintenance_options metadata_options = var.metadata_options - monitoring = true + monitoring = var.monitoring + network_interface = var.network_interface root_block_device = var.root_block_device subnet_id = var.subnet_id user_data = var.user_data diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index 888811db..4033ef40 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -298,6 +298,24 @@ variable "key_name" { default = null } +variable "launch_template" { + description = "Specifies a Launch Template to configure the instance. Parameters configured on this resource will override the corresponding parameters in the Launch Template" + type = object({ + id = optional(string) + name = optional(string) + version = optional(string) + }) + default = null +} + +variable "maintenance_options" { + description = "The maintenance options for the instance" + type = object({ + auto_recovery = optional(string) + }) + default = null +} + variable "metadata_options" { description = "Customize the metadata options of the instance" type = object({ @@ -310,6 +328,23 @@ variable "metadata_options" { default = {} } +variable "monitoring" { + description = "If true, the launched EC2 instance will have detailed monitoring enabled" + type = bool + default = null +} + +variable "network_interface" { + description = "Customize network interfaces to be attached at instance boot time" + type = map(object({ + delete_on_termination = optional(bool) + device_index = optional(number) # Will fall back to use map key as device index + network_card_index = optional(number) + network_interface_id = string + })) + default = null +} + variable "root_block_device" { description = "Customize details about the root block device of the instance" type = object({ From 5dfc1a152888984b25a758cb0619df9dddaf489a Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 16:07:01 +0100 Subject: [PATCH 20/30] add inputs p-r --- infrastructure/modules/ec2-instance/main.tf | 6 +++ .../modules/ec2-instance/variables.tf | 40 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 3c5e75d8..e994ad49 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -53,6 +53,12 @@ module "ec2_instance" { metadata_options = var.metadata_options monitoring = var.monitoring network_interface = var.network_interface + placement_group = var.placement_group + placement_group_id = var.placement_group_id + placement_partition_number = var.placement_partition_number + private_dns_name_options = var.private_dns_name_options + private_ip = var.private_ip + region = var.region root_block_device = var.root_block_device subnet_id = var.subnet_id user_data = var.user_data diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index 4033ef40..f3d37879 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -345,6 +345,46 @@ variable "network_interface" { default = null } +variable "placement_group" { + description = "The Placement Group to start the instance in" + type = string + default = null +} + +variable "placement_group_id" { + description = "Placement Group ID to start the instance in" + type = string + default = null +} + +variable "placement_partition_number" { + description = "Number of the partition the instance is in. Valid only if the `aws_placement_group` resource's `strategy` argument is set to `partition`" + type = number + default = null +} + +variable "private_dns_name_options" { + description = "Customize the private DNS name options of the instance" + type = object({ + enable_resource_name_dns_aaaa_record = optional(bool) + enable_resource_name_dns_a_record = optional(bool) + hostname_type = optional(string) + }) + default = null +} + +variable "private_ip" { + description = "The private IP address to associate with the instance in a VPC" + type = string + default = null +} + +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null +} + variable "root_block_device" { description = "Customize details about the root block device of the instance" type = object({ From 2c342db58f544496bfcfe0d5e7691a6438fba4a7 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 16:20:10 +0100 Subject: [PATCH 21/30] add inputs s --- infrastructure/modules/ec2-instance/main.tf | 17 +++ .../modules/ec2-instance/variables.tf | 141 ++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index e994ad49..e338d4c4 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -60,7 +60,24 @@ module "ec2_instance" { private_ip = var.private_ip region = var.region root_block_device = var.root_block_device + secondary_network_interface = var.secondary_network_interface + secondary_private_ips = var.secondary_private_ips + security_group_description = var.security_group_description + security_group_egress_rules = var.security_group_egress_rules + security_group_ingress_rules = var.security_group_ingress_rules + security_group_name = var.security_group_name + security_group_tags = var.security_group_tags + security_group_use_name_prefix = var.security_group_use_name_prefix + security_group_vpc_id = var.security_group_vpc_id + source_dest_check = var.source_dest_check + spot_instance_interruption_behavior = var.spot_instance_interruption_behavior + spot_price = var.spot_price + spot_type = var.spot_type + spot_valid_from = var.spot_valid_from + spot_valid_until = var.spot_valid_until + spot_wait_for_fulfillment = var.spot_wait_for_fulfillment subnet_id = var.subnet_id + spot_launch_group = var.spot_launch_group user_data = var.user_data user_data_replace_on_change = false vpc_security_group_ids = var.vpc_security_group_ids diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index f3d37879..f52d38f7 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -400,6 +400,147 @@ variable "root_block_device" { default = null } +variable "secondary_network_interface" { + description = "Customize secondary network interfaces to be attached to the EC2 instance" + type = map(object({ + delete_on_termination = optional(bool) + device_index = optional(number) # Will fall back to use map key as device index + interface_type = optional(string) + network_card_index = number + private_ip_address_count = optional(number) + private_ip_addresses = optional(list(string)) + secondary_subnet_id = string + })) + default = null +} + +variable "secondary_private_ips" { + description = "A list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a `network_interface block`" + type = list(string) + default = null +} + +variable "security_group_description" { + description = "Description of the security group" + type = string + default = null +} + +variable "security_group_egress_rules" { + description = "Egress rules to add to the security group" + type = map(object({ + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(number) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(number) + })) + default = { + "ipv4_default" : { + "cidr_ipv4" : "0.0.0.0/0", + "description" : "Allow all IPv4 traffic", + "ip_protocol" : "-1" + }, + "ipv6_default" : { + "cidr_ipv6" : "::/0", + "description" : "Allow all IPv6 traffic", + "ip_protocol" : "-1" + } + } +} + +variable "security_group_ingress_rules" { + description = "Ingress rules to add to the security group" + type = map(object({ + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(number) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(number) + })) + default = null +} + +variable "security_group_name" { + description = "Name to use on security group created" + type = string + default = null +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`security_group_name` or `name`) is used as a prefix" + type = bool + default = true +} + +variable "security_group_vpc_id" { + description = "VPC ID to create the security group in. If not set, the security group will be created in the default VPC" + type = string + default = null +} + +variable "source_dest_check" { + description = "Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs" + type = bool + default = null +} + +variable "spot_instance_interruption_behavior" { + description = "Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate`" + type = string + default = null +} + +variable "spot_launch_group" { + description = "A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually" + type = string + default = null +} + +variable "spot_price" { + description = "The maximum price to request on the spot market. Defaults to on-demand price" + type = string + default = null +} + +variable "spot_type" { + description = "If set to one-time, after the instance is terminated, the spot request will be closed. Default `persistent`" + type = string + default = null +} + +variable "spot_valid_from" { + description = "The start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)" + type = string + default = null +} + +variable "spot_valid_until" { + description = "The end date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)" + type = string + default = null +} + +variable "spot_wait_for_fulfillment" { + description = "If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached" + type = bool + default = null +} + variable "subnet_id" { description = "The VPC Subnet ID to launch in" type = string From 15e0e4064097799388a2e03453ceccc32f112bbb Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 16:23:44 +0100 Subject: [PATCH 22/30] add inputs t-z --- infrastructure/modules/ec2-instance/main.tf | 6 +++- .../modules/ec2-instance/variables.tf | 34 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index e338d4c4..87bddd1f 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -78,7 +78,11 @@ module "ec2_instance" { spot_wait_for_fulfillment = var.spot_wait_for_fulfillment subnet_id = var.subnet_id spot_launch_group = var.spot_launch_group + tenancy = var.tenancy + timeouts = var.timeouts user_data = var.user_data - user_data_replace_on_change = false + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + volume_tags = var.volume_tags vpc_security_group_ids = var.vpc_security_group_ids } diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index f52d38f7..ab125271 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -547,12 +547,46 @@ variable "subnet_id" { default = null } +variable "tenancy" { + description = "The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host" + type = string + default = null +} + +variable "timeouts" { + description = "Define maximum timeout for creating, updating, and deleting EC2 instance resources" + type = object({ + create = optional(string) + update = optional(string) + delete = optional(string) + }) + default = null +} + variable "user_data" { description = "The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument" type = string default = null } +variable "user_data_base64" { + description = "Can be used instead of user_data to pass base64-encoded binary data directly. Use this instead of user_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption" + type = string + default = null +} + +variable "user_data_replace_on_change" { + description = "When used in combination with user_data or user_data_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set" + type = bool + default = null +} + +variable "volume_tags" { + description = "A mapping of tags to assign to the devices created by the instance at launch time" + type = map(string) + default = {} +} + variable "vpc_security_group_ids" { description = "List of VPC Security Group IDs to associate with" type = list(string) From 24bfc77e48a26c08888ae6768654d1eec5ad88ee Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 16:27:50 +0100 Subject: [PATCH 23/30] start on docs --- infrastructure/modules/ec2-instance/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/ec2-instance/README.md b/infrastructure/modules/ec2-instance/README.md index efc36a3c..c4f3b58a 100644 --- a/infrastructure/modules/ec2-instance/README.md +++ b/infrastructure/modules/ec2-instance/README.md @@ -1,4 +1,12 @@ -# DAVEH +# EC2-Instance + +NHS Screening wrapper around the community +[`terraform-aws-modules/ec2-instance/aws`](https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/latest) +module that consumes the shared `context.tf` for naming and tagging. + +## Usage + +TODO From 34a7304284533549142ba8052edec5a9d13f17f1 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 16:30:29 +0100 Subject: [PATCH 24/30] add usage --- infrastructure/modules/ec2-instance/README.md | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/ec2-instance/README.md b/infrastructure/modules/ec2-instance/README.md index c4f3b58a..17d52508 100644 --- a/infrastructure/modules/ec2-instance/README.md +++ b/infrastructure/modules/ec2-instance/README.md @@ -6,7 +6,73 @@ module that consumes the shared `context.tf` for naming and tagging. ## Usage -TODO +### Minimal instance + +```hcl +module "app_instance" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" + + service = "bcss" + project = "platform" + environment = "development" + name = "app" + + ami = "ami-0123456789abcdef0" + subnet_id = "subnet-0123456789abcdef0" + vpc_security_group_ids = ["sg-0123456789abcdef0"] +} +``` + +### Instance with IAM profile and SSH key + +```hcl +module "ops_instance" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" + + service = "bcss" + project = "operations" + environment = "test" + name = "ops" + + ami = "ami-0123456789abcdef0" + instance_type = "t3.small" + subnet_id = "subnet-0123456789abcdef0" + vpc_security_group_ids = ["sg-0123456789abcdef0"] + + iam_instance_profile = "ec2-ops-profile" + key_name = "screening-ops" +} +``` + +### Instance with custom root volume and user data + +```hcl +module "worker_instance" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" + + service = "bcss" + project = "batch" + environment = "prod" + name = "worker" + + ami = "ami-0123456789abcdef0" + instance_type = "t3.medium" + subnet_id = "subnet-0123456789abcdef0" + vpc_security_group_ids = ["sg-0123456789abcdef0"] + + root_block_device = { + encrypted = true + size = 50 + type = "gp3" + } + + user_data = <<-EOT + #!/bin/bash + set -euo pipefail + yum update -y + EOT +} +``` From a837faef73842bc6059209e089867079650b59ea Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 16 Jun 2026 16:32:25 +0100 Subject: [PATCH 25/30] tfdocs --- infrastructure/modules/ec2-instance/README.md | 99 ++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/infrastructure/modules/ec2-instance/README.md b/infrastructure/modules/ec2-instance/README.md index 17d52508..3adb10f4 100644 --- a/infrastructure/modules/ec2-instance/README.md +++ b/infrastructure/modules/ec2-instance/README.md @@ -102,44 +102,111 @@ No resources. | ---- | ----------- | ---- | ------- | :------: | | [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | | [ami](#input\_ami) | ID of AMI to use for the instance | `string` | `null` | no | +| [ami\_ssm\_parameter](#input\_ami\_ssm\_parameter) | SSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html | `string` | `"/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"` | no | | [application\_role](#input\_application\_role) | The role the application is performing | `string` | `"General"` | no | +| [associate\_public\_ip\_address](#input\_associate\_public\_ip\_address) | Whether to associate a public IP address with an instance in a VPC | `bool` | `null` | no | | [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [availability\_zone](#input\_availability\_zone) | The AZ to start the instance in | `string` | `null` | no | | [aws\_region](#input\_aws\_region) | The AWS region | `string` | `"eu-west-2"` | no | +| [capacity\_reservation\_specification](#input\_capacity\_reservation\_specification) | Describes an instance's Capacity Reservation targeting option |
object({
capacity_reservation_preference = optional(string)
capacity_reservation_target = optional(object({
capacity_reservation_id = optional(string)
capacity_reservation_resource_group_arn = optional(string)
}))
})
| `null` | no | | [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"project": null,
"regex_replace_chars": null,
"region": null,
"service": null,
"stack": null,
"tags": {},
"terraform_source": null,
"workspace": null
}
| no | +| [cpu\_credits](#input\_cpu\_credits) | The credit option for CPU usage (unlimited or standard) | `string` | `null` | no | +| [cpu\_options](#input\_cpu\_options) | Defines CPU options to apply to the instance at launch time. |
object({
amd_sev_snp = optional(string)
core_count = optional(number)
nested_virtualization = optional(string)
threads_per_core = optional(number)
})
| `null` | no | +| [create\_eip](#input\_create\_eip) | Determines whether a public EIP will be created and associated with the instance. | `bool` | `false` | no | +| [create\_iam\_instance\_profile](#input\_create\_iam\_instance\_profile) | Determines whether an IAM instance profile is created or to use an existing IAM instance profile | `bool` | `false` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines whether a security group will be created | `bool` | `true` | no | +| [create\_spot\_instance](#input\_create\_spot\_instance) | Depicts if the instance is a spot instance | `bool` | `false` | no | | [data\_classification](#input\_data\_classification) | Used to identify the data classification of the resource, e.g 1-5 | `string` | `"n/a"` | no | | [data\_type](#input\_data\_type) | The tag data\_type | `string` | `"None"` | no | | [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | | [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [disable\_api\_stop](#input\_disable\_api\_stop) | If true, enables EC2 Instance Stop Protection | `bool` | `null` | no | | [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 Instance Termination Protection | `bool` | `null` | no | | [ebs\_optimized](#input\_ebs\_optimized) | If true, the launched EC2 instance will be EBS-optimized | `bool` | `null` | no | +| [ebs\_volumes](#input\_ebs\_volumes) | Map of EBS volumes to attach to the instance |
map(object({
encrypted = optional(bool)
final_snapshot = optional(bool)
iops = optional(number)
kms_key_id = optional(string)
multi_attach_enabled = optional(bool)
outpost_arn = optional(string)
size = optional(number)
snapshot_id = optional(string)
tags = optional(map(string), {})
throughput = optional(number)
type = optional(string, "gp3")
volume_initialization_rate = optional(number)
# Attachment
device_name = optional(string) # Will fall back to use map key as device name
force_detach = optional(bool)
skip_destroy = optional(bool)
stop_instance_before_detaching = optional(bool)
}))
| `null` | no | +| [eip\_domain](#input\_eip\_domain) | Indicates if this EIP is for use in VPC | `string` | `"vpc"` | no | +| [eip\_tags](#input\_eip\_tags) | A map of additional tags to add to the EIP | `map(string)` | `{}` | no | +| [enable\_primary\_ipv6](#input\_enable\_primary\_ipv6) | Whether to assign a primary IPv6 Global Unicast Address (GUA) to the instance when launched in a dual-stack or IPv6-only subnet | `bool` | `null` | no | +| [enable\_volume\_tags](#input\_enable\_volume\_tags) | Whether to enable volume tags (if enabled it conflicts with root\_block\_device tags) | `bool` | `true` | no | | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [enclave\_options\_enabled](#input\_enclave\_options\_enabled) | Whether Nitro Enclaves will be enabled on the instance. Defaults to `false` | `bool` | `null` | no | | [environment](#input\_environment) | ID element. Usually used to indicate role, e.g. 'prd', 'dev', 'test', 'preprod', 'prod', 'uat' | `string` | `null` | no | +| [ephemeral\_block\_device](#input\_ephemeral\_block\_device) | Customize Ephemeral (also known as Instance Store) volumes on the instance |
map(object({
device_name = optional(string)
no_device = optional(bool)
virtual_name = optional(string)
}))
| `null` | no | +| [force\_destroy](#input\_force\_destroy) | Destroys instance even if `disable_api_termination` or `disable_api_stop` is set to true. Once this parameter is set to true, a successful terraform apply run before a destroy is required to update this value in the resource state. Without a successful terraform apply after this parameter is set, this flag will have no effect. If setting this field in the same operation that would require replacing the instance or destroying the instance, this flag will not work. Additionally when importing an instance, a successful terraform apply is required to set this value in state before it will take effect on a destroy operation. | `bool` | `null` | no | +| [get\_password\_data](#input\_get\_password\_data) | If true, wait for password data to become available and retrieve it | `bool` | `null` | no | +| [hibernation](#input\_hibernation) | If true, the launched EC2 instance will support hibernation | `bool` | `null` | no | +| [host\_id](#input\_host\_id) | ID of a dedicated host that the instance will be assigned to. Use when an instance is to be launched on a specific dedicated host | `string` | `null` | no | +| [host\_resource\_group\_arn](#input\_host\_resource\_group\_arn) | ARN of the host resource group in which to launch the instances. If you specify an ARN, omit the `tenancy` parameter or set it to `host` | `string` | `null` | no | | [iam\_instance\_profile](#input\_iam\_instance\_profile) | IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile | `string` | `null` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_policies](#input\_iam\_role\_policies) | Policies attached to the IAM role | `map(string)` | `{}` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role/profile created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name` or `name`) is used as a prefix | `bool` | `true` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [ignore\_ami\_changes](#input\_ignore\_ami\_changes) | Whether changes to the AMI ID changes should be ignored by Terraform. Note - changing this value will result in the replacement of the instance | `bool` | `false` | no | +| [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Shutdown behavior for the instance. Amazon defaults this to stop for EBS-backed instances and terminate for instance-store instances. Cannot be set on instance-store instance | `string` | `null` | no | +| [instance\_market\_options](#input\_instance\_market\_options) | The market (purchasing) option for the instance. If set, overrides the `create_spot_instance` variable |
object({
market_type = optional(string)
spot_options = optional(object({
instance_interruption_behavior = optional(string)
max_price = optional(string)
spot_instance_type = optional(string)
valid_until = optional(string)
}))
})
| `null` | no | +| [instance\_tags](#input\_instance\_tags) | Additional tags for the instance | `map(string)` | `{}` | no | | [instance\_type](#input\_instance\_type) | The type of instance to start | `string` | `"t3.micro"` | no | +| [ipv6\_address\_count](#input\_ipv6\_address\_count) | A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet | `number` | `null` | no | +| [ipv6\_addresses](#input\_ipv6\_addresses) | Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface | `list(string)` | `null` | no | | [key\_name](#input\_key\_name) | Name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource | `string` | `null` | no | | [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [launch\_template](#input\_launch\_template) | Specifies a Launch Template to configure the instance. Parameters configured on this resource will override the corresponding parameters in the Launch Template |
object({
id = optional(string)
name = optional(string)
version = optional(string)
})
| `null` | no | +| [maintenance\_options](#input\_maintenance\_options) | The maintenance options for the instance |
object({
auto_recovery = optional(string)
})
| `null` | no | | [metadata\_options](#input\_metadata\_options) | Customize the metadata options of the instance |
object({
http_endpoint = optional(string, "enabled")
http_protocol_ipv6 = optional(string)
http_put_response_hop_limit = optional(number, 1)
http_tokens = optional(string, "required")
instance_metadata_tags = optional(string)
})
| `{}` | no | +| [monitoring](#input\_monitoring) | If true, the launched EC2 instance will have detailed monitoring enabled | `bool` | `null` | no | | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [network\_interface](#input\_network\_interface) | Customize network interfaces to be attached at instance boot time |
map(object({
delete_on_termination = optional(bool)
device_index = optional(number) # Will fall back to use map key as device index
network_card_index = optional(number)
network_interface_id = string
}))
| `null` | no | | [on\_off\_pattern](#input\_on\_off\_pattern) | Used to turn resources on and off based on a time pattern | `string` | `"n/a"` | no | | [owner](#input\_owner) | The name and or NHS.net email address of the service owner | `string` | `"None"` | no | +| [placement\_group](#input\_placement\_group) | The Placement Group to start the instance in | `string` | `null` | no | +| [placement\_group\_id](#input\_placement\_group\_id) | Placement Group ID to start the instance in | `string` | `null` | no | +| [placement\_partition\_number](#input\_placement\_partition\_number) | Number of the partition the instance is in. Valid only if the `aws_placement_group` resource's `strategy` argument is set to `partition` | `number` | `null` | no | +| [private\_dns\_name\_options](#input\_private\_dns\_name\_options) | Customize the private DNS name options of the instance |
object({
enable_resource_name_dns_aaaa_record = optional(bool)
enable_resource_name_dns_a_record = optional(bool)
hostname_type = optional(string)
})
| `null` | no | +| [private\_ip](#input\_private\_ip) | The private IP address to associate with the instance in a VPC | `string` | `null` | no | | [project](#input\_project) | ID element. A project identifier, indicating the name or role of the project the resource is for, such as `website` or `api` | `string` | `null` | no | | [public\_facing](#input\_public\_facing) | Whether this resource is public facing | `bool` | `false` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | -| [region](#input\_region) | ID element \_(Rarely used, not included by default)\_. Usually an abbreviation of the selected AWS region e.g. 'uw2', 'ew2' or 'gbl' for resources like IAM roles that have no region | `string` | `null` | no | +| [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no | | [root\_block\_device](#input\_root\_block\_device) | Customize details about the root block device of the instance |
object({
delete_on_termination = optional(bool)
encrypted = optional(bool)
iops = optional(number)
kms_key_id = optional(string)
tags = optional(map(string))
throughput = optional(number)
size = optional(number)
type = optional(string)
})
| `null` | no | +| [secondary\_network\_interface](#input\_secondary\_network\_interface) | Customize secondary network interfaces to be attached to the EC2 instance |
map(object({
delete_on_termination = optional(bool)
device_index = optional(number) # Will fall back to use map key as device index
interface_type = optional(string)
network_card_index = number
private_ip_address_count = optional(number)
private_ip_addresses = optional(list(string))
secondary_subnet_id = string
}))
| `null` | no | +| [secondary\_private\_ips](#input\_secondary\_private\_ips) | A list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a `network_interface block` | `list(string)` | `null` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description of the security group | `string` | `null` | no | +| [security\_group\_egress\_rules](#input\_security\_group\_egress\_rules) | Egress rules to add to the security group |
map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(number)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(number)
}))
|
{
"ipv4_default": {
"cidr_ipv4": "0.0.0.0/0",
"description": "Allow all IPv4 traffic",
"ip_protocol": "-1"
},
"ipv6_default": {
"cidr_ipv6": "::/0",
"description": "Allow all IPv6 traffic",
"ip_protocol": "-1"
}
}
| no | +| [security\_group\_ingress\_rules](#input\_security\_group\_ingress\_rules) | Ingress rules to add to the security group |
map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(number)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(number)
}))
| `null` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name` or `name`) is used as a prefix | `bool` | `true` | no | +| [security\_group\_vpc\_id](#input\_security\_group\_vpc\_id) | VPC ID to create the security group in. If not set, the security group will be created in the default VPC | `string` | `null` | no | | [service](#input\_service) | ID element. Usually an abbreviation of your service directorate name, e.g. 'bcss' or 'csms', to help ensure generated IDs are globally unique | `string` | `null` | no | | [service\_category](#input\_service\_category) | The tag service\_category | `string` | `"n/a"` | no | +| [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `null` | no | +| [spot\_instance\_interruption\_behavior](#input\_spot\_instance\_interruption\_behavior) | Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate` | `string` | `null` | no | +| [spot\_launch\_group](#input\_spot\_launch\_group) | A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually | `string` | `null` | no | +| [spot\_price](#input\_spot\_price) | The maximum price to request on the spot market. Defaults to on-demand price | `string` | `null` | no | +| [spot\_type](#input\_spot\_type) | If set to one-time, after the instance is terminated, the spot request will be closed. Default `persistent` | `string` | `null` | no | +| [spot\_valid\_from](#input\_spot\_valid\_from) | The start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ) | `string` | `null` | no | +| [spot\_valid\_until](#input\_spot\_valid\_until) | The end date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ) | `string` | `null` | no | +| [spot\_wait\_for\_fulfillment](#input\_spot\_wait\_for\_fulfillment) | If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached | `bool` | `null` | no | | [stack](#input\_stack) | ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks` | `string` | `null` | no | | [subnet\_id](#input\_subnet\_id) | The VPC Subnet ID to launch in | `string` | `null` | no | | [tag\_version](#input\_tag\_version) | Used to identify the tagging version in use | `string` | `"1.0"` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenancy](#input\_tenancy) | The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host | `string` | `null` | no | | [terraform\_source](#input\_terraform\_source) | Source location to record in the Terraform\_source tag. Defaults to the caller module path when not set. | `string` | `null` | no | +| [timeouts](#input\_timeouts) | Define maximum timeout for creating, updating, and deleting EC2 instance resources |
object({
create = optional(string)
update = optional(string)
delete = optional(string)
})
| `null` | no | | [tool](#input\_tool) | The tool used to deploy the resource | `string` | `"Terraform"` | no | | [user\_data](#input\_user\_data) | The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument | `string` | `null` | no | +| [user\_data\_base64](#input\_user\_data\_base64) | Can be used instead of user\_data to pass base64-encoded binary data directly. Use this instead of user\_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption | `string` | `null` | no | +| [user\_data\_replace\_on\_change](#input\_user\_data\_replace\_on\_change) | When used in combination with user\_data or user\_data\_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set | `bool` | `null` | no | +| [volume\_tags](#input\_volume\_tags) | A mapping of tags to assign to the devices created by the instance at launch time | `map(string)` | `{}` | no | | [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of VPC Security Group IDs to associate with | `list(string)` | `[]` | no | | [workspace](#input\_workspace) | ID element. The Terraform workspace, to help ensure generated IDs are unique across workspaces | `string` | `null` | no | @@ -147,8 +214,36 @@ No resources. | Name | Description | | ---- | ----------- | +| [ami](#output\_ami) | AMI ID that was used to create the instance | +| [availability\_zone](#output\_availability\_zone) | The availability zone of the created instance | +| [capacity\_reservation\_specification](#output\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | +| [ebs\_block\_device](#output\_ebs\_block\_device) | EBS block device information | +| [ebs\_volumes](#output\_ebs\_volumes) | Map of EBS volumes created and their attributes | +| [ec2\_instance\_arn](#output\_ec2\_instance\_arn) | The ARN of the EC2 instance | | [ec2\_instance\_id](#output\_ec2\_instance\_id) | The ID of the EC2 instance | -| [private\_ip](#output\_private\_ip) | The private IP address of the EC2 instance | +| [ephemeral\_block\_device](#output\_ephemeral\_block\_device) | Ephemeral block device information | +| [iam\_instance\_profile\_arn](#output\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [iam\_instance\_profile\_id](#output\_iam\_instance\_profile\_id) | Instance profile's ID | +| [iam\_instance\_profile\_unique](#output\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [iam\_role\_arn](#output\_iam\_role\_arn) | The ARN specifying the IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | The name of the IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [instance\_state](#output\_instance\_state) | The state of the instance | +| [ipv6\_addresses](#output\_ipv6\_addresses) | The IPv6 address assigned to the instance, if applicable | +| [outpost\_arn](#output\_outpost\_arn) | The ARN of the Outpost the instance is assigned to | +| [password\_data](#output\_password\_data) | Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true | +| [primary\_network\_interface\_id](#output\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | +| [private\_dns](#output\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC | +| [private\_ip](#output\_private\_ip) | The private IP address assigned to the instance | +| [public\_dns](#output\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC | +| [public\_ip](#output\_public\_ip) | The public IP address assigned to the instance, if applicable. | +| [root\_block\_device](#output\_root\_block\_device) | Root block device information | +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group | +| [spot\_bid\_status](#output\_spot\_bid\_status) | The current bid status of the Spot Instance Request | +| [spot\_instance\_id](#output\_spot\_instance\_id) | The Instance ID (if any) that is currently fulfilling the Spot Instance request | +| [spot\_request\_state](#output\_spot\_request\_state) | The current request state of the Spot Instance Request | +| [tags\_all](#output\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block | From a2e258155ebae378a3eca62e85427d576cf396e1 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 17 Jun 2026 08:27:14 +0100 Subject: [PATCH 26/30] install go --- mise.lock | 32 ++++++++++++++++++++++++++++++++ mise.toml | 1 + 2 files changed, 33 insertions(+) diff --git a/mise.lock b/mise.lock index acdbd014..3ad1f5e4 100644 --- a/mise.lock +++ b/mise.lock @@ -32,6 +32,38 @@ url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8 checksum = "sha256:d29144deff3a68aa93ced33dddf84b7fdc26070add4aa0f4513094c8332afc4e" url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_windows_x64.zip" +[[tools.go]] +version = "1.26.4" +backend = "core:go" + +[tools.go."platforms.linux-arm64"] +checksum = "sha256:ef758ae7c6cf9267c9c0ef080b8965f453d89ab2d25d9eb22de4405925238768" +url = "https://dl.google.com/go/go1.26.4.linux-arm64.tar.gz" + +[tools.go."platforms.linux-arm64-musl"] +checksum = "sha256:ef758ae7c6cf9267c9c0ef080b8965f453d89ab2d25d9eb22de4405925238768" +url = "https://dl.google.com/go/go1.26.4.linux-arm64.tar.gz" + +[tools.go."platforms.linux-x64"] +checksum = "sha256:1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f" +url = "https://dl.google.com/go/go1.26.4.linux-amd64.tar.gz" + +[tools.go."platforms.linux-x64-musl"] +checksum = "sha256:1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f" +url = "https://dl.google.com/go/go1.26.4.linux-amd64.tar.gz" + +[tools.go."platforms.macos-arm64"] +checksum = "sha256:b62ad2b6d7d2464f12a5bcad7ff47f19d08325773b5efd21610e445a05a9bf53" +url = "https://dl.google.com/go/go1.26.4.darwin-arm64.tar.gz" + +[tools.go."platforms.macos-x64"] +checksum = "sha256:05dc9b5f9997744520aaebb3d5deaa7c755371aebbfb7f97c2511a9f3367538d" +url = "https://dl.google.com/go/go1.26.4.darwin-amd64.tar.gz" + +[tools.go."platforms.windows-x64"] +checksum = "sha256:3ca8fb4630b07c419cbdd51f754e31363cfcfb83b3a5354d9e895c90be2cc345" +url = "https://dl.google.com/go/go1.26.4.windows-amd64.zip" + [[tools."go:github.com/hashicorp/terraform-config-inspect"]] version = "0.0.0-20260224005459-813a97530220" backend = "go:github.com/hashicorp/terraform-config-inspect" diff --git a/mise.toml b/mise.toml index ecde9ddc..fc942f93 100644 --- a/mise.toml +++ b/mise.toml @@ -18,3 +18,4 @@ nodejs = "24.16.0" # Go-based tools using backend syntax "go:github.com/hashicorp/terraform-config-inspect" = "latest" +go = "1.26.4" From ec3ef6e1536a0c4a06237c558231fe496bef3f40 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 17 Jun 2026 08:42:16 +0100 Subject: [PATCH 27/30] docs(ec2-instance): fix case, indentation --- infrastructure/modules/ec2-instance/README.md | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/infrastructure/modules/ec2-instance/README.md b/infrastructure/modules/ec2-instance/README.md index 3adb10f4..cecdd43a 100644 --- a/infrastructure/modules/ec2-instance/README.md +++ b/infrastructure/modules/ec2-instance/README.md @@ -10,37 +10,37 @@ module that consumes the shared `context.tf` for naming and tagging. ```hcl module "app_instance" { - source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" - service = "bcss" - project = "platform" - environment = "development" - name = "app" + service = "bcss" + project = "platform" + environment = "development" + name = "app" - ami = "ami-0123456789abcdef0" - subnet_id = "subnet-0123456789abcdef0" - vpc_security_group_ids = ["sg-0123456789abcdef0"] + ami = "ami-0123456789abcdef0" + subnet_id = "subnet-0123456789abcdef0" + vpc_security_group_ids = ["sg-0123456789abcdef0"] } ``` -### Instance with IAM profile and SSH key +### Instance with iam profile and SSH key ```hcl module "ops_instance" { - source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" - service = "bcss" - project = "operations" - environment = "test" - name = "ops" + service = "bcss" + project = "operations" + environment = "test" + name = "ops" - ami = "ami-0123456789abcdef0" - instance_type = "t3.small" - subnet_id = "subnet-0123456789abcdef0" - vpc_security_group_ids = ["sg-0123456789abcdef0"] + ami = "ami-0123456789abcdef0" + instance_type = "t3.small" + subnet_id = "subnet-0123456789abcdef0" + vpc_security_group_ids = ["sg-0123456789abcdef0"] - iam_instance_profile = "ec2-ops-profile" - key_name = "screening-ops" + iam_instance_profile = "ec2-ops-profile" + key_name = "screening-ops" } ``` @@ -48,29 +48,29 @@ module "ops_instance" { ```hcl module "worker_instance" { - source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" - - service = "bcss" - project = "batch" - environment = "prod" - name = "worker" - - ami = "ami-0123456789abcdef0" - instance_type = "t3.medium" - subnet_id = "subnet-0123456789abcdef0" - vpc_security_group_ids = ["sg-0123456789abcdef0"] - - root_block_device = { - encrypted = true - size = 50 - type = "gp3" - } - - user_data = <<-EOT - #!/bin/bash - set -euo pipefail - yum update -y - EOT + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/ec2-instance?ref=main" + + service = "bcss" + project = "batch" + environment = "prod" + name = "worker" + + ami = "ami-0123456789abcdef0" + instance_type = "t3.medium" + subnet_id = "subnet-0123456789abcdef0" + vpc_security_group_ids = ["sg-0123456789abcdef0"] + + root_block_device = { + encrypted = true + size = 50 + type = "gp3" + } + + user_data = <<-EOT + #!/bin/bash + set -euo pipefail + yum update -y + EOT } ``` From 41a2de3b488a95eda31ed8e1e9de69f2fa519795 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 17 Jun 2026 08:49:39 +0100 Subject: [PATCH 28/30] fix(ec2-instance): rm duplicate region variable --- infrastructure/modules/ec2-instance/main.tf | 2 +- infrastructure/modules/ec2-instance/variables.tf | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/infrastructure/modules/ec2-instance/main.tf b/infrastructure/modules/ec2-instance/main.tf index 87bddd1f..8b0f48e2 100644 --- a/infrastructure/modules/ec2-instance/main.tf +++ b/infrastructure/modules/ec2-instance/main.tf @@ -58,7 +58,7 @@ module "ec2_instance" { placement_partition_number = var.placement_partition_number private_dns_name_options = var.private_dns_name_options private_ip = var.private_ip - region = var.region + region = var.aws_region root_block_device = var.root_block_device secondary_network_interface = var.secondary_network_interface secondary_private_ips = var.secondary_private_ips diff --git a/infrastructure/modules/ec2-instance/variables.tf b/infrastructure/modules/ec2-instance/variables.tf index ab125271..6202002c 100644 --- a/infrastructure/modules/ec2-instance/variables.tf +++ b/infrastructure/modules/ec2-instance/variables.tf @@ -379,12 +379,6 @@ variable "private_ip" { default = null } -variable "region" { - description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" - type = string - default = null -} - variable "root_block_device" { description = "Customize details about the root block device of the instance" type = object({ From 3cd0e1545a4211a0b4f35a8ad5bb965e87c197f2 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 17 Jun 2026 08:53:33 +0100 Subject: [PATCH 29/30] chore: keep .tool-versions and mise.toml in sync --- .tool-versions | 1 + mise.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 158277de..1feda587 100644 --- a/.tool-versions +++ b/.tool-versions @@ -11,6 +11,7 @@ gitleaks 8.30.1 shellcheck 0.11.0 jq 1.7.1 nodejs 24.16.0 +go 1.26.4 # ============================================================================== # The section below is reserved for Docker image versions. diff --git a/mise.toml b/mise.toml index fc942f93..aed809d7 100644 --- a/mise.toml +++ b/mise.toml @@ -15,7 +15,7 @@ gitleaks = "8.30.1" shellcheck = "0.11.0" jq = "1.7.1" nodejs = "24.16.0" +go = "1.26.4" # Go-based tools using backend syntax "go:github.com/hashicorp/terraform-config-inspect" = "latest" -go = "1.26.4" From 801475e4da9f1248492453625f2ec870b89cae78 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 17 Jun 2026 11:11:30 +0100 Subject: [PATCH 30/30] =?UTF-8?q?chore:=20revert=20tooling=20changes;=20th?= =?UTF-8?q?ey=E2=80=99re=20coming=20in=20a=20separate=20PR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tool-versions | 1 - mise.lock | 32 -------------------------------- mise.toml | 1 - 3 files changed, 34 deletions(-) diff --git a/.tool-versions b/.tool-versions index 1feda587..158277de 100644 --- a/.tool-versions +++ b/.tool-versions @@ -11,7 +11,6 @@ gitleaks 8.30.1 shellcheck 0.11.0 jq 1.7.1 nodejs 24.16.0 -go 1.26.4 # ============================================================================== # The section below is reserved for Docker image versions. diff --git a/mise.lock b/mise.lock index 3ad1f5e4..acdbd014 100644 --- a/mise.lock +++ b/mise.lock @@ -32,38 +32,6 @@ url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8 checksum = "sha256:d29144deff3a68aa93ced33dddf84b7fdc26070add4aa0f4513094c8332afc4e" url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_windows_x64.zip" -[[tools.go]] -version = "1.26.4" -backend = "core:go" - -[tools.go."platforms.linux-arm64"] -checksum = "sha256:ef758ae7c6cf9267c9c0ef080b8965f453d89ab2d25d9eb22de4405925238768" -url = "https://dl.google.com/go/go1.26.4.linux-arm64.tar.gz" - -[tools.go."platforms.linux-arm64-musl"] -checksum = "sha256:ef758ae7c6cf9267c9c0ef080b8965f453d89ab2d25d9eb22de4405925238768" -url = "https://dl.google.com/go/go1.26.4.linux-arm64.tar.gz" - -[tools.go."platforms.linux-x64"] -checksum = "sha256:1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f" -url = "https://dl.google.com/go/go1.26.4.linux-amd64.tar.gz" - -[tools.go."platforms.linux-x64-musl"] -checksum = "sha256:1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f" -url = "https://dl.google.com/go/go1.26.4.linux-amd64.tar.gz" - -[tools.go."platforms.macos-arm64"] -checksum = "sha256:b62ad2b6d7d2464f12a5bcad7ff47f19d08325773b5efd21610e445a05a9bf53" -url = "https://dl.google.com/go/go1.26.4.darwin-arm64.tar.gz" - -[tools.go."platforms.macos-x64"] -checksum = "sha256:05dc9b5f9997744520aaebb3d5deaa7c755371aebbfb7f97c2511a9f3367538d" -url = "https://dl.google.com/go/go1.26.4.darwin-amd64.tar.gz" - -[tools.go."platforms.windows-x64"] -checksum = "sha256:3ca8fb4630b07c419cbdd51f754e31363cfcfb83b3a5354d9e895c90be2cc345" -url = "https://dl.google.com/go/go1.26.4.windows-amd64.zip" - [[tools."go:github.com/hashicorp/terraform-config-inspect"]] version = "0.0.0-20260224005459-813a97530220" backend = "go:github.com/hashicorp/terraform-config-inspect" diff --git a/mise.toml b/mise.toml index aed809d7..ecde9ddc 100644 --- a/mise.toml +++ b/mise.toml @@ -15,7 +15,6 @@ gitleaks = "8.30.1" shellcheck = "0.11.0" jq = "1.7.1" nodejs = "24.16.0" -go = "1.26.4" # Go-based tools using backend syntax "go:github.com/hashicorp/terraform-config-inspect" = "latest"