From c4e43030abb690fec2be50da872f0fcf6e856874 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 10 Sep 2026 11:12:39 +0100 Subject: [PATCH 01/64] chore(eventbridge): create directory --- infrastructure/modules/eventbridge/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 infrastructure/modules/eventbridge/README.md diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md new file mode 100644 index 00000000..9df56b73 --- /dev/null +++ b/infrastructure/modules/eventbridge/README.md @@ -0,0 +1,14 @@ +# EventBridge + +NHS Screening wrapper around the community +[`terraform-aws-modules/terraform-aws-eventbridge`](https://registry.terraform.io/modules/terraform-aws-modules/eventbridge/aws/4.3.2) +module that consumes the shared `context.tf` for naming and tagging. + +DAVEH + + + + + + + From f0e2d5aee210058df482c5e58448f2f1815db54a Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 10 Sep 2026 11:22:10 +0100 Subject: [PATCH 02/64] chore: add versions --- .github/dependabot.yaml | 1 + README.md | 1 + .../modules/eventbridge/.terraform.lock.hcl | 30 +++++++++++++++++++ infrastructure/modules/eventbridge/README.md | 26 ++++++++++++++++ infrastructure/modules/eventbridge/main.tf | 0 infrastructure/modules/eventbridge/outputs.tf | 0 .../modules/eventbridge/variables.tf | 0 .../modules/eventbridge/versions.tf | 10 +++++++ 8 files changed, 68 insertions(+) create mode 100644 infrastructure/modules/eventbridge/.terraform.lock.hcl create mode 100644 infrastructure/modules/eventbridge/main.tf create mode 100644 infrastructure/modules/eventbridge/outputs.tf create mode 100644 infrastructure/modules/eventbridge/variables.tf create mode 100644 infrastructure/modules/eventbridge/versions.tf diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 6b252b9e..acb49f6f 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -53,6 +53,7 @@ updates: - "infrastructure/modules/ecs-service" - "infrastructure/modules/efs" - "infrastructure/modules/elasticache" + - "infrastructure/modules/eventbridge" - "infrastructure/modules/github-config" - "infrastructure/modules/guardduty" - "infrastructure/modules/iam" diff --git a/README.md b/README.md index ff744ad4..3435d779 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,7 @@ Rules: | `ecs-service` | terraform-aws-modules/ecs/aws//modules/service | ECS service and task definition | | `efs` | terraform-aws-modules/efs/aws | EFS file system with access points and mount targets | | `elasticache` | — | ElastiCache cluster (Redis/Memcached) | +| `eventbridge` | — | — | | `github-config` | — | GitHub OIDC provider and runner configuration | | `guardduty` | — | GuardDuty threat detection | | `iam` | terraform-aws-modules/iam/aws | IAM policies and roles | diff --git a/infrastructure/modules/eventbridge/.terraform.lock.hcl b/infrastructure/modules/eventbridge/.terraform.lock.hcl new file mode 100644 index 00000000..2f63013b --- /dev/null +++ b/infrastructure/modules/eventbridge/.terraform.lock.hcl @@ -0,0 +1,30 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.64.0" + constraints = ">= 6.42.0" + hashes = [ + "h1:/wtl8WUDUuXhCa2iiZA4Qm2uwo3sAi0zegrHiC7YiJ0=", + "h1:2fTLxzUDmp/KVIHbIeLTB4bIzWHx8E6Dw+1ALLUi+Yw=", + "h1:4siTahLyzGh4BoMQcL7VXeL/mn8iR/zKv93NyhQob+0=", + "h1:EEWCXlg69fty/Qi+kehrREnVEaWdKnLlVon542b6nxQ=", + "h1:wXARLY+IeQ7ufYxCLTPCwToWGMRvOpiOTfJS97iwUzI=", + "zh:07172315d67bc9781240272759cdfc7bd32b7e72384a56862c2c1da3cca99a81", + "zh:154ce7d2659de9a59ddfe96d7cab41a9ddc2cb267a7d4bcdf4e737ff2ffdec06", + "zh:17324d4335a7a7ac01cc23eded530775606680ff53b47cb74a3cb95d1121f836", + "zh:307ab92324ec5a61b124881ab8cac1d9e316f4527dfd0e1b59794c229407eb4e", + "zh:31e25f1903661332e36a95283042dd3ec50b47c186db00663fbd976a11e6a6b2", + "zh:3311d9f3bd12a24886027dbe73859dcd1e67bd0e3046227a338cf2c7ca04d18e", + "zh:37916156a3aac3b29be3acebd15d53145ea4ab5d4aaa825eaebe75481fa00500", + "zh:4158cb8c38b3ac6aa98eb15935ec6bd7c30838d85d2b00acc9812df8382ae908", + "zh:5bfb9499c66d9db5b34dc5c60f426a1ab1baa5457ce2aefebca826a9c3f92fb0", + "zh:6eb29ead5a4aca3b1f35812e7e8c75419180e1928e479b458f206861277736db", + "zh:7a82b6dd0c0cdef8045a4adfbddd36acb86b6b23fcbed8e189c2d71f7dc4a502", + "zh:9556bd792032c3f7e73ea4dd08cec88dc1327f5a4a57d79c30ba844ae2b9a3c0", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:c5234180464cb800c83a41f57462742b802c150ad7d4417626fcd9cb511c01d2", + "zh:cd776b83b1f7b36635957350afe7ce28ba4e4ea3a5e2deb00d13dbd3b35d9d40", + "zh:fb583a7b791c6f915b86573d04f05ddbf7f1a5e4120c5d8a7450a3086c1225c4", + ] +} diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 9df56b73..c4e39f5f 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -9,6 +9,32 @@ DAVEH +## Requirements + +| Name | Version | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.13 | +| [aws](#requirement\_aws) | >= 6.42 | + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +No outputs. diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf new file mode 100644 index 00000000..e69de29b diff --git a/infrastructure/modules/eventbridge/outputs.tf b/infrastructure/modules/eventbridge/outputs.tf new file mode 100644 index 00000000..e69de29b diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/infrastructure/modules/eventbridge/versions.tf b/infrastructure/modules/eventbridge/versions.tf new file mode 100644 index 00000000..cb30fe5c --- /dev/null +++ b/infrastructure/modules/eventbridge/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.42" + } + } +} From d48fa5fb45c18e48774657381285187f2e05b92b Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 10 Sep 2026 11:58:08 +0100 Subject: [PATCH 03/64] chore: start main --- infrastructure/modules/eventbridge/README.md | 4 ++- infrastructure/modules/eventbridge/main.tf | 26 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index c4e39f5f..405b89dd 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -22,7 +22,9 @@ No providers. ## Modules -No modules. +| Name | Source | Version | +| ---- | ------ | ------- | +| [eventbridge](#module\_eventbridge) | git::https://github.com/terraform-aws-modules/terraform-aws-eventbridge.git | f9934726324c988f823682884b4fa003586a7b6f | ## Resources diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index e69de29b..dc627bbf 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -0,0 +1,26 @@ +module "eventbridge" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-eventbridge.git?ref=f9934726324c988f823682884b4fa003586a7b6f" # v4.3.2 + + # Using a separate EventBridge bus per workspace would remove collisions for resources scoped to a bus: + # + # - rules keys and rule names + # - targets associated with those rules + # - archives + # - EventBridge permissions + # - bus-specific log-delivery associations + # + # However, these remain account/region-wide and still need workspace-unique names: + # + # - IAM role role_name + # - Generated IAM policy names + # - EventBridge connections + # - API destinations + # - Scheduler schedule groups and schedules + # - EventBridge Pipes + # - Log-delivery source and destination names + # + # The bus itself also needs a workspace-unique bus_name. + # DAVEH: rm above comment when actioned + + # DAVEH: add attributes +} From a663c3bba35902bb8bf23bc1e45c5dcadd50b751 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Fri, 11 Sep 2026 11:49:57 +0100 Subject: [PATCH 04/64] docs: start notes --- infrastructure/modules/eventbridge/NOTES.md | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 infrastructure/modules/eventbridge/NOTES.md diff --git a/infrastructure/modules/eventbridge/NOTES.md b/infrastructure/modules/eventbridge/NOTES.md new file mode 100644 index 00000000..cd220065 --- /dev/null +++ b/infrastructure/modules/eventbridge/NOTES.md @@ -0,0 +1,108 @@ +# Implementation Notes + +DAVEH: rm or tidy + +These notes are about v4.3.2 of the underlying `terraform-aws-modules/terraform-aws-eventbridge` community module. + +The main on/off switch is called `create`. + +## Underlying resources + +### `aws_cloudwatch_event_bus.this` + +- data/resource +- gated by `var.create_bus`; can otherwise add to an existing bus +- named by `var.bus_name` + - default account bus (created by AWS) is named `default` +- events on the bus are encrypted by `var.kms_key_identifier` + - can be the key ARN, KeyId, key alias, or key alias ARN + +### `aws_cloudwatch_event_api_destination.this` + +### `aws_cloudwatch_event_archive.this` + +### `aws_cloudwatch_event_connection.this` + +### `aws_cloudwatch_event_permission.this` + +### `aws_cloudwatch_event_rule.this` + +### `aws_cloudwatch_event_target.this` + +### `aws_cloudwatch_log_delivery.this` + +### `aws_cloudwatch_log_delivery_destination.this` + +### `aws_cloudwatch_log_delivery_source.this` + +### `aws_iam_policy.additional_inline` + +### `aws_iam_policy.additional_json` + +### `aws_iam_policy.additional_jsons` + +### `aws_iam_policy.api_destination` + +### `aws_iam_policy.cloudwatch` + +### `aws_iam_policy.ecs` + +### `aws_iam_policy.kinesis` + +### `aws_iam_policy.kinesis_firehose` + +### `aws_iam_policy.lambda` + +### `aws_iam_policy.service` + +### `aws_iam_policy.sfn` + +### `aws_iam_policy.sns` + +### `aws_iam_policy.sqs` + +### `aws_iam_policy.tracing` + +### `aws_iam_policy_attachment.additional_inline` + +### `aws_iam_policy_attachment.additional_json` + +### `aws_iam_policy_attachment.additional_jsons` + +### `aws_iam_policy_attachment.api_destination` + +### `aws_iam_policy_attachment.cloudwatch` + +### `aws_iam_policy_attachment.ecs` + +### `aws_iam_policy_attachment.kinesis` + +### `aws_iam_policy_attachment.kinesis_firehose` + +### `aws_iam_policy_attachment.lambda` + +### `aws_iam_policy_attachment.service` + +### `aws_iam_policy_attachment.sfn` + +### `aws_iam_policy_attachment.sns` + +### `aws_iam_policy_attachment.sqs` + +### `aws_iam_policy_attachment.tracing` + +### `aws_iam_role.eventbridge` + +### `aws_iam_role.eventbridge_pipe` + +### `aws_iam_role_policy_attachment.additional_many` + +### `aws_iam_role_policy_attachment.additional_one` + +### `aws_pipes_pipe.this` + +### `aws_scheduler_schedule.this` + +### `aws_scheduler_schedule_group.this` + +### `aws_schemas_discoverer.this` From cb2199afc8672f4be7594943cf066307089cbe14 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 15 Sep 2026 10:58:58 +0100 Subject: [PATCH 05/64] docs: continue notes --- infrastructure/modules/eventbridge/NOTES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/infrastructure/modules/eventbridge/NOTES.md b/infrastructure/modules/eventbridge/NOTES.md index cd220065..1fd5af0b 100644 --- a/infrastructure/modules/eventbridge/NOTES.md +++ b/infrastructure/modules/eventbridge/NOTES.md @@ -19,8 +19,18 @@ The main on/off switch is called `create`. ### `aws_cloudwatch_event_api_destination.this` +- resource +- gated by `var.create_api_destinations` +- named by `var.api_destinations` keys + - modified if `var.append_destination_postfix` is true + ### `aws_cloudwatch_event_archive.this` +- resource +- gated by `var.create_archives` +- named by `name` field of `var.archives` sub-value, falling back to `var.archives` key +- encrypted using `kms_key_identifier` field of `var.archives` sub-value, falling back to unencrypted + ### `aws_cloudwatch_event_connection.this` ### `aws_cloudwatch_event_permission.this` From 9a33382a9133566a5597969a55f3926637892138 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 15 Sep 2026 13:06:10 +0100 Subject: [PATCH 06/64] docs: more notes --- infrastructure/modules/eventbridge/NOTES.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/infrastructure/modules/eventbridge/NOTES.md b/infrastructure/modules/eventbridge/NOTES.md index 1fd5af0b..5c84a3f2 100644 --- a/infrastructure/modules/eventbridge/NOTES.md +++ b/infrastructure/modules/eventbridge/NOTES.md @@ -33,10 +33,24 @@ The main on/off switch is called `create`. ### `aws_cloudwatch_event_connection.this` +- resource +- gated by `var.create_connections` +- named by `Name` field of `var.connections` sub-value + - modified if `var.append_connection_postfix` is true +- encrypted using `kms_key_identifier` field of `var.archives` sub-value, falling back to unencrypted + ### `aws_cloudwatch_event_permission.this` +- resource +- gated by `var.create_permissions` + ### `aws_cloudwatch_event_rule.this` +- resource +- gated by `var.create_rules` +- named by `var.rules` key + - modified if `var.append_rule_postfix` is true + ### `aws_cloudwatch_event_target.this` ### `aws_cloudwatch_log_delivery.this` From 5adbe350bd12219ec658dcabb6b0ee9da9e6f1f5 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 15 Sep 2026 13:29:47 +0100 Subject: [PATCH 07/64] docs: document name uniqueness requirements --- infrastructure/modules/eventbridge/NOTES.md | 86 +++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/infrastructure/modules/eventbridge/NOTES.md b/infrastructure/modules/eventbridge/NOTES.md index 5c84a3f2..38967579 100644 --- a/infrastructure/modules/eventbridge/NOTES.md +++ b/infrastructure/modules/eventbridge/NOTES.md @@ -12,6 +12,7 @@ The main on/off switch is called `create`. - data/resource - gated by `var.create_bus`; can otherwise add to an existing bus +- name status: named and must be unique per AWS account and region - named by `var.bus_name` - default account bus (created by AWS) is named `default` - events on the bus are encrypted by `var.kms_key_identifier` @@ -21,6 +22,7 @@ The main on/off switch is called `create`. - resource - gated by `var.create_api_destinations` +- name status: named and must be unique per AWS account and region - named by `var.api_destinations` keys - modified if `var.append_destination_postfix` is true @@ -28,6 +30,7 @@ The main on/off switch is called `create`. - resource - gated by `var.create_archives` +- name status: named and must be unique per event bus - named by `name` field of `var.archives` sub-value, falling back to `var.archives` key - encrypted using `kms_key_identifier` field of `var.archives` sub-value, falling back to unencrypted @@ -35,6 +38,7 @@ The main on/off switch is called `create`. - resource - gated by `var.create_connections` +- name status: named and must be unique per AWS account and region - named by `Name` field of `var.connections` sub-value - modified if `var.append_connection_postfix` is true - encrypted using `kms_key_identifier` field of `var.archives` sub-value, falling back to unencrypted @@ -43,90 +47,172 @@ The main on/off switch is called `create`. - resource - gated by `var.create_permissions` +- name status: unnamed; its statement ID must be unique on the event bus ### `aws_cloudwatch_event_rule.this` - resource - gated by `var.create_rules` +- name status: named and must be unique per event bus - named by `var.rules` key - modified if `var.append_rule_postfix` is true ### `aws_cloudwatch_event_target.this` +- name status: unnamed; its target ID must be unique per rule on an event bus + ### `aws_cloudwatch_log_delivery.this` +- name status: unnamed; one delivery is allowed per source-destination pair + ### `aws_cloudwatch_log_delivery_destination.this` +- name status: named and must be unique per AWS account + ### `aws_cloudwatch_log_delivery_source.this` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.additional_inline` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.additional_json` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.additional_jsons` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.api_destination` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.cloudwatch` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.ecs` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.kinesis` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.kinesis_firehose` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.lambda` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.service` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.sfn` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.sns` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.sqs` +- name status: named and must be unique per AWS account + ### `aws_iam_policy.tracing` +- name status: named and must be unique per AWS account + ### `aws_iam_policy_attachment.additional_inline` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.additional_json` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.additional_jsons` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.api_destination` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.cloudwatch` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.ecs` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.kinesis` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.kinesis_firehose` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.lambda` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.service` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.sfn` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.sns` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.sqs` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_policy_attachment.tracing` +- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs + ### `aws_iam_role.eventbridge` +- name status: named and must be unique per AWS account + ### `aws_iam_role.eventbridge_pipe` +- name status: named and must be unique per AWS account + ### `aws_iam_role_policy_attachment.additional_many` +- name status: unnamed; this is an attachment relationship identified by the role and policy ARNs + ### `aws_iam_role_policy_attachment.additional_one` +- name status: unnamed; this is an attachment relationship identified by the role and policy ARNs + ### `aws_pipes_pipe.this` +- name status: named and must be unique per AWS account and region + ### `aws_scheduler_schedule.this` +- name status: named and must be unique per schedule group + ### `aws_scheduler_schedule_group.this` +- name status: named and must be unique per AWS account and region + ### `aws_schemas_discoverer.this` + +- name status: named and must be unique per AWS account and region From a904d8e8bcb02a3b4c1034a4c0e56763934f3e27 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 15 Sep 2026 14:51:36 +0100 Subject: [PATCH 08/64] docs: document encryption key inputs --- infrastructure/modules/eventbridge/NOTES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/NOTES.md b/infrastructure/modules/eventbridge/NOTES.md index 38967579..634239b5 100644 --- a/infrastructure/modules/eventbridge/NOTES.md +++ b/infrastructure/modules/eventbridge/NOTES.md @@ -41,7 +41,7 @@ The main on/off switch is called `create`. - name status: named and must be unique per AWS account and region - named by `Name` field of `var.connections` sub-value - modified if `var.append_connection_postfix` is true -- encrypted using `kms_key_identifier` field of `var.archives` sub-value, falling back to unencrypted +- encrypted using `kms_key_identifier` field of `var.connections` sub-value, falling back to unencrypted ### `aws_cloudwatch_event_permission.this` @@ -204,10 +204,12 @@ The main on/off switch is called `create`. ### `aws_pipes_pipe.this` - name status: named and must be unique per AWS account and region +- encrypted using `kms_key_identifier` field of `var.pipes` sub-value, falling back to unencrypted ### `aws_scheduler_schedule.this` - name status: named and must be unique per schedule group +- encrypted using `kms_key_arn` field of `var.schedules` sub-value, falling back to unencrypted ### `aws_scheduler_schedule_group.this` From 9415eb8e7f73203996207bf8a0a13d9086b1b2ac Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 08:05:42 +0100 Subject: [PATCH 09/64] docs: rm comment that is expanded on by notes --- infrastructure/modules/eventbridge/main.tf | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index dc627bbf..daf911c8 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -1,26 +1,5 @@ module "eventbridge" { source = "git::https://github.com/terraform-aws-modules/terraform-aws-eventbridge.git?ref=f9934726324c988f823682884b4fa003586a7b6f" # v4.3.2 - # Using a separate EventBridge bus per workspace would remove collisions for resources scoped to a bus: - # - # - rules keys and rule names - # - targets associated with those rules - # - archives - # - EventBridge permissions - # - bus-specific log-delivery associations - # - # However, these remain account/region-wide and still need workspace-unique names: - # - # - IAM role role_name - # - Generated IAM policy names - # - EventBridge connections - # - API destinations - # - Scheduler schedule groups and schedules - # - EventBridge Pipes - # - Log-delivery source and destination names - # - # The bus itself also needs a workspace-unique bus_name. - # DAVEH: rm above comment when actioned - # DAVEH: add attributes } From 83326d6bcb08abe63c254e9c216830fa496e72a5 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 08:17:27 +0100 Subject: [PATCH 10/64] chore: add context --- infrastructure/modules/eventbridge/README.md | 35 +- infrastructure/modules/eventbridge/context.tf | 376 ++++++++++++++++++ infrastructure/modules/eventbridge/main.tf | 3 + 3 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 infrastructure/modules/eventbridge/context.tf diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 405b89dd..0de5a75b 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -25,6 +25,7 @@ No providers. | Name | Source | Version | | ---- | ------ | ------- | | [eventbridge](#module\_eventbridge) | git::https://github.com/terraform-aws-modules/terraform-aws-eventbridge.git | f9934726324c988f823682884b4fa003586a7b6f | +| [this](#module\_this) | ../tags | n/a | ## Resources @@ -32,7 +33,39 @@ No resources. ## Inputs -No 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 | +| [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 | +| [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 | +| [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 | +| [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 | +| [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 | +| [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 | +| [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 | +| [workspace](#input\_workspace) | ID element. The Terraform workspace, to help ensure generated IDs are unique across workspaces | `string` | `null` | no | ## Outputs diff --git a/infrastructure/modules/eventbridge/context.tf b/infrastructure/modules/eventbridge/context.tf new file mode 100644 index 00000000..e934a84f --- /dev/null +++ b/infrastructure/modules/eventbridge/context.tf @@ -0,0 +1,376 @@ +# tflint-ignore-file: terraform_standard_module_structure, terraform_unused_declarations +# +# 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" + + enabled = var.enabled + 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 diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index daf911c8..8cc6c63a 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -1,5 +1,8 @@ module "eventbridge" { source = "git::https://github.com/terraform-aws-modules/terraform-aws-eventbridge.git?ref=f9934726324c988f823682884b4fa003586a7b6f" # v4.3.2 + create = module.this.enabled + tags = module.this.tags + # DAVEH: add attributes } From b9b6b5a48489465eff7d04e33793e5bb25124cb8 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 08:55:46 +0100 Subject: [PATCH 11/64] feat: copy input variables from underlying module --- infrastructure/modules/eventbridge/README.md | 80 +++ infrastructure/modules/eventbridge/main.tf | 82 ++- .../modules/eventbridge/variables.tf | 494 ++++++++++++++++++ 3 files changed, 655 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 0de5a75b..7e352b08 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -36,35 +36,115 @@ No resources. | 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 | +| [api\_destinations](#input\_api\_destinations) | A map of objects with EventBridge Destination definitions. | `map(any)` | `{}` | no | +| [append\_connection\_postfix](#input\_append\_connection\_postfix) | Controls whether to append '-connection' to the name of the connection | `bool` | `true` | no | +| [append\_destination\_postfix](#input\_append\_destination\_postfix) | Controls whether to append '-destination' to the name of the destination | `bool` | `true` | no | +| [append\_pipe\_postfix](#input\_append\_pipe\_postfix) | Controls whether to append '-pipe' to the name of the pipe | `bool` | `true` | no | +| [append\_rule\_postfix](#input\_append\_rule\_postfix) | Controls whether to append '-rule' to the name of the rule | `bool` | `true` | no | +| [append\_schedule\_group\_postfix](#input\_append\_schedule\_group\_postfix) | Controls whether to append '-group' to the name of the schedule group | `bool` | `true` | no | +| [append\_schedule\_postfix](#input\_append\_schedule\_postfix) | Controls whether to append '-schedule' to the name of the schedule | `bool` | `true` | no | | [application\_role](#input\_application\_role) | The role the application is performing | `string` | `"General"` | no | +| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions. | `map(any)` | `{}` | no | +| [attach\_api\_destination\_policy](#input\_attach\_api\_destination\_policy) | Controls whether the API Destination policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_cloudwatch\_policy](#input\_attach\_cloudwatch\_policy) | Controls whether the Cloudwatch policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_ecs\_policy](#input\_attach\_ecs\_policy) | Controls whether the ECS policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_kinesis\_firehose\_policy](#input\_attach\_kinesis\_firehose\_policy) | Controls whether the Kinesis Firehose policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_kinesis\_policy](#input\_attach\_kinesis\_policy) | Controls whether the Kinesis policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_lambda\_policy](#input\_attach\_lambda\_policy) | Controls whether the Lambda Function policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_policies](#input\_attach\_policies) | Controls whether list of policies should be added to IAM role | `bool` | `false` | no | +| [attach\_policy](#input\_attach\_policy) | Controls whether policy should be added to IAM role | `bool` | `false` | no | +| [attach\_policy\_json](#input\_attach\_policy\_json) | Controls whether policy\_json should be added to IAM role | `bool` | `false` | no | +| [attach\_policy\_jsons](#input\_attach\_policy\_jsons) | Controls whether policy\_jsons should be added to IAM role | `bool` | `false` | no | +| [attach\_policy\_statements](#input\_attach\_policy\_statements) | Controls whether policy\_statements should be added to IAM role | `bool` | `false` | no | +| [attach\_sfn\_policy](#input\_attach\_sfn\_policy) | Controls whether the StepFunction policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_sns\_policy](#input\_attach\_sns\_policy) | Controls whether the SNS policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_sqs\_policy](#input\_attach\_sqs\_policy) | Controls whether the SQS policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | +| [attach\_tracing\_policy](#input\_attach\_tracing\_policy) | Controls whether X-Ray tracing policy should be added to IAM role for EventBridge | `bool` | `false` | 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 | +| [bus\_description](#input\_bus\_description) | Event bus description | `string` | `null` | no | +| [bus\_name](#input\_bus\_name) | A unique name for your EventBridge Bus | `string` | `"default"` | no | +| [cloudwatch\_target\_arns](#input\_cloudwatch\_target\_arns) | The Amazon Resource Name (ARN) of the Cloudwatch Log Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | +| [connections](#input\_connections) | A map of objects with EventBridge Connection definitions. | `any` | `{}` | 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 | +| [create\_api\_destinations](#input\_create\_api\_destinations) | Controls whether EventBridge Destination resources should be created | `bool` | `false` | no | +| [create\_archives](#input\_create\_archives) | Controls whether EventBridge Archive resources should be created | `bool` | `false` | no | +| [create\_bus](#input\_create\_bus) | Controls whether EventBridge Bus resource should be created | `bool` | `true` | no | +| [create\_connections](#input\_create\_connections) | Controls whether EventBridge Connection resources should be created | `bool` | `false` | no | +| [create\_log\_delivery](#input\_create\_log\_delivery) | Controls whether EventBridge log delivery resources should be created | `bool` | `true` | no | +| [create\_log\_delivery\_source](#input\_create\_log\_delivery\_source) | Controls whether EventBridge log delivery source resource should be created | `bool` | `true` | no | +| [create\_permissions](#input\_create\_permissions) | Controls whether EventBridge Permission resources should be created | `bool` | `true` | no | +| [create\_pipe\_role\_only](#input\_create\_pipe\_role\_only) | Controls whether an IAM role should be created for the pipes only | `bool` | `false` | no | +| [create\_pipes](#input\_create\_pipes) | Controls whether EventBridge Pipes resources should be created | `bool` | `true` | no | +| [create\_role](#input\_create\_role) | Controls whether IAM roles should be created | `bool` | `true` | no | +| [create\_rules](#input\_create\_rules) | Controls whether EventBridge Rule resources should be created | `bool` | `true` | no | +| [create\_schedule\_groups](#input\_create\_schedule\_groups) | Controls whether EventBridge Schedule Group resources should be created | `bool` | `true` | no | +| [create\_schedules](#input\_create\_schedules) | Controls whether EventBridge Schedule resources should be created | `bool` | `true` | no | +| [create\_schemas\_discoverer](#input\_create\_schemas\_discoverer) | Controls whether default schemas discoverer should be created | `bool` | `false` | no | +| [create\_targets](#input\_create\_targets) | Controls whether EventBridge Target resources should be created | `bool` | `true` | 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 | +| [dead\_letter\_config](#input\_dead\_letter\_config) | Configuration details of the Amazon SQS queue for EventBridge to use as a dead-letter queue (DLQ) | `any` | `{}` | 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 | +| [ecs\_pass\_role\_resources](#input\_ecs\_pass\_role\_resources) | List of approved roles to be passed | `list(string)` | `[]` | no | +| [ecs\_target\_arns](#input\_ecs\_target\_arns) | The Amazon Resource Name (ARN) of the AWS ECS Tasks you want to use as EventBridge targets | `list(string)` | `[]` | 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 | +| [event\_source\_name](#input\_event\_source\_name) | The partner event source that the new event bus will be matched with. Must match name. | `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 | +| [kinesis\_firehose\_target\_arns](#input\_kinesis\_firehose\_target\_arns) | The Amazon Resource Name (ARN) of the Kinesis Firehose Delivery Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | +| [kinesis\_target\_arns](#input\_kinesis\_target\_arns) | The Amazon Resource Name (ARN) of the Kinesis Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | +| [kms\_key\_identifier](#input\_kms\_key\_identifier) | The identifier of the AWS KMS customer managed key for EventBridge to use, if you choose to use a customer managed key to encrypt events on this event bus. The identifier can be the key Amazon Resource Name (ARN), KeyId, key alias, or key alias ARN. | `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 | +| [lambda\_target\_arns](#input\_lambda\_target\_arns) | The Amazon Resource Name (ARN) of the Lambda Functions you want to use as EventBridge targets | `list(string)` | `[]` | no | +| [log\_config](#input\_log\_config) | The configuration block for the EventBridge bus log config settings |
object({
include_detail = string
level = string
})
| `null` | no | +| [log\_delivery](#input\_log\_delivery) | Map of the configuration block for the EventBridge bus log delivery settings (key is the type of log delivery: cloudwatch\_logs, s3, firehose) |
map(object({
enabled = optional(bool, true)
destination_arn = string
source_name = optional(string)
name = optional(string)
output_format = optional(string)
field_delimiter = optional(string)
record_fields = optional(list(string))
s3_delivery_configuration = optional(object({
enable_hive_compatible_path = optional(bool)
suffix_path = optional(string)
}))
}))
| `{}` | no | +| [log\_delivery\_source\_name](#input\_log\_delivery\_source\_name) | Name of log delivery source | `string` | `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 | +| [number\_of\_policies](#input\_number\_of\_policies) | Number of policies to attach to IAM role | `number` | `0` | no | +| [number\_of\_policy\_jsons](#input\_number\_of\_policy\_jsons) | Number of policies JSON to attach to IAM role | `number` | `0` | 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 | +| [permissions](#input\_permissions) | A map of objects with EventBridge Permission definitions. | `map(any)` | `{}` | no | +| [pipes](#input\_pipes) | A map of objects with EventBridge Pipe definitions. | `any` | `{}` | no | +| [policies](#input\_policies) | List of policy statements ARN to attach to IAM role | `list(string)` | `[]` | no | +| [policy](#input\_policy) | An additional policy document ARN to attach to IAM role | `string` | `null` | no | +| [policy\_json](#input\_policy\_json) | An additional policy document as JSON to attach to IAM role | `string` | `null` | no | +| [policy\_jsons](#input\_policy\_jsons) | List of additional policy documents as JSON to attach to IAM role | `list(string)` | `[]` | no | +| [policy\_path](#input\_policy\_path) | Path of IAM policy to use for EventBridge | `string` | `null` | no | +| [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to IAM role | `any` | `{}` | 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 | +| [role\_description](#input\_role\_description) | Description of IAM role to use for EventBridge | `string` | `null` | no | +| [role\_force\_detach\_policies](#input\_role\_force\_detach\_policies) | Specifies to force detaching any policies the IAM role has before destroying it. | `bool` | `true` | no | +| [role\_name](#input\_role\_name) | Name of IAM role to use for EventBridge | `string` | `null` | no | +| [role\_path](#input\_role\_path) | Path of IAM role to use for EventBridge | `string` | `null` | no | +| [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the IAM role used by EventBridge | `string` | `null` | no | +| [role\_tags](#input\_role\_tags) | A map of tags to assign to IAM role | `map(string)` | `{}` | no | +| [rules](#input\_rules) | A map of objects with EventBridge Rule definitions. | `map(any)` | `{}` | no | +| [schedule\_group\_timeouts](#input\_schedule\_group\_timeouts) | A map of objects with EventBridge Schedule Group create and delete timeouts. | `map(string)` | `{}` | no | +| [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions. | `any` | `{}` | no | +| [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions. | `map(any)` | `{}` | no | +| [schemas\_discoverer\_description](#input\_schemas\_discoverer\_description) | Default schemas discoverer description | `string` | `"Auto schemas discoverer event"` | 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 | +| [sfn\_target\_arns](#input\_sfn\_target\_arns) | The Amazon Resource Name (ARN) of the StepFunctions you want to use as EventBridge targets | `list(string)` | `[]` | no | +| [sns\_kms\_arns](#input\_sns\_kms\_arns) | The Amazon Resource Name (ARN) of the AWS KMS's configured for AWS SNS you want Decrypt/GenerateDataKey for | `list(string)` |
[
"*"
]
| no | +| [sns\_target\_arns](#input\_sns\_target\_arns) | The Amazon Resource Name (ARN) of the AWS SNS's you want to use as EventBridge targets | `list(string)` | `[]` | no | +| [sqs\_target\_arns](#input\_sqs\_target\_arns) | The Amazon Resource Name (ARN) of the AWS SQS Queues you want to use as EventBridge targets | `list(string)` | `[]` | no | | [stack](#input\_stack) | ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks` | `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 | +| [targets](#input\_targets) | A map of objects with EventBridge Target definitions. | `any` | `{}` | 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 | +| [trusted\_entities](#input\_trusted\_entities) | Additional trusted entities for assuming roles (trust relationship) | `list(string)` | `[]` | no | | [workspace](#input\_workspace) | ID element. The Terraform workspace, to help ensure generated IDs are unique across workspaces | `string` | `null` | no | ## Outputs diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 8cc6c63a..c8ff6cc8 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -4,5 +4,85 @@ module "eventbridge" { create = module.this.enabled tags = module.this.tags - # DAVEH: add attributes + create_role = var.create_role + create_pipe_role_only = var.create_pipe_role_only + append_rule_postfix = var.append_rule_postfix + append_connection_postfix = var.append_connection_postfix + append_destination_postfix = var.append_destination_postfix + append_schedule_group_postfix = var.append_schedule_group_postfix + append_schedule_postfix = var.append_schedule_postfix + append_pipe_postfix = var.append_pipe_postfix + create_bus = var.create_bus + create_rules = var.create_rules + create_targets = var.create_targets + create_permissions = var.create_permissions + create_archives = var.create_archives + create_connections = var.create_connections + create_api_destinations = var.create_api_destinations + create_schemas_discoverer = var.create_schemas_discoverer + create_schedule_groups = var.create_schedule_groups + create_schedules = var.create_schedules + create_pipes = var.create_pipes + create_log_delivery_source = var.create_log_delivery_source + create_log_delivery = var.create_log_delivery + region = var.aws_region + bus_name = var.bus_name + bus_description = var.bus_description + log_config = var.log_config + log_delivery = var.log_delivery + log_delivery_source_name = var.log_delivery_source_name + event_source_name = var.event_source_name + kms_key_identifier = var.kms_key_identifier + dead_letter_config = var.dead_letter_config + schemas_discoverer_description = var.schemas_discoverer_description + rules = var.rules + targets = var.targets + archives = var.archives + permissions = var.permissions + connections = var.connections + api_destinations = var.api_destinations + schedule_groups = var.schedule_groups + schedules = var.schedules + pipes = var.pipes + schedule_group_timeouts = var.schedule_group_timeouts + role_name = var.role_name + role_description = var.role_description + role_path = var.role_path + policy_path = var.policy_path + role_force_detach_policies = var.role_force_detach_policies + role_permissions_boundary = var.role_permissions_boundary + role_tags = var.role_tags + ecs_pass_role_resources = var.ecs_pass_role_resources + attach_kinesis_policy = var.attach_kinesis_policy + attach_kinesis_firehose_policy = var.attach_kinesis_firehose_policy + attach_sqs_policy = var.attach_sqs_policy + attach_sns_policy = var.attach_sns_policy + attach_ecs_policy = var.attach_ecs_policy + attach_lambda_policy = var.attach_lambda_policy + attach_sfn_policy = var.attach_sfn_policy + attach_cloudwatch_policy = var.attach_cloudwatch_policy + attach_api_destination_policy = var.attach_api_destination_policy + attach_tracing_policy = var.attach_tracing_policy + kinesis_target_arns = var.kinesis_target_arns + kinesis_firehose_target_arns = var.kinesis_firehose_target_arns + sqs_target_arns = var.sqs_target_arns + sns_target_arns = var.sns_target_arns + sns_kms_arns = var.sns_kms_arns + ecs_target_arns = var.ecs_target_arns + lambda_target_arns = var.lambda_target_arns + sfn_target_arns = var.sfn_target_arns + cloudwatch_target_arns = var.cloudwatch_target_arns + attach_policy_json = var.attach_policy_json + attach_policy_jsons = var.attach_policy_jsons + attach_policy = var.attach_policy + attach_policies = var.attach_policies + number_of_policy_jsons = var.number_of_policy_jsons + number_of_policies = var.number_of_policies + attach_policy_statements = var.attach_policy_statements + trusted_entities = var.trusted_entities + policy_json = var.policy_json + policy_jsons = var.policy_jsons + policy = var.policy + policies = var.policies + policy_statements = var.policy_statements } diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index e69de29b..cb6b501b 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -0,0 +1,494 @@ +variable "create_role" { + description = "Controls whether IAM roles should be created" + type = bool + default = true +} + +variable "create_pipe_role_only" { + description = "Controls whether an IAM role should be created for the pipes only" + type = bool + default = false +} + +variable "append_rule_postfix" { + description = "Controls whether to append '-rule' to the name of the rule" + type = bool + default = true +} + +variable "append_connection_postfix" { + description = "Controls whether to append '-connection' to the name of the connection" + type = bool + default = true +} + +variable "append_destination_postfix" { + description = "Controls whether to append '-destination' to the name of the destination" + type = bool + default = true +} + +variable "append_schedule_group_postfix" { + description = "Controls whether to append '-group' to the name of the schedule group" + type = bool + default = true +} + +variable "append_schedule_postfix" { + description = "Controls whether to append '-schedule' to the name of the schedule" + type = bool + default = true +} + +variable "append_pipe_postfix" { + description = "Controls whether to append '-pipe' to the name of the pipe" + type = bool + default = true +} + +variable "create_bus" { + description = "Controls whether EventBridge Bus resource should be created" + type = bool + default = true +} + +variable "create_rules" { + description = "Controls whether EventBridge Rule resources should be created" + type = bool + default = true +} + +variable "create_targets" { + description = "Controls whether EventBridge Target resources should be created" + type = bool + default = true +} + +variable "create_permissions" { + description = "Controls whether EventBridge Permission resources should be created" + type = bool + default = true +} + +variable "create_archives" { + description = "Controls whether EventBridge Archive resources should be created" + type = bool + default = false +} + +variable "create_connections" { + description = "Controls whether EventBridge Connection resources should be created" + type = bool + default = false +} + +variable "create_api_destinations" { + description = "Controls whether EventBridge Destination resources should be created" + type = bool + default = false +} + +variable "create_schemas_discoverer" { + description = "Controls whether default schemas discoverer should be created" + type = bool + default = false +} + +variable "create_schedule_groups" { + description = "Controls whether EventBridge Schedule Group resources should be created" + type = bool + default = true +} + +variable "create_schedules" { + description = "Controls whether EventBridge Schedule resources should be created" + type = bool + default = true +} + +variable "create_pipes" { + description = "Controls whether EventBridge Pipes resources should be created" + type = bool + default = true +} + +variable "create_log_delivery_source" { + description = "Controls whether EventBridge log delivery source resource should be created" + type = bool + default = true +} + +variable "create_log_delivery" { + description = "Controls whether EventBridge log delivery resources should be created" + type = bool + default = true +} + +variable "bus_name" { + description = "A unique name for your EventBridge Bus" + type = string + default = "default" +} + +variable "bus_description" { + description = "Event bus description" + type = string + default = null +} + +variable "log_config" { + description = "The configuration block for the EventBridge bus log config settings" + type = object({ + include_detail = string + level = string + }) + default = null +} + +variable "log_delivery" { + description = "Map of the configuration block for the EventBridge bus log delivery settings (key is the type of log delivery: cloudwatch_logs, s3, firehose)" + type = map(object({ + enabled = optional(bool, true) + destination_arn = string + source_name = optional(string) + name = optional(string) + output_format = optional(string) + field_delimiter = optional(string) + record_fields = optional(list(string)) + s3_delivery_configuration = optional(object({ + enable_hive_compatible_path = optional(bool) + suffix_path = optional(string) + })) + })) + default = {} +} + +variable "log_delivery_source_name" { + description = "Name of log delivery source" + type = string + default = null +} + +variable "event_source_name" { + description = "The partner event source that the new event bus will be matched with. Must match name." + type = string + default = null +} + +variable "kms_key_identifier" { + description = "The identifier of the AWS KMS customer managed key for EventBridge to use, if you choose to use a customer managed key to encrypt events on this event bus. The identifier can be the key Amazon Resource Name (ARN), KeyId, key alias, or key alias ARN." + type = string + default = null +} + +variable "dead_letter_config" { + description = "Configuration details of the Amazon SQS queue for EventBridge to use as a dead-letter queue (DLQ)" + type = any + default = {} +} + +variable "schemas_discoverer_description" { + description = "Default schemas discoverer description" + type = string + default = "Auto schemas discoverer event" +} + +variable "rules" { + description = "A map of objects with EventBridge Rule definitions." + type = map(any) + default = {} +} + +variable "targets" { + description = "A map of objects with EventBridge Target definitions." + type = any + default = {} +} + +variable "archives" { + description = "A map of objects with the EventBridge Archive definitions." + type = map(any) + default = {} +} + +variable "permissions" { + description = "A map of objects with EventBridge Permission definitions." + type = map(any) + default = {} +} + +variable "connections" { + description = "A map of objects with EventBridge Connection definitions." + type = any + default = {} +} + +variable "api_destinations" { + description = "A map of objects with EventBridge Destination definitions." + type = map(any) + default = {} +} + +variable "schedule_groups" { + description = "A map of objects with EventBridge Schedule Group definitions." + type = any + default = {} +} + +variable "schedules" { + description = "A map of objects with EventBridge Schedule definitions." + type = map(any) + default = {} +} + +variable "pipes" { + description = "A map of objects with EventBridge Pipe definitions." + type = any + default = {} +} + +variable "schedule_group_timeouts" { + description = "A map of objects with EventBridge Schedule Group create and delete timeouts." + type = map(string) + default = {} +} + +variable "role_name" { + description = "Name of IAM role to use for EventBridge" + type = string + default = null +} + +variable "role_description" { + description = "Description of IAM role to use for EventBridge" + type = string + default = null +} + +variable "role_path" { + description = "Path of IAM role to use for EventBridge" + type = string + default = null +} + +variable "policy_path" { + description = "Path of IAM policy to use for EventBridge" + type = string + default = null +} + +variable "role_force_detach_policies" { + description = "Specifies to force detaching any policies the IAM role has before destroying it." + type = bool + default = true +} + +variable "role_permissions_boundary" { + description = "The ARN of the policy that is used to set the permissions boundary for the IAM role used by EventBridge" + type = string + default = null +} + +variable "role_tags" { + description = "A map of tags to assign to IAM role" + type = map(string) + default = {} +} + +variable "ecs_pass_role_resources" { + description = "List of approved roles to be passed" + type = list(string) + default = [] +} + +variable "attach_kinesis_policy" { + description = "Controls whether the Kinesis policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_kinesis_firehose_policy" { + description = "Controls whether the Kinesis Firehose policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_sqs_policy" { + description = "Controls whether the SQS policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_sns_policy" { + description = "Controls whether the SNS policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_ecs_policy" { + description = "Controls whether the ECS policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_lambda_policy" { + description = "Controls whether the Lambda Function policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_sfn_policy" { + description = "Controls whether the StepFunction policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_cloudwatch_policy" { + description = "Controls whether the Cloudwatch policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_api_destination_policy" { + description = "Controls whether the API Destination policy should be added to IAM role for EventBridge Target" + type = bool + default = false +} + +variable "attach_tracing_policy" { + description = "Controls whether X-Ray tracing policy should be added to IAM role for EventBridge" + type = bool + default = false +} + +variable "kinesis_target_arns" { + description = "The Amazon Resource Name (ARN) of the Kinesis Streams you want to use as EventBridge targets" + type = list(string) + default = [] +} + +variable "kinesis_firehose_target_arns" { + description = "The Amazon Resource Name (ARN) of the Kinesis Firehose Delivery Streams you want to use as EventBridge targets" + type = list(string) + default = [] +} + +variable "sqs_target_arns" { + description = "The Amazon Resource Name (ARN) of the AWS SQS Queues you want to use as EventBridge targets" + type = list(string) + default = [] +} + +variable "sns_target_arns" { + description = "The Amazon Resource Name (ARN) of the AWS SNS's you want to use as EventBridge targets" + type = list(string) + default = [] +} + +variable "sns_kms_arns" { + description = "The Amazon Resource Name (ARN) of the AWS KMS's configured for AWS SNS you want Decrypt/GenerateDataKey for" + type = list(string) + default = ["*"] +} + +variable "ecs_target_arns" { + description = "The Amazon Resource Name (ARN) of the AWS ECS Tasks you want to use as EventBridge targets" + type = list(string) + default = [] +} + +variable "lambda_target_arns" { + description = "The Amazon Resource Name (ARN) of the Lambda Functions you want to use as EventBridge targets" + type = list(string) + default = [] +} + +variable "sfn_target_arns" { + description = "The Amazon Resource Name (ARN) of the StepFunctions you want to use as EventBridge targets" + type = list(string) + default = [] +} + +variable "cloudwatch_target_arns" { + description = "The Amazon Resource Name (ARN) of the Cloudwatch Log Streams you want to use as EventBridge targets" + type = list(string) + default = [] +} + +variable "attach_policy_json" { + description = "Controls whether policy_json should be added to IAM role" + type = bool + default = false +} + +variable "attach_policy_jsons" { + description = "Controls whether policy_jsons should be added to IAM role" + type = bool + default = false +} + +variable "attach_policy" { + description = "Controls whether policy should be added to IAM role" + type = bool + default = false +} + +variable "attach_policies" { + description = "Controls whether list of policies should be added to IAM role" + type = bool + default = false +} + +variable "number_of_policy_jsons" { + description = "Number of policies JSON to attach to IAM role" + type = number + default = 0 +} + +variable "number_of_policies" { + description = "Number of policies to attach to IAM role" + type = number + default = 0 +} + +variable "attach_policy_statements" { + description = "Controls whether policy_statements should be added to IAM role" + type = bool + default = false +} + +variable "trusted_entities" { + description = "Additional trusted entities for assuming roles (trust relationship)" + type = list(string) + default = [] +} + +variable "policy_json" { + description = "An additional policy document as JSON to attach to IAM role" + type = string + default = null +} + +variable "policy_jsons" { + description = "List of additional policy documents as JSON to attach to IAM role" + type = list(string) + default = [] +} + +variable "policy" { + description = "An additional policy document ARN to attach to IAM role" + type = string + default = null +} + +variable "policies" { + description = "List of policy statements ARN to attach to IAM role" + type = list(string) + default = [] +} + +variable "policy_statements" { + description = "Map of dynamic policy statements to attach to IAM role" + type = any + default = {} +} From b41c0834c1347446fd5e366afb0639ff64d41c56 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 09:05:07 +0100 Subject: [PATCH 12/64] feat: copy output variables from underlying module --- infrastructure/modules/eventbridge/README.md | 37 +++- infrastructure/modules/eventbridge/outputs.tf | 170 ++++++++++++++++++ 2 files changed, 206 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 7e352b08..b7a3338f 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -149,7 +149,42 @@ No resources. ## Outputs -No outputs. +| Name | Description | +| ---- | ----------- | +| [eventbridge\_api\_destination\_arns](#output\_eventbridge\_api\_destination\_arns) | The EventBridge API Destination ARNs | +| [eventbridge\_api\_destinations](#output\_eventbridge\_api\_destinations) | The EventBridge API Destinations created and their attributes | +| [eventbridge\_archive\_arns](#output\_eventbridge\_archive\_arns) | The EventBridge Archive ARNs | +| [eventbridge\_archives](#output\_eventbridge\_archives) | The EventBridge Archives created and their attributes | +| [eventbridge\_bus](#output\_eventbridge\_bus) | The EventBridge Bus created and their attributes | +| [eventbridge\_bus\_arn](#output\_eventbridge\_bus\_arn) | The EventBridge Bus ARN | +| [eventbridge\_bus\_name](#output\_eventbridge\_bus\_name) | The EventBridge Bus Name | +| [eventbridge\_connection\_arns](#output\_eventbridge\_connection\_arns) | The EventBridge Connection Arns | +| [eventbridge\_connection\_ids](#output\_eventbridge\_connection\_ids) | The EventBridge Connection IDs | +| [eventbridge\_connections](#output\_eventbridge\_connections) | The EventBridge Connections created and their attributes | +| [eventbridge\_iam\_roles](#output\_eventbridge\_iam\_roles) | The EventBridge IAM roles created and their attributes | +| [eventbridge\_log\_delivery\_source\_arn](#output\_eventbridge\_log\_delivery\_source\_arn) | The EventBridge Bus CloudWatch Log Delivery Source ARN | +| [eventbridge\_log\_delivery\_source\_name](#output\_eventbridge\_log\_delivery\_source\_name) | The EventBridge Bus CloudWatch Log Delivery Source Name | +| [eventbridge\_permission\_ids](#output\_eventbridge\_permission\_ids) | The EventBridge Permission IDs | +| [eventbridge\_permissions](#output\_eventbridge\_permissions) | The EventBridge Permissions created and their attributes | +| [eventbridge\_pipe\_arns](#output\_eventbridge\_pipe\_arns) | The EventBridge Pipes ARNs | +| [eventbridge\_pipe\_ids](#output\_eventbridge\_pipe\_ids) | The EventBridge Pipes IDs | +| [eventbridge\_pipe\_role\_arns](#output\_eventbridge\_pipe\_role\_arns) | The ARNs of the IAM role created for EventBridge Pipes | +| [eventbridge\_pipe\_role\_names](#output\_eventbridge\_pipe\_role\_names) | The names of the IAM role created for EventBridge Pipes | +| [eventbridge\_pipes](#output\_eventbridge\_pipes) | The EventBridge Pipes created and their attributes | +| [eventbridge\_pipes\_iam\_roles](#output\_eventbridge\_pipes\_iam\_roles) | The EventBridge Pipes IAM roles created and their attributes | +| [eventbridge\_role\_arn](#output\_eventbridge\_role\_arn) | The ARN of the IAM role created for EventBridge | +| [eventbridge\_role\_name](#output\_eventbridge\_role\_name) | The name of the IAM role created for EventBridge | +| [eventbridge\_rule\_arns](#output\_eventbridge\_rule\_arns) | The EventBridge Rule ARNs | +| [eventbridge\_rule\_ids](#output\_eventbridge\_rule\_ids) | The EventBridge Rule IDs | +| [eventbridge\_rules](#output\_eventbridge\_rules) | The EventBridge Rules created and their attributes | +| [eventbridge\_schedule\_arns](#output\_eventbridge\_schedule\_arns) | The EventBridge Schedule ARNs created | +| [eventbridge\_schedule\_group\_arns](#output\_eventbridge\_schedule\_group\_arns) | The EventBridge Schedule Group ARNs | +| [eventbridge\_schedule\_group\_ids](#output\_eventbridge\_schedule\_group\_ids) | The EventBridge Schedule Group IDs | +| [eventbridge\_schedule\_group\_states](#output\_eventbridge\_schedule\_group\_states) | The EventBridge Schedule Group states | +| [eventbridge\_schedule\_groups](#output\_eventbridge\_schedule\_groups) | The EventBridge Schedule Groups created and their attributes | +| [eventbridge\_schedule\_ids](#output\_eventbridge\_schedule\_ids) | The EventBridge Schedule IDs created | +| [eventbridge\_schedules](#output\_eventbridge\_schedules) | The EventBridge Schedules created and their attributes | +| [eventbridge\_targets](#output\_eventbridge\_targets) | The EventBridge Targets created and their attributes | diff --git a/infrastructure/modules/eventbridge/outputs.tf b/infrastructure/modules/eventbridge/outputs.tf index e69de29b..33dfc38f 100644 --- a/infrastructure/modules/eventbridge/outputs.tf +++ b/infrastructure/modules/eventbridge/outputs.tf @@ -0,0 +1,170 @@ +output "eventbridge_bus_name" { + description = "The EventBridge Bus Name" + value = module.eventbridge.eventbridge_bus_name +} + +output "eventbridge_bus_arn" { + description = "The EventBridge Bus ARN" + value = module.eventbridge.eventbridge_bus_arn +} + +output "eventbridge_archive_arns" { + description = "The EventBridge Archive ARNs" + value = module.eventbridge.eventbridge_archive_arns +} + +output "eventbridge_permission_ids" { + description = "The EventBridge Permission IDs" + value = module.eventbridge.eventbridge_permission_ids +} + +output "eventbridge_connection_ids" { + description = "The EventBridge Connection IDs" + value = module.eventbridge.eventbridge_connection_ids +} + +output "eventbridge_connection_arns" { + description = "The EventBridge Connection Arns" + value = module.eventbridge.eventbridge_connection_arns +} + +output "eventbridge_api_destination_arns" { + description = "The EventBridge API Destination ARNs" + value = module.eventbridge.eventbridge_api_destination_arns +} + +output "eventbridge_rule_ids" { + description = "The EventBridge Rule IDs" + value = module.eventbridge.eventbridge_rule_ids +} + +output "eventbridge_rule_arns" { + description = "The EventBridge Rule ARNs" + value = module.eventbridge.eventbridge_rule_arns +} + +output "eventbridge_schedule_group_ids" { + description = "The EventBridge Schedule Group IDs" + value = module.eventbridge.eventbridge_schedule_group_ids +} + +output "eventbridge_schedule_group_arns" { + description = "The EventBridge Schedule Group ARNs" + value = module.eventbridge.eventbridge_schedule_group_arns +} + +output "eventbridge_schedule_group_states" { + description = "The EventBridge Schedule Group states" + value = module.eventbridge.eventbridge_schedule_group_states +} + +output "eventbridge_schedule_ids" { + description = "The EventBridge Schedule IDs created" + value = module.eventbridge.eventbridge_schedule_ids +} + +output "eventbridge_schedule_arns" { + description = "The EventBridge Schedule ARNs created" + value = module.eventbridge.eventbridge_schedule_arns +} + +output "eventbridge_role_arn" { + description = "The ARN of the IAM role created for EventBridge" + value = module.eventbridge.eventbridge_role_arn +} + +output "eventbridge_role_name" { + description = "The name of the IAM role created for EventBridge" + value = module.eventbridge.eventbridge_role_name +} + +output "eventbridge_pipe_ids" { + description = "The EventBridge Pipes IDs" + value = module.eventbridge.eventbridge_pipe_ids +} + +output "eventbridge_pipe_arns" { + description = "The EventBridge Pipes ARNs" + value = module.eventbridge.eventbridge_pipe_arns +} + +output "eventbridge_pipe_role_arns" { + description = "The ARNs of the IAM role created for EventBridge Pipes" + value = module.eventbridge.eventbridge_pipe_role_arns +} + +output "eventbridge_pipe_role_names" { + description = "The names of the IAM role created for EventBridge Pipes" + value = module.eventbridge.eventbridge_pipe_role_names +} + +output "eventbridge_bus" { + description = "The EventBridge Bus created and their attributes" + value = module.eventbridge.eventbridge_bus +} + +output "eventbridge_archives" { + description = "The EventBridge Archives created and their attributes" + value = module.eventbridge.eventbridge_archives +} + +output "eventbridge_permissions" { + description = "The EventBridge Permissions created and their attributes" + value = module.eventbridge.eventbridge_permissions +} + +output "eventbridge_connections" { + description = "The EventBridge Connections created and their attributes" + value = module.eventbridge.eventbridge_connections + sensitive = true +} + +output "eventbridge_api_destinations" { + description = "The EventBridge API Destinations created and their attributes" + value = module.eventbridge.eventbridge_api_destinations +} + +output "eventbridge_targets" { + description = "The EventBridge Targets created and their attributes" + value = module.eventbridge.eventbridge_targets +} + +output "eventbridge_rules" { + description = "The EventBridge Rules created and their attributes" + value = module.eventbridge.eventbridge_rules +} + +output "eventbridge_schedule_groups" { + description = "The EventBridge Schedule Groups created and their attributes" + value = module.eventbridge.eventbridge_schedule_groups +} + +output "eventbridge_schedules" { + description = "The EventBridge Schedules created and their attributes" + value = module.eventbridge.eventbridge_schedules +} + +output "eventbridge_pipes" { + description = "The EventBridge Pipes created and their attributes" + value = module.eventbridge.eventbridge_pipes +} + +output "eventbridge_log_delivery_source_arn" { + description = "The EventBridge Bus CloudWatch Log Delivery Source ARN" + value = module.eventbridge.eventbridge_log_delivery_source_arn +} + +output "eventbridge_log_delivery_source_name" { + description = "The EventBridge Bus CloudWatch Log Delivery Source Name" + value = module.eventbridge.eventbridge_log_delivery_source_name +} + +output "eventbridge_pipes_iam_roles" { + description = "The EventBridge Pipes IAM roles created and their attributes" + value = module.eventbridge.eventbridge_pipes_iam_roles +} + +output "eventbridge_iam_roles" { + description = "The EventBridge IAM roles created and their attributes" + value = module.eventbridge.eventbridge_iam_roles +} From 0451196497ae173693435cc84eb8745c610dae99 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 10:53:57 +0100 Subject: [PATCH 13/64] docs: add notes about where names are obtained from where uniqueness is required --- infrastructure/modules/eventbridge/NOTES.md | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/NOTES.md b/infrastructure/modules/eventbridge/NOTES.md index 634239b5..cbca218a 100644 --- a/infrastructure/modules/eventbridge/NOTES.md +++ b/infrastructure/modules/eventbridge/NOTES.md @@ -39,7 +39,7 @@ The main on/off switch is called `create`. - resource - gated by `var.create_connections` - name status: named and must be unique per AWS account and region -- named by `Name` field of `var.connections` sub-value +- named by the key of `var.connections` - modified if `var.append_connection_postfix` is true - encrypted using `kms_key_identifier` field of `var.connections` sub-value, falling back to unencrypted @@ -68,66 +68,82 @@ The main on/off switch is called `create`. ### `aws_cloudwatch_log_delivery_destination.this` - name status: named and must be unique per AWS account +- name is obtained from the `name` field of each `var.log_delivery` sub-value, falling back to `var.bus_name` and the `var.log_delivery` map key ### `aws_cloudwatch_log_delivery_source.this` - name status: named and must be unique per AWS account +- name is obtained from `var.log_delivery_source_name`, falling back to `var.bus_name` ### `aws_iam_policy.additional_inline` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-inline` appended ### `aws_iam_policy.additional_json` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name` ### `aws_iam_policy.additional_jsons` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-` appended ### `aws_iam_policy.api_destination` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-api-destination` appended ### `aws_iam_policy.cloudwatch` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-cloudwatch` appended ### `aws_iam_policy.ecs` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-ecs` appended ### `aws_iam_policy.kinesis` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-kinesis` appended ### `aws_iam_policy.kinesis_firehose` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-kinesis-firehose` appended ### `aws_iam_policy.lambda` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-lambda` appended ### `aws_iam_policy.service` - name status: named and must be unique per AWS account +- name is derived from the generated Pipe role name and the key of `var.pipes`; the role name uses `role_name_prefix` from each `var.pipes` sub-value, falling back to its map key ### `aws_iam_policy.sfn` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-sfn` appended ### `aws_iam_policy.sns` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-sns` appended ### `aws_iam_policy.sqs` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-sqs` appended ### `aws_iam_policy.tracing` - name status: named and must be unique per AWS account +- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-tracing` appended ### `aws_iam_policy_attachment.additional_inline` @@ -188,10 +204,12 @@ The main on/off switch is called `create`. ### `aws_iam_role.eventbridge` - name status: named and must be unique per AWS account +- name is obtained from `var.role_name`, falling back to `var.bus_name` ### `aws_iam_role.eventbridge_pipe` - name status: named and must be unique per AWS account +- name uses `role_name_prefix` from each `var.pipes` sub-value, falling back to its map key ### `aws_iam_role_policy_attachment.additional_many` @@ -204,6 +222,7 @@ The main on/off switch is called `create`. ### `aws_pipes_pipe.this` - name status: named and must be unique per AWS account and region +- name is obtained from the key of `var.pipes`, with the optional postfix controlled by `var.append_pipe_postfix` - encrypted using `kms_key_identifier` field of `var.pipes` sub-value, falling back to unencrypted ### `aws_scheduler_schedule.this` @@ -214,7 +233,9 @@ The main on/off switch is called `create`. ### `aws_scheduler_schedule_group.this` - name status: named and must be unique per AWS account and region +- name is obtained from the `name` or `name_prefix` field of each `var.schedule_groups` sub-value, falling back to its map key ### `aws_schemas_discoverer.this` - name status: named and must be unique per AWS account and region +- name is generated by AWS; it is not obtained from a module input variable From 779d976a9aa356bbbb484ae107b22ac15d803a4a Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 11:25:50 +0100 Subject: [PATCH 14/64] chore: add todos for inputs that need to be unique names --- infrastructure/modules/eventbridge/main.tf | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index c8ff6cc8..c1d84eff 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -26,11 +26,11 @@ module "eventbridge" { create_log_delivery_source = var.create_log_delivery_source create_log_delivery = var.create_log_delivery region = var.aws_region - bus_name = var.bus_name + bus_name = var.bus_name # DAVEH: unique per AWS account and region; the event bus name is taken from var.bus_name bus_description = var.bus_description log_config = var.log_config - log_delivery = var.log_delivery - log_delivery_source_name = var.log_delivery_source_name + log_delivery = var.log_delivery # DAVEH: unique per AWS account; the destination name comes from var.log_delivery..name, falling back to var.bus_name and the map key + log_delivery_source_name = var.log_delivery_source_name # DAVEH: unique per AWS account; the source name is taken from var.log_delivery_source_name, falling back to var.bus_name event_source_name = var.event_source_name kms_key_identifier = var.kms_key_identifier dead_letter_config = var.dead_letter_config @@ -39,13 +39,13 @@ module "eventbridge" { targets = var.targets archives = var.archives permissions = var.permissions - connections = var.connections - api_destinations = var.api_destinations - schedule_groups = var.schedule_groups + connections = var.connections # DAVEH: unique per AWS account and region; the connection name is taken from the key of var.connections + api_destinations = var.api_destinations # DAVEH: unique per AWS account and region; the API destination name is taken from the key of var.api_destinations + schedule_groups = var.schedule_groups # DAVEH: unique per AWS account and region; the schedule group name is taken from var.schedule_groups..name or .name_prefix, falling back to the map key schedules = var.schedules - pipes = var.pipes + pipes = var.pipes # DAVEH: unique per AWS account and region; the pipe name is taken from the key of var.pipes, and the related IAM role names derive from role_name_prefix in each entry schedule_group_timeouts = var.schedule_group_timeouts - role_name = var.role_name + role_name = var.role_name # DAVEH: unique per AWS account; the EventBridge role and related IAM policy names are derived from var.role_name, falling back to var.bus_name role_description = var.role_description role_path = var.role_path policy_path = var.policy_path From df64bb97a5af2537fc7fce05113a5001d8692170 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 11:42:26 +0100 Subject: [PATCH 15/64] docs: todo --- infrastructure/modules/eventbridge/main.tf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index c1d84eff..03285319 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -1,6 +1,8 @@ module "eventbridge" { source = "git::https://github.com/terraform-aws-modules/terraform-aws-eventbridge.git?ref=f9934726324c988f823682884b4fa003586a7b6f" # v4.3.2 + # DAVEH: force enable encryption + create = module.this.enabled tags = module.this.tags From 2f9bd3255cf1fed0268366c610e98e084ef338db Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 13:42:32 +0100 Subject: [PATCH 16/64] feat: allow overriding bus name --- infrastructure/modules/eventbridge/README.md | 4 +++- infrastructure/modules/eventbridge/locals.tf | 4 ++++ infrastructure/modules/eventbridge/main.tf | 2 +- infrastructure/modules/eventbridge/variables.tf | 5 +++-- 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 infrastructure/modules/eventbridge/locals.tf diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index b7a3338f..05106246 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -6,6 +6,8 @@ module that consumes the shared `context.tf` for naming and tagging. DAVEH +DAVEH: document var.bus_name + @@ -63,7 +65,7 @@ No resources. | [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 | | [bus\_description](#input\_bus\_description) | Event bus description | `string` | `null` | no | -| [bus\_name](#input\_bus\_name) | A unique name for your EventBridge Bus | `string` | `"default"` | no | +| [bus\_name](#input\_bus\_name) | A unique name for your EventBridge Bus. Must be unique per AWS account and region. Defaults to whatever the tags module produces | `string` | `null` | no | | [cloudwatch\_target\_arns](#input\_cloudwatch\_target\_arns) | The Amazon Resource Name (ARN) of the Cloudwatch Log Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | | [connections](#input\_connections) | A map of objects with EventBridge Connection definitions. | `any` | `{}` | 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 | diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf new file mode 100644 index 00000000..6152e044 --- /dev/null +++ b/infrastructure/modules/eventbridge/locals.tf @@ -0,0 +1,4 @@ +locals { + # allow bus name to be overridden without touching tags + bus_name = coalesce(var.bus_name, module.this.id) +} diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 03285319..4937ab37 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -28,7 +28,7 @@ module "eventbridge" { create_log_delivery_source = var.create_log_delivery_source create_log_delivery = var.create_log_delivery region = var.aws_region - bus_name = var.bus_name # DAVEH: unique per AWS account and region; the event bus name is taken from var.bus_name + bus_name = local.bus_name bus_description = var.bus_description log_config = var.log_config log_delivery = var.log_delivery # DAVEH: unique per AWS account; the destination name comes from var.log_delivery..name, falling back to var.bus_name and the map key diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index cb6b501b..094bed16 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -125,9 +125,10 @@ variable "create_log_delivery" { } variable "bus_name" { - description = "A unique name for your EventBridge Bus" + description = "A unique name for your EventBridge Bus. Must be unique per AWS account and region. Defaults to whatever the tags module produces" type = string - default = "default" + nullable = true + default = null } variable "bus_description" { From 5ca23d239d54768f4132cebdb3954a7cb507ce3d Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 14:05:21 +0100 Subject: [PATCH 17/64] =?UTF-8?q?docs:=20log=5Fdelivery=5Fsource=5Fname=20?= =?UTF-8?q?defaults=20to=20bus=20name,=20so=20doesn=E2=80=99t=20need=20to?= =?UTF-8?q?=20explicitly=20be=20made=20unique?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infrastructure/modules/eventbridge/README.md | 2 +- infrastructure/modules/eventbridge/main.tf | 4 ++-- infrastructure/modules/eventbridge/variables.tf | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 05106246..e1020452 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -105,7 +105,7 @@ No resources. | [lambda\_target\_arns](#input\_lambda\_target\_arns) | The Amazon Resource Name (ARN) of the Lambda Functions you want to use as EventBridge targets | `list(string)` | `[]` | no | | [log\_config](#input\_log\_config) | The configuration block for the EventBridge bus log config settings |
object({
include_detail = string
level = string
})
| `null` | no | | [log\_delivery](#input\_log\_delivery) | Map of the configuration block for the EventBridge bus log delivery settings (key is the type of log delivery: cloudwatch\_logs, s3, firehose) |
map(object({
enabled = optional(bool, true)
destination_arn = string
source_name = optional(string)
name = optional(string)
output_format = optional(string)
field_delimiter = optional(string)
record_fields = optional(list(string))
s3_delivery_configuration = optional(object({
enable_hive_compatible_path = optional(bool)
suffix_path = optional(string)
}))
}))
| `{}` | no | -| [log\_delivery\_source\_name](#input\_log\_delivery\_source\_name) | Name of log delivery source | `string` | `null` | no | +| [log\_delivery\_source\_name](#input\_log\_delivery\_source\_name) | Name of log delivery source; defaults to the name we use for the bus | `string` | `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 | | [number\_of\_policies](#input\_number\_of\_policies) | Number of policies to attach to IAM role | `number` | `0` | no | | [number\_of\_policy\_jsons](#input\_number\_of\_policy\_jsons) | Number of policies JSON to attach to IAM role | `number` | `0` | no | diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 4937ab37..dcb11a56 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -31,8 +31,8 @@ module "eventbridge" { bus_name = local.bus_name bus_description = var.bus_description log_config = var.log_config - log_delivery = var.log_delivery # DAVEH: unique per AWS account; the destination name comes from var.log_delivery..name, falling back to var.bus_name and the map key - log_delivery_source_name = var.log_delivery_source_name # DAVEH: unique per AWS account; the source name is taken from var.log_delivery_source_name, falling back to var.bus_name + log_delivery = var.log_delivery # DAVEH: unique per AWS account; the destination name comes from var.log_delivery..name, falling back to var.bus_name and the map key + log_delivery_source_name = var.log_delivery_source_name event_source_name = var.event_source_name kms_key_identifier = var.kms_key_identifier dead_letter_config = var.dead_letter_config diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 094bed16..29ad8956 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -165,7 +165,7 @@ variable "log_delivery" { } variable "log_delivery_source_name" { - description = "Name of log delivery source" + description = "Name of log delivery source; defaults to the name we use for the bus" type = string default = null } From dfd375a3cdda1b9516103cb69aad277fe8debf17 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 14:18:37 +0100 Subject: [PATCH 18/64] feat: provide default unique log delivery names --- infrastructure/modules/eventbridge/README.md | 2 ++ infrastructure/modules/eventbridge/locals.tf | 11 +++++++++++ infrastructure/modules/eventbridge/main.tf | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index e1020452..56300957 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -8,6 +8,8 @@ DAVEH DAVEH: document var.bus_name +DAVEH: document var.log_delivery + diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 6152e044..b7da598f 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -1,4 +1,15 @@ locals { # allow bus name to be overridden without touching tags bus_name = coalesce(var.bus_name, module.this.id) + + # log delivery names must be unique per AWS account + # provide a default name based on the module ID + log_delivery = { + for k, v in var.log_delivery : k => merge( + { + name = "${module.this.id}-${k}" + }, + v + ) + } } diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index dcb11a56..96f50c1c 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -31,7 +31,7 @@ module "eventbridge" { bus_name = local.bus_name bus_description = var.bus_description log_config = var.log_config - log_delivery = var.log_delivery # DAVEH: unique per AWS account; the destination name comes from var.log_delivery..name, falling back to var.bus_name and the map key + log_delivery = local.log_delivery log_delivery_source_name = var.log_delivery_source_name event_source_name = var.event_source_name kms_key_identifier = var.kms_key_identifier From f5510f00404b141440da1b3a51f942cbd3a25a74 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 14:29:53 +0100 Subject: [PATCH 19/64] feat: enforce unique connection names --- infrastructure/modules/eventbridge/README.md | 2 ++ infrastructure/modules/eventbridge/locals.tf | 6 ++++++ infrastructure/modules/eventbridge/main.tf | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 56300957..d277b3c8 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -10,6 +10,8 @@ DAVEH: document var.bus_name DAVEH: document var.log_delivery +DAVEH: document var.connections + diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index b7da598f..15300b23 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -12,4 +12,10 @@ locals { v ) } + + # connection names must be unique per AWS account and region + # prefix provided names with the module ID to ensure uniqueness + connections = { + for k, v in var.connections : "${module.this.id}-${k}" => v + } } diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 96f50c1c..565db737 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -41,7 +41,7 @@ module "eventbridge" { targets = var.targets archives = var.archives permissions = var.permissions - connections = var.connections # DAVEH: unique per AWS account and region; the connection name is taken from the key of var.connections + connections = local.connections api_destinations = var.api_destinations # DAVEH: unique per AWS account and region; the API destination name is taken from the key of var.api_destinations schedule_groups = var.schedule_groups # DAVEH: unique per AWS account and region; the schedule group name is taken from var.schedule_groups..name or .name_prefix, falling back to the map key schedules = var.schedules From bd8057fdeeeeffb6ed336026cdc37d378c76d31b Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 14:32:22 +0100 Subject: [PATCH 20/64] feat: enforce unique API destination names --- infrastructure/modules/eventbridge/README.md | 2 ++ infrastructure/modules/eventbridge/locals.tf | 6 ++++++ infrastructure/modules/eventbridge/main.tf | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index d277b3c8..c65ed820 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -12,6 +12,8 @@ DAVEH: document var.log_delivery DAVEH: document var.connections +DAVEH: document var.api_destinations + diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 15300b23..927f6f72 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -18,4 +18,10 @@ locals { connections = { for k, v in var.connections : "${module.this.id}-${k}" => v } + + # API destination names must be unique per AWS account and region + # prefix provided names with the module ID to ensure uniqueness + api_destinations = { + for k, v in var.api_destinations : "${module.this.id}-${k}" => v + } } diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 565db737..ebcbcc05 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -42,8 +42,8 @@ module "eventbridge" { archives = var.archives permissions = var.permissions connections = local.connections - api_destinations = var.api_destinations # DAVEH: unique per AWS account and region; the API destination name is taken from the key of var.api_destinations - schedule_groups = var.schedule_groups # DAVEH: unique per AWS account and region; the schedule group name is taken from var.schedule_groups..name or .name_prefix, falling back to the map key + api_destinations = local.api_destinations + schedule_groups = var.schedule_groups # DAVEH: unique per AWS account and region; the schedule group name is taken from var.schedule_groups..name or .name_prefix, falling back to the map key schedules = var.schedules pipes = var.pipes # DAVEH: unique per AWS account and region; the pipe name is taken from the key of var.pipes, and the related IAM role names derive from role_name_prefix in each entry schedule_group_timeouts = var.schedule_group_timeouts From d91c751fb85d77f28b796eef67289af9806a408c Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 15:24:11 +0100 Subject: [PATCH 21/64] feat: enforce unique schedule group names --- infrastructure/modules/eventbridge/README.md | 2 ++ infrastructure/modules/eventbridge/locals.tf | 16 ++++++++++++++++ infrastructure/modules/eventbridge/main.tf | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index c65ed820..8fbf5a0c 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -14,6 +14,8 @@ DAVEH: document var.connections DAVEH: document var.api_destinations +DAVEH: document var.schedule_groups + diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 927f6f72..808af12b 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -24,4 +24,20 @@ locals { api_destinations = { for k, v in var.api_destinations : "${module.this.id}-${k}" => v } + + # schedule group names must be unique per AWS account and region + # respect `name` and `name_prefix` if given + # otherwise include module ID and key in `name_prefix` + schedule_groups = { + for k, v in var.schedule_groups : k => ( + contains(keys(v), "name") || contains(keys(v), "name_prefix") + ? v + : merge( + { + name_prefix = "${module.this.id}-${k}" + }, + v + ) + ) + } } diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index ebcbcc05..6a2ce959 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -43,7 +43,7 @@ module "eventbridge" { permissions = var.permissions connections = local.connections api_destinations = local.api_destinations - schedule_groups = var.schedule_groups # DAVEH: unique per AWS account and region; the schedule group name is taken from var.schedule_groups..name or .name_prefix, falling back to the map key + schedule_groups = local.schedule_groups schedules = var.schedules pipes = var.pipes # DAVEH: unique per AWS account and region; the pipe name is taken from the key of var.pipes, and the related IAM role names derive from role_name_prefix in each entry schedule_group_timeouts = var.schedule_group_timeouts From 7233623a294c17cb5415c91642bab0b7aa35fc75 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 15:27:24 +0100 Subject: [PATCH 22/64] feat: enforce unique pipe names --- infrastructure/modules/eventbridge/README.md | 2 ++ infrastructure/modules/eventbridge/locals.tf | 6 ++++++ infrastructure/modules/eventbridge/main.tf | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 8fbf5a0c..f0a29fe7 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -16,6 +16,8 @@ DAVEH: document var.api_destinations DAVEH: document var.schedule_groups +DAVEH: document var.pipes + diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 808af12b..a27fd54b 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -40,4 +40,10 @@ locals { ) ) } + + # pipe names must be unique per AWS account and region + # prefix keys with the module ID to ensure uniqueness + pipes = { + for k, v in var.pipes : "${module.this.id}-${k}" => v + } } diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 6a2ce959..d2785852 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -45,7 +45,7 @@ module "eventbridge" { api_destinations = local.api_destinations schedule_groups = local.schedule_groups schedules = var.schedules - pipes = var.pipes # DAVEH: unique per AWS account and region; the pipe name is taken from the key of var.pipes, and the related IAM role names derive from role_name_prefix in each entry + pipes = local.pipes schedule_group_timeouts = var.schedule_group_timeouts role_name = var.role_name # DAVEH: unique per AWS account; the EventBridge role and related IAM policy names are derived from var.role_name, falling back to var.bus_name role_description = var.role_description From d5c7f52cb26f4062327a66c7cddfc143ecad9fc1 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 15:28:57 +0100 Subject: [PATCH 23/64] =?UTF-8?q?docs:=20role=5Fname=20defaults=20to=20bus?= =?UTF-8?q?=20name,=20so=20doesn=E2=80=99t=20need=20to=20explicitly=20be?= =?UTF-8?q?=20made=20unique?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infrastructure/modules/eventbridge/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index d2785852..eabdcbd6 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -47,7 +47,7 @@ module "eventbridge" { schedules = var.schedules pipes = local.pipes schedule_group_timeouts = var.schedule_group_timeouts - role_name = var.role_name # DAVEH: unique per AWS account; the EventBridge role and related IAM policy names are derived from var.role_name, falling back to var.bus_name + role_name = var.role_name role_description = var.role_description role_path = var.role_path policy_path = var.policy_path From 3d7f4f3e1deab1ee46d3c9c4d2851597d1076958 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 15:36:03 +0100 Subject: [PATCH 24/64] docs: todos --- infrastructure/modules/eventbridge/main.tf | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index eabdcbd6..22fff8b5 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -1,8 +1,6 @@ module "eventbridge" { source = "git::https://github.com/terraform-aws-modules/terraform-aws-eventbridge.git?ref=f9934726324c988f823682884b4fa003586a7b6f" # v4.3.2 - # DAVEH: force enable encryption - create = module.this.enabled tags = module.this.tags @@ -31,21 +29,21 @@ module "eventbridge" { bus_name = local.bus_name bus_description = var.bus_description log_config = var.log_config - log_delivery = local.log_delivery + log_delivery = local.log_delivery # DAVEH: per-destination KMS keys can come from var.log_delivery..kms_key_identifier or fall back to unencrypted log_delivery_source_name = var.log_delivery_source_name event_source_name = var.event_source_name - kms_key_identifier = var.kms_key_identifier + kms_key_identifier = var.kms_key_identifier # DAVEH: KMS key for the event bus comes from var.kms_key_identifier dead_letter_config = var.dead_letter_config schemas_discoverer_description = var.schemas_discoverer_description rules = var.rules targets = var.targets archives = var.archives permissions = var.permissions - connections = local.connections + connections = local.connections # DAVEH: per-connection KMS keys can come from var.connections..kms_key_identifier or fall back to unencrypted api_destinations = local.api_destinations schedule_groups = local.schedule_groups - schedules = var.schedules - pipes = local.pipes + schedules = var.schedules # DAVEH: per-schedule KMS keys can come from var.schedules..kms_key_arn or fall back to unencrypted + pipes = local.pipes # DAVEH: per-pipe KMS keys can come from var.pipes..kms_key_identifier or fall back to unencrypted schedule_group_timeouts = var.schedule_group_timeouts role_name = var.role_name role_description = var.role_description From 3962cf69160b603adc183a21aa897edbf108d8ea Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 15:53:23 +0100 Subject: [PATCH 25/64] feat: enforce encryption of events on the event bus --- infrastructure/modules/eventbridge/README.md | 8 +++++++- infrastructure/modules/eventbridge/variables.tf | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index f0a29fe7..53699547 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -8,6 +8,8 @@ DAVEH DAVEH: document var.bus_name +---- + DAVEH: document var.log_delivery DAVEH: document var.connections @@ -18,6 +20,10 @@ DAVEH: document var.schedule_groups DAVEH: document var.pipes +---- + +DAVEH: document var.kms_key_identifier + @@ -107,7 +113,7 @@ No resources. | [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 | | [kinesis\_firehose\_target\_arns](#input\_kinesis\_firehose\_target\_arns) | The Amazon Resource Name (ARN) of the Kinesis Firehose Delivery Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | | [kinesis\_target\_arns](#input\_kinesis\_target\_arns) | The Amazon Resource Name (ARN) of the Kinesis Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | -| [kms\_key\_identifier](#input\_kms\_key\_identifier) | The identifier of the AWS KMS customer managed key for EventBridge to use, if you choose to use a customer managed key to encrypt events on this event bus. The identifier can be the key Amazon Resource Name (ARN), KeyId, key alias, or key alias ARN. | `string` | `null` | no | +| [kms\_key\_identifier](#input\_kms\_key\_identifier) | The identifier of the AWS KMS customer managed key for EventBridge to use, to encrypt events on this event bus. The identifier can be the key Amazon Resource Name (ARN), KeyId, key alias, or key alias ARN. | `string` | n/a | yes | | [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 | diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 29ad8956..2cdabc4f 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -177,9 +177,9 @@ variable "event_source_name" { } variable "kms_key_identifier" { - description = "The identifier of the AWS KMS customer managed key for EventBridge to use, if you choose to use a customer managed key to encrypt events on this event bus. The identifier can be the key Amazon Resource Name (ARN), KeyId, key alias, or key alias ARN." + description = "The identifier of the AWS KMS customer managed key for EventBridge to use, to encrypt events on this event bus. The identifier can be the key Amazon Resource Name (ARN), KeyId, key alias, or key alias ARN." type = string - default = null + nullable = false } variable "dead_letter_config" { From d8418b982483c27203f7567d0aeab95b1f635daf Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 15:53:53 +0100 Subject: [PATCH 26/64] feat: enforce encryption of events on the event bus --- infrastructure/modules/eventbridge/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 22fff8b5..dae111e1 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -32,7 +32,7 @@ module "eventbridge" { log_delivery = local.log_delivery # DAVEH: per-destination KMS keys can come from var.log_delivery..kms_key_identifier or fall back to unencrypted log_delivery_source_name = var.log_delivery_source_name event_source_name = var.event_source_name - kms_key_identifier = var.kms_key_identifier # DAVEH: KMS key for the event bus comes from var.kms_key_identifier + kms_key_identifier = var.kms_key_identifier dead_letter_config = var.dead_letter_config schemas_discoverer_description = var.schemas_discoverer_description rules = var.rules From 38de2c50735b5ef6d3898883d6b3a938743c0083 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 16:47:35 +0100 Subject: [PATCH 27/64] docs: comment was inaccurate: per-destination KMS keys are configured by the destination, not by these delivery rules --- infrastructure/modules/eventbridge/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index dae111e1..37cf4584 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -29,7 +29,7 @@ module "eventbridge" { bus_name = local.bus_name bus_description = var.bus_description log_config = var.log_config - log_delivery = local.log_delivery # DAVEH: per-destination KMS keys can come from var.log_delivery..kms_key_identifier or fall back to unencrypted + log_delivery = local.log_delivery log_delivery_source_name = var.log_delivery_source_name event_source_name = var.event_source_name kms_key_identifier = var.kms_key_identifier From c7a0b1d12795c81cf55227818b5efeaa0582c3bd Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 17:02:55 +0100 Subject: [PATCH 28/64] feat: force connections to use explicit KMS keys for encryption --- infrastructure/modules/eventbridge/README.md | 2 +- infrastructure/modules/eventbridge/main.tf | 2 +- .../modules/eventbridge/variables.tf | 20 +++++++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 53699547..b1dc8079 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -83,7 +83,7 @@ No resources. | [bus\_description](#input\_bus\_description) | Event bus description | `string` | `null` | no | | [bus\_name](#input\_bus\_name) | A unique name for your EventBridge Bus. Must be unique per AWS account and region. Defaults to whatever the tags module produces | `string` | `null` | no | | [cloudwatch\_target\_arns](#input\_cloudwatch\_target\_arns) | The Amazon Resource Name (ARN) of the Cloudwatch Log Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | -| [connections](#input\_connections) | A map of objects with EventBridge Connection definitions. | `any` | `{}` | no | +| [connections](#input\_connections) | A map of objects with EventBridge Connection definitions. |
map(object({
authorization_type = string
auth_parameters = any
kms_key_identifier = string
description = optional(string)
invocation_connectivity_parameters = optional(any)
}))
| `{}` | 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 | | [create\_api\_destinations](#input\_create\_api\_destinations) | Controls whether EventBridge Destination resources should be created | `bool` | `false` | no | | [create\_archives](#input\_create\_archives) | Controls whether EventBridge Archive resources should be created | `bool` | `false` | no | diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 37cf4584..a08b2003 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -39,7 +39,7 @@ module "eventbridge" { targets = var.targets archives = var.archives permissions = var.permissions - connections = local.connections # DAVEH: per-connection KMS keys can come from var.connections..kms_key_identifier or fall back to unencrypted + connections = local.connections api_destinations = local.api_destinations schedule_groups = local.schedule_groups schedules = var.schedules # DAVEH: per-schedule KMS keys can come from var.schedules..kms_key_arn or fall back to unencrypted diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 2cdabc4f..18657cff 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -220,8 +220,24 @@ variable "permissions" { variable "connections" { description = "A map of objects with EventBridge Connection definitions." - type = any - default = {} + type = map(object({ + authorization_type = string + auth_parameters = any + kms_key_identifier = string + description = optional(string) + invocation_connectivity_parameters = optional(any) + })) + default = {} + + validation { + condition = alltrue([ + for connection in var.connections : + connection.kms_key_identifier != null && + trimspace(connection.kms_key_identifier) != "" + ]) + + error_message = "Each connection must specify a non-empty kms_key_identifier." + } } variable "api_destinations" { From 2ac949f2fb4ab7e5a67fafb38ae885c24522e9ed Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 17:25:40 +0100 Subject: [PATCH 29/64] feat: force schedules to use explicit KMS keys for encryption --- infrastructure/modules/eventbridge/README.md | 2 +- infrastructure/modules/eventbridge/main.tf | 4 +-- .../modules/eventbridge/variables.tf | 36 +++++++++++++++++-- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index b1dc8079..0929ebd4 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -148,7 +148,7 @@ No resources. | [rules](#input\_rules) | A map of objects with EventBridge Rule definitions. | `map(any)` | `{}` | no | | [schedule\_group\_timeouts](#input\_schedule\_group\_timeouts) | A map of objects with EventBridge Schedule Group create and delete timeouts. | `map(string)` | `{}` | no | | [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions. | `any` | `{}` | no | -| [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions. | `map(any)` | `{}` | no | +| [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions. |
map(object({
arn = string
schedule_expression = string
name_prefix = optional(string)
description = optional(string)
group_name = optional(string)
start_date = optional(string)
end_date = optional(string)
kms_key_arn = string
timezone = optional(string)
state = optional(bool, true)
maximum_window_in_minutes = optional(number)
use_flexible_time_window = optional(bool, false)
role_arn = optional(string)
input = optional(string)
dead_letter_arn = optional(string)
ecs_parameters = optional(any)
eventbridge_parameters = optional(any)
partition_key = optional(string)
sagemaker_pipeline_parameters = optional(any)
message_group_id = optional(string)
retry_policy = optional(any)
}))
| `{}` | no | | [schemas\_discoverer\_description](#input\_schemas\_discoverer\_description) | Default schemas discoverer description | `string` | `"Auto schemas discoverer event"` | 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 | diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index a08b2003..e8258b6a 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -42,8 +42,8 @@ module "eventbridge" { connections = local.connections api_destinations = local.api_destinations schedule_groups = local.schedule_groups - schedules = var.schedules # DAVEH: per-schedule KMS keys can come from var.schedules..kms_key_arn or fall back to unencrypted - pipes = local.pipes # DAVEH: per-pipe KMS keys can come from var.pipes..kms_key_identifier or fall back to unencrypted + schedules = var.schedules + pipes = local.pipes # DAVEH: per-pipe KMS keys can come from var.pipes..kms_key_identifier or fall back to unencrypted schedule_group_timeouts = var.schedule_group_timeouts role_name = var.role_name role_description = var.role_description diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 18657cff..dc571316 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -254,8 +254,40 @@ variable "schedule_groups" { variable "schedules" { description = "A map of objects with EventBridge Schedule definitions." - type = map(any) - default = {} + type = map(object({ + arn = string + schedule_expression = string + name_prefix = optional(string) + description = optional(string) + group_name = optional(string) + start_date = optional(string) + end_date = optional(string) + kms_key_arn = string + timezone = optional(string) + state = optional(bool, true) + maximum_window_in_minutes = optional(number) + use_flexible_time_window = optional(bool, false) + role_arn = optional(string) + input = optional(string) + dead_letter_arn = optional(string) + ecs_parameters = optional(any) + eventbridge_parameters = optional(any) + partition_key = optional(string) + sagemaker_pipeline_parameters = optional(any) + message_group_id = optional(string) + retry_policy = optional(any) + })) + default = {} + + validation { + condition = alltrue([ + for schedule in var.schedules : + schedule.kms_key_arn != null && + trimspace(schedule.kms_key_arn) != "" + ]) + + error_message = "Each schedule must specify a non-empty kms_key_arn." + } } variable "pipes" { From 4cdf6e12f33eed76e269d31655090816e1a40420 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 17:38:26 +0100 Subject: [PATCH 30/64] feat: force pipes to use explicit KMS keys for encryption --- infrastructure/modules/eventbridge/README.md | 4 ++- infrastructure/modules/eventbridge/locals.tf | 4 ++- infrastructure/modules/eventbridge/main.tf | 2 +- .../modules/eventbridge/variables.tf | 29 +++++++++++++++++-- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 0929ebd4..da07c059 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -24,6 +24,8 @@ DAVEH: document var.pipes DAVEH: document var.kms_key_identifier +DAVEH: document var.pipes + @@ -128,7 +130,7 @@ No resources. | [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 | | [permissions](#input\_permissions) | A map of objects with EventBridge Permission definitions. | `map(any)` | `{}` | no | -| [pipes](#input\_pipes) | A map of objects with EventBridge Pipe definitions. | `any` | `{}` | no | +| [pipes](#input\_pipes) | A map of EventBridge Pipe definitions. |
map(object({
role_arn = optional(string)
source = string
target = string
kms_key_identifier = string
description = optional(string)
desired_state = optional(string)
source_parameters = optional(any)
target_parameters = optional(any)
enrichment = optional(string)
enrichment_parameters = optional(any)
log_configuration = optional(any)
tags = optional(map(string), {})
}))
| `{}` | no | | [policies](#input\_policies) | List of policy statements ARN to attach to IAM role | `list(string)` | `[]` | no | | [policy](#input\_policy) | An additional policy document ARN to attach to IAM role | `string` | `null` | no | | [policy\_json](#input\_policy\_json) | An additional policy document as JSON to attach to IAM role | `string` | `null` | no | diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index a27fd54b..10122cb8 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -44,6 +44,8 @@ locals { # pipe names must be unique per AWS account and region # prefix keys with the module ID to ensure uniqueness pipes = { - for k, v in var.pipes : "${module.this.id}-${k}" => v + for k, v in var.pipes : "${module.this.id}-${k}" => { + for attribute, value in v : attribute => value if value != null + } } } diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index e8258b6a..87b4b486 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -43,7 +43,7 @@ module "eventbridge" { api_destinations = local.api_destinations schedule_groups = local.schedule_groups schedules = var.schedules - pipes = local.pipes # DAVEH: per-pipe KMS keys can come from var.pipes..kms_key_identifier or fall back to unencrypted + pipes = local.pipes schedule_group_timeouts = var.schedule_group_timeouts role_name = var.role_name role_description = var.role_description diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index dc571316..91c25c33 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -291,9 +291,32 @@ variable "schedules" { } variable "pipes" { - description = "A map of objects with EventBridge Pipe definitions." - type = any - default = {} + description = "A map of EventBridge Pipe definitions." + type = map(object({ + role_arn = optional(string) + source = string + target = string + kms_key_identifier = string + description = optional(string) + desired_state = optional(string) + source_parameters = optional(any) + target_parameters = optional(any) + enrichment = optional(string) + enrichment_parameters = optional(any) + log_configuration = optional(any) + tags = optional(map(string), {}) + })) + default = {} + + validation { + condition = alltrue([ + for pipe in var.pipes : + pipe.kms_key_identifier != null && + trimspace(pipe.kms_key_identifier) != "" + ]) + + error_message = "Each pipe must specify a non-empty kms_key_identifier." + } } variable "schedule_group_timeouts" { From 6424f85903885c449789220dd16df246f2151309 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Wed, 16 Sep 2026 17:43:08 +0100 Subject: [PATCH 31/64] docs: update todos --- infrastructure/modules/eventbridge/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index da07c059..0e6466cb 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -24,6 +24,10 @@ DAVEH: document var.pipes DAVEH: document var.kms_key_identifier +DAVEH: document var.connections + +DAVEH: document var.schedules + DAVEH: document var.pipes From d3479a9eabd2ad49153270b3b283d0190717785a Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Mon, 21 Sep 2026 08:39:40 +0100 Subject: [PATCH 32/64] chore: use more precise type+validation for schedule_groups --- infrastructure/modules/eventbridge/README.md | 2 +- infrastructure/modules/eventbridge/variables.tf | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 0e6466cb..66f12749 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -153,7 +153,7 @@ No resources. | [role\_tags](#input\_role\_tags) | A map of tags to assign to IAM role | `map(string)` | `{}` | no | | [rules](#input\_rules) | A map of objects with EventBridge Rule definitions. | `map(any)` | `{}` | no | | [schedule\_group\_timeouts](#input\_schedule\_group\_timeouts) | A map of objects with EventBridge Schedule Group create and delete timeouts. | `map(string)` | `{}` | no | -| [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions. | `any` | `{}` | no | +| [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions. |
map(object({
name = optional(string)
name_prefix = optional(string)
tags = optional(map(string), {})
}))
| `{}` | no | | [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions. |
map(object({
arn = string
schedule_expression = string
name_prefix = optional(string)
description = optional(string)
group_name = optional(string)
start_date = optional(string)
end_date = optional(string)
kms_key_arn = string
timezone = optional(string)
state = optional(bool, true)
maximum_window_in_minutes = optional(number)
use_flexible_time_window = optional(bool, false)
role_arn = optional(string)
input = optional(string)
dead_letter_arn = optional(string)
ecs_parameters = optional(any)
eventbridge_parameters = optional(any)
partition_key = optional(string)
sagemaker_pipeline_parameters = optional(any)
message_group_id = optional(string)
retry_policy = optional(any)
}))
| `{}` | no | | [schemas\_discoverer\_description](#input\_schemas\_discoverer\_description) | Default schemas discoverer description | `string` | `"Auto schemas discoverer event"` | 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 | diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 91c25c33..b49f9815 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -248,8 +248,21 @@ variable "api_destinations" { variable "schedule_groups" { description = "A map of objects with EventBridge Schedule Group definitions." - type = any - default = {} + type = map(object({ + name = optional(string) + name_prefix = optional(string) + tags = optional(map(string), {}) + })) + default = {} + + validation { + condition = alltrue([ + for schedule_group in var.schedule_groups : + schedule_group.name == null || schedule_group.name_prefix == null + ]) + + error_message = "Each schedule group may specify either name or name_prefix, but not both." + } } variable "schedules" { From 33b80eb6265683bc84104f6e11206315fc08da56 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Mon, 21 Sep 2026 10:14:26 +0100 Subject: [PATCH 33/64] feat: fix group names in schedules to match names in schedule groups --- infrastructure/modules/eventbridge/README.md | 2 +- infrastructure/modules/eventbridge/locals.tf | 24 ++++++++++++------- infrastructure/modules/eventbridge/main.tf | 2 +- .../modules/eventbridge/variables.tf | 15 ++---------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 66f12749..e54d9f0f 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -153,7 +153,7 @@ No resources. | [role\_tags](#input\_role\_tags) | A map of tags to assign to IAM role | `map(string)` | `{}` | no | | [rules](#input\_rules) | A map of objects with EventBridge Rule definitions. | `map(any)` | `{}` | no | | [schedule\_group\_timeouts](#input\_schedule\_group\_timeouts) | A map of objects with EventBridge Schedule Group create and delete timeouts. | `map(string)` | `{}` | no | -| [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions. |
map(object({
name = optional(string)
name_prefix = optional(string)
tags = optional(map(string), {})
}))
| `{}` | no | +| [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions. Names are derived from the object keys and cannot be overridden. |
map(object({
tags = optional(map(string), {})
}))
| `{}` | no | | [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions. |
map(object({
arn = string
schedule_expression = string
name_prefix = optional(string)
description = optional(string)
group_name = optional(string)
start_date = optional(string)
end_date = optional(string)
kms_key_arn = string
timezone = optional(string)
state = optional(bool, true)
maximum_window_in_minutes = optional(number)
use_flexible_time_window = optional(bool, false)
role_arn = optional(string)
input = optional(string)
dead_letter_arn = optional(string)
ecs_parameters = optional(any)
eventbridge_parameters = optional(any)
partition_key = optional(string)
sagemaker_pipeline_parameters = optional(any)
message_group_id = optional(string)
retry_policy = optional(any)
}))
| `{}` | no | | [schemas\_discoverer\_description](#input\_schemas\_discoverer\_description) | Default schemas discoverer description | `string` | `"Auto schemas discoverer event"` | 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 | diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 10122cb8..8b60dc6f 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -26,18 +26,26 @@ locals { } # schedule group names must be unique per AWS account and region - # respect `name` and `name_prefix` if given - # otherwise include module ID and key in `name_prefix` + # prefix provided names with the module ID to ensure uniqueness + # disallow explicitly setting `name` or `name_prefix` schedule_groups = { for k, v in var.schedule_groups : k => ( - contains(keys(v), "name") || contains(keys(v), "name_prefix") - ? v - : merge( + merge(v, { name = "${module.this.id}-${k}" }) + ) + } + + # if a schedule gives a group_name, fix it to match the corresponding + # name in schedule_groups + schedules = { + for k, v in var.schedules : k => ( + contains(keys(v), "group_name") && local.schedule_groups[v.group_name] != null + ? merge( + v, { - name_prefix = "${module.this.id}-${k}" - }, - v + group_name = local.schedule_groups[v.group_name].name + } ) + : v ) } diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 87b4b486..f8f34fe3 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -42,7 +42,7 @@ module "eventbridge" { connections = local.connections api_destinations = local.api_destinations schedule_groups = local.schedule_groups - schedules = var.schedules + schedules = local.schedules pipes = local.pipes schedule_group_timeouts = var.schedule_group_timeouts role_name = var.role_name diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index b49f9815..c1c42ac5 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -247,22 +247,11 @@ variable "api_destinations" { } variable "schedule_groups" { - description = "A map of objects with EventBridge Schedule Group definitions." + description = "A map of objects with EventBridge Schedule Group definitions. Names are derived from the object keys and cannot be overridden." type = map(object({ - name = optional(string) - name_prefix = optional(string) - tags = optional(map(string), {}) + tags = optional(map(string), {}) })) default = {} - - validation { - condition = alltrue([ - for schedule_group in var.schedule_groups : - schedule_group.name == null || schedule_group.name_prefix == null - ]) - - error_message = "Each schedule group may specify either name or name_prefix, but not both." - } } variable "schedules" { From f74805c558d908c11eed90d534f0d0317ccfbced Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 22 Sep 2026 11:42:34 +0100 Subject: [PATCH 34/64] chore: todos --- infrastructure/modules/eventbridge/locals.tf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 8b60dc6f..e73d03ab 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -24,6 +24,9 @@ locals { api_destinations = { for k, v in var.api_destinations : "${module.this.id}-${k}" => v } + # DAVEH: API destination → connection + # The wrapper prefixes connections keys in locals.tf:18-21, but does + # not update api_destinations[*].connection_name. # schedule group names must be unique per AWS account and region # prefix provided names with the module ID to ensure uniqueness @@ -56,4 +59,10 @@ locals { for attribute, value in v : attribute => value if value != null } } + # DAVEH: Pipe → API destination enrichment + # The wrapper prefixes API-destination keys, but leaves pipes[*].enrichment unchanged. + + # DAVEH: EventBridge target → API destination + # The wrapper prefixes api_destinations keys in locals.tf:23-26, but + # leaves targets[*].destination unchanged. } From 8fd74b15bc41b6836256f96cc71886d3bc442ac0 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 22 Sep 2026 11:54:30 +0100 Subject: [PATCH 35/64] feat: more precise types for inputs --- infrastructure/modules/eventbridge/README.md | 14 +-- .../modules/eventbridge/variables.tf | 85 ++++++++++++++++--- 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index e54d9f0f..623c119a 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -60,7 +60,7 @@ No resources. | 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 | -| [api\_destinations](#input\_api\_destinations) | A map of objects with EventBridge Destination definitions. | `map(any)` | `{}` | no | +| [api\_destinations](#input\_api\_destinations) | A map of objects with EventBridge Destination definitions. |
map(object({
description = optional(string)
invocation_endpoint = string
http_method = string
invocation_rate_limit_per_second = optional(number)
connection_name = optional(string)
}))
| `{}` | no | | [append\_connection\_postfix](#input\_append\_connection\_postfix) | Controls whether to append '-connection' to the name of the connection | `bool` | `true` | no | | [append\_destination\_postfix](#input\_append\_destination\_postfix) | Controls whether to append '-destination' to the name of the destination | `bool` | `true` | no | | [append\_pipe\_postfix](#input\_append\_pipe\_postfix) | Controls whether to append '-pipe' to the name of the pipe | `bool` | `true` | no | @@ -68,7 +68,7 @@ No resources. | [append\_schedule\_group\_postfix](#input\_append\_schedule\_group\_postfix) | Controls whether to append '-group' to the name of the schedule group | `bool` | `true` | no | | [append\_schedule\_postfix](#input\_append\_schedule\_postfix) | Controls whether to append '-schedule' to the name of the schedule | `bool` | `true` | no | | [application\_role](#input\_application\_role) | The role the application is performing | `string` | `"General"` | no | -| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions. | `map(any)` | `{}` | no | +| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions. |
map(object({
name = optional(string)
event_source_arn = optional(string)
description = optional(string)
event_pattern = optional(string)
retention_days = optional(number)
kms_key_identifier = optional(string)
}))
| `{}` | no | | [attach\_api\_destination\_policy](#input\_attach\_api\_destination\_policy) | Controls whether the API Destination policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | | [attach\_cloudwatch\_policy](#input\_attach\_cloudwatch\_policy) | Controls whether the Cloudwatch policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | | [attach\_ecs\_policy](#input\_attach\_ecs\_policy) | Controls whether the ECS policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | @@ -108,7 +108,7 @@ No resources. | [create\_targets](#input\_create\_targets) | Controls whether EventBridge Target resources should be created | `bool` | `true` | 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 | -| [dead\_letter\_config](#input\_dead\_letter\_config) | Configuration details of the Amazon SQS queue for EventBridge to use as a dead-letter queue (DLQ) | `any` | `{}` | no | +| [dead\_letter\_config](#input\_dead\_letter\_config) | Configuration details of the Amazon SQS queue for EventBridge to use as a dead-letter queue (DLQ) |
object({
arn = optional(string)
})
| `{}` | 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 | | [ecs\_pass\_role\_resources](#input\_ecs\_pass\_role\_resources) | List of approved roles to be passed | `list(string)` | `[]` | no | @@ -133,14 +133,14 @@ No resources. | [number\_of\_policy\_jsons](#input\_number\_of\_policy\_jsons) | Number of policies JSON to attach to IAM role | `number` | `0` | 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 | -| [permissions](#input\_permissions) | A map of objects with EventBridge Permission definitions. | `map(any)` | `{}` | no | +| [permissions](#input\_permissions) | A map of objects with EventBridge Permission definitions. |
map(object({
action = optional(string)
event_bus_name = optional(string)
condition_org = optional(string)
}))
| `{}` | no | | [pipes](#input\_pipes) | A map of EventBridge Pipe definitions. |
map(object({
role_arn = optional(string)
source = string
target = string
kms_key_identifier = string
description = optional(string)
desired_state = optional(string)
source_parameters = optional(any)
target_parameters = optional(any)
enrichment = optional(string)
enrichment_parameters = optional(any)
log_configuration = optional(any)
tags = optional(map(string), {})
}))
| `{}` | no | | [policies](#input\_policies) | List of policy statements ARN to attach to IAM role | `list(string)` | `[]` | no | | [policy](#input\_policy) | An additional policy document ARN to attach to IAM role | `string` | `null` | no | | [policy\_json](#input\_policy\_json) | An additional policy document as JSON to attach to IAM role | `string` | `null` | no | | [policy\_jsons](#input\_policy\_jsons) | List of additional policy documents as JSON to attach to IAM role | `list(string)` | `[]` | no | | [policy\_path](#input\_policy\_path) | Path of IAM policy to use for EventBridge | `string` | `null` | no | -| [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to IAM role | `any` | `{}` | no | +| [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to IAM role |
map(object({
sid = optional(string)
effect = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(any)
not_principals = optional(any)
condition = optional(any)
}))
| `{}` | 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 | @@ -151,7 +151,7 @@ No resources. | [role\_path](#input\_role\_path) | Path of IAM role to use for EventBridge | `string` | `null` | no | | [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the IAM role used by EventBridge | `string` | `null` | no | | [role\_tags](#input\_role\_tags) | A map of tags to assign to IAM role | `map(string)` | `{}` | no | -| [rules](#input\_rules) | A map of objects with EventBridge Rule definitions. | `map(any)` | `{}` | no | +| [rules](#input\_rules) | A map of objects with EventBridge Rule definitions. |
map(object({
name_prefix = optional(string)
description = optional(string)
event_pattern = optional(string)
schedule_expression = optional(string)
role_arn = optional(bool)
enabled = optional(bool)
state = optional(string)
force_destroy = optional(bool)
}))
| `{}` | no | | [schedule\_group\_timeouts](#input\_schedule\_group\_timeouts) | A map of objects with EventBridge Schedule Group create and delete timeouts. | `map(string)` | `{}` | no | | [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions. Names are derived from the object keys and cannot be overridden. |
map(object({
tags = optional(map(string), {})
}))
| `{}` | no | | [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions. |
map(object({
arn = string
schedule_expression = string
name_prefix = optional(string)
description = optional(string)
group_name = optional(string)
start_date = optional(string)
end_date = optional(string)
kms_key_arn = string
timezone = optional(string)
state = optional(bool, true)
maximum_window_in_minutes = optional(number)
use_flexible_time_window = optional(bool, false)
role_arn = optional(string)
input = optional(string)
dead_letter_arn = optional(string)
ecs_parameters = optional(any)
eventbridge_parameters = optional(any)
partition_key = optional(string)
sagemaker_pipeline_parameters = optional(any)
message_group_id = optional(string)
retry_policy = optional(any)
}))
| `{}` | no | @@ -165,7 +165,7 @@ No resources. | [stack](#input\_stack) | ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks` | `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 | -| [targets](#input\_targets) | A map of objects with EventBridge Target definitions. | `any` | `{}` | no | +| [targets](#input\_targets) | A map of objects with EventBridge Target definitions. |
map(list(object({
name = string
arn = optional(string)
destination = optional(string)
target_id = optional(string)
input = optional(string)
input_path = optional(string)
force_destroy = optional(bool)
attach_role_arn = optional(bool)
run_command_targets = optional(any)
ecs_target = optional(any)
batch_target = optional(any)
partition_key_path = optional(string)
message_group_id = optional(string)
http_target = optional(any)
appsync_target = optional(any)
input_transformer = optional(any)
dead_letter_arn = optional(string)
retry_policy = optional(any)
})))
| `{}` | 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 | | [trusted\_entities](#input\_trusted\_entities) | Additional trusted entities for assuming roles (trust relationship) | `list(string)` | `[]` | no | diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index c1c42ac5..c199c298 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -184,8 +184,10 @@ variable "kms_key_identifier" { variable "dead_letter_config" { description = "Configuration details of the Amazon SQS queue for EventBridge to use as a dead-letter queue (DLQ)" - type = any - default = {} + type = object({ + arn = optional(string) + }) + default = {} } variable "schemas_discoverer_description" { @@ -196,26 +198,65 @@ variable "schemas_discoverer_description" { variable "rules" { description = "A map of objects with EventBridge Rule definitions." - type = map(any) - default = {} + type = map(object({ + name_prefix = optional(string) + description = optional(string) + event_pattern = optional(string) + schedule_expression = optional(string) + role_arn = optional(bool) + enabled = optional(bool) + state = optional(string) + force_destroy = optional(bool) + })) + default = {} } variable "targets" { description = "A map of objects with EventBridge Target definitions." - type = any - default = {} + type = map(list(object({ + name = string + arn = optional(string) + destination = optional(string) + target_id = optional(string) + input = optional(string) + input_path = optional(string) + force_destroy = optional(bool) + attach_role_arn = optional(bool) + run_command_targets = optional(any) + ecs_target = optional(any) + batch_target = optional(any) + partition_key_path = optional(string) + message_group_id = optional(string) + http_target = optional(any) + appsync_target = optional(any) + input_transformer = optional(any) + dead_letter_arn = optional(string) + retry_policy = optional(any) + }))) + default = {} } variable "archives" { description = "A map of objects with the EventBridge Archive definitions." - type = map(any) - default = {} + type = map(object({ + name = optional(string) + event_source_arn = optional(string) + description = optional(string) + event_pattern = optional(string) + retention_days = optional(number) + kms_key_identifier = optional(string) + })) + default = {} } variable "permissions" { description = "A map of objects with EventBridge Permission definitions." - type = map(any) - default = {} + type = map(object({ + action = optional(string) + event_bus_name = optional(string) + condition_org = optional(string) + })) + default = {} } variable "connections" { @@ -242,8 +283,14 @@ variable "connections" { variable "api_destinations" { description = "A map of objects with EventBridge Destination definitions." - type = map(any) - default = {} + type = map(object({ + description = optional(string) + invocation_endpoint = string + http_method = string + invocation_rate_limit_per_second = optional(number) + connection_name = optional(string) + })) + default = {} } variable "schedule_groups" { @@ -563,6 +610,16 @@ variable "policies" { variable "policy_statements" { description = "Map of dynamic policy statements to attach to IAM role" - type = any - default = {} + type = map(object({ + sid = optional(string) + effect = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(any) + not_principals = optional(any) + condition = optional(any) + })) + default = {} } From 4c26c968423cdf569875df6964a6f5c2d7bdabfd Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 22 Sep 2026 14:05:57 +0100 Subject: [PATCH 36/64] feat: insist on every archive having a kms_key_identifier --- infrastructure/modules/eventbridge/README.md | 4 +++- infrastructure/modules/eventbridge/variables.tf | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 623c119a..b48a4881 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -30,6 +30,8 @@ DAVEH: document var.schedules DAVEH: document var.pipes +DAVEH: document var.archives + @@ -68,7 +70,7 @@ No resources. | [append\_schedule\_group\_postfix](#input\_append\_schedule\_group\_postfix) | Controls whether to append '-group' to the name of the schedule group | `bool` | `true` | no | | [append\_schedule\_postfix](#input\_append\_schedule\_postfix) | Controls whether to append '-schedule' to the name of the schedule | `bool` | `true` | no | | [application\_role](#input\_application\_role) | The role the application is performing | `string` | `"General"` | no | -| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions. |
map(object({
name = optional(string)
event_source_arn = optional(string)
description = optional(string)
event_pattern = optional(string)
retention_days = optional(number)
kms_key_identifier = optional(string)
}))
| `{}` | no | +| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions. |
map(object({
name = optional(string)
event_source_arn = optional(string)
description = optional(string)
event_pattern = optional(string)
retention_days = optional(number)
kms_key_identifier = string
}))
| `{}` | no | | [attach\_api\_destination\_policy](#input\_attach\_api\_destination\_policy) | Controls whether the API Destination policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | | [attach\_cloudwatch\_policy](#input\_attach\_cloudwatch\_policy) | Controls whether the Cloudwatch policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | | [attach\_ecs\_policy](#input\_attach\_ecs\_policy) | Controls whether the ECS policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index c199c298..9f1cb6f1 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -244,9 +244,19 @@ variable "archives" { description = optional(string) event_pattern = optional(string) retention_days = optional(number) - kms_key_identifier = optional(string) + kms_key_identifier = string })) default = {} + + validation { + condition = alltrue([ + for archive in var.archives : + archive.kms_key_identifier != null && + trimspace(archive.kms_key_identifier) != "" + ]) + + error_message = "Each archive must specify a non-empty kms_key_identifier." + } } variable "permissions" { From b64a31c07de9fdcd9f8a633e5e2e08b07898a6d3 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 22 Sep 2026 14:22:34 +0100 Subject: [PATCH 37/64] feat: mangle `connection_name` field of `var.api_destinations` --- infrastructure/modules/eventbridge/locals.tf | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index e73d03ab..8a11900c 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -19,14 +19,20 @@ locals { for k, v in var.connections : "${module.this.id}-${k}" => v } - # API destination names must be unique per AWS account and region - # prefix provided names with the module ID to ensure uniqueness + # API destination names must be unique per AWS account and region. + # We prefix provided names with the module ID to ensure uniqueness. + # Furthermore, `connection_name`s must match up with the keys of + # `connections`. api_destinations = { - for k, v in var.api_destinations : "${module.this.id}-${k}" => v + for k, v in var.api_destinations : "${module.this.id}-${k}" => merge( + v, + can(v, "connection_name") + && v.connection_name != null + && contains(keys(var.connections), v.connection_name) + ? { connection_name = "${module.this.id}-${v.connection_name}" } + : {} + ) } - # DAVEH: API destination → connection - # The wrapper prefixes connections keys in locals.tf:18-21, but does - # not update api_destinations[*].connection_name. # schedule group names must be unique per AWS account and region # prefix provided names with the module ID to ensure uniqueness From b4e849e044bbddf42dd1c3df2f942902dd1ba2a3 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Fri, 25 Sep 2026 10:28:58 +0100 Subject: [PATCH 38/64] fix: adding optional fields to the type causes trouble for the underlying module so revert but document conceptual type --- infrastructure/modules/eventbridge/README.md | 22 +- .../modules/eventbridge/variables.tf | 367 +++++++++++------- 2 files changed, 245 insertions(+), 144 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index b48a4881..ce32cfa8 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -62,7 +62,7 @@ No resources. | 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 | -| [api\_destinations](#input\_api\_destinations) | A map of objects with EventBridge Destination definitions. |
map(object({
description = optional(string)
invocation_endpoint = string
http_method = string
invocation_rate_limit_per_second = optional(number)
connection_name = optional(string)
}))
| `{}` | no | +| [api\_destinations](#input\_api\_destinations) | A map of objects with EventBridge Destination definitions.

The type should really be

map(object({
description = optional(string)
invocation\_endpoint = string
http\_method = string
invocation\_rate\_limit\_per\_second = optional(number)
connection\_name = optional(string)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [append\_connection\_postfix](#input\_append\_connection\_postfix) | Controls whether to append '-connection' to the name of the connection | `bool` | `true` | no | | [append\_destination\_postfix](#input\_append\_destination\_postfix) | Controls whether to append '-destination' to the name of the destination | `bool` | `true` | no | | [append\_pipe\_postfix](#input\_append\_pipe\_postfix) | Controls whether to append '-pipe' to the name of the pipe | `bool` | `true` | no | @@ -70,7 +70,7 @@ No resources. | [append\_schedule\_group\_postfix](#input\_append\_schedule\_group\_postfix) | Controls whether to append '-group' to the name of the schedule group | `bool` | `true` | no | | [append\_schedule\_postfix](#input\_append\_schedule\_postfix) | Controls whether to append '-schedule' to the name of the schedule | `bool` | `true` | no | | [application\_role](#input\_application\_role) | The role the application is performing | `string` | `"General"` | no | -| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions. |
map(object({
name = optional(string)
event_source_arn = optional(string)
description = optional(string)
event_pattern = optional(string)
retention_days = optional(number)
kms_key_identifier = string
}))
| `{}` | no | +| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions.

The type should really be

map(object({
name = optional(string)
event\_source\_arn = optional(string)
description = optional(string)
event\_pattern = optional(string)
retention\_days = optional(number)
kms\_key\_identifier = string
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | n/a | yes | | [attach\_api\_destination\_policy](#input\_attach\_api\_destination\_policy) | Controls whether the API Destination policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | | [attach\_cloudwatch\_policy](#input\_attach\_cloudwatch\_policy) | Controls whether the Cloudwatch policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | | [attach\_ecs\_policy](#input\_attach\_ecs\_policy) | Controls whether the ECS policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | @@ -91,7 +91,7 @@ No resources. | [bus\_description](#input\_bus\_description) | Event bus description | `string` | `null` | no | | [bus\_name](#input\_bus\_name) | A unique name for your EventBridge Bus. Must be unique per AWS account and region. Defaults to whatever the tags module produces | `string` | `null` | no | | [cloudwatch\_target\_arns](#input\_cloudwatch\_target\_arns) | The Amazon Resource Name (ARN) of the Cloudwatch Log Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | -| [connections](#input\_connections) | A map of objects with EventBridge Connection definitions. |
map(object({
authorization_type = string
auth_parameters = any
kms_key_identifier = string
description = optional(string)
invocation_connectivity_parameters = optional(any)
}))
| `{}` | no | +| [connections](#input\_connections) | A map of objects with EventBridge Connection definitions.

The type should really be

map(object({
authorization\_type = string
auth\_parameters = any
kms\_key\_identifier = string
description = optional(string)
invocation\_connectivity\_parameters = optional(any)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | 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 | | [create\_api\_destinations](#input\_create\_api\_destinations) | Controls whether EventBridge Destination resources should be created | `bool` | `false` | no | | [create\_archives](#input\_create\_archives) | Controls whether EventBridge Archive resources should be created | `bool` | `false` | no | @@ -110,7 +110,7 @@ No resources. | [create\_targets](#input\_create\_targets) | Controls whether EventBridge Target resources should be created | `bool` | `true` | 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 | -| [dead\_letter\_config](#input\_dead\_letter\_config) | Configuration details of the Amazon SQS queue for EventBridge to use as a dead-letter queue (DLQ) |
object({
arn = optional(string)
})
| `{}` | no | +| [dead\_letter\_config](#input\_dead\_letter\_config) | Configuration details of the Amazon SQS queue for EventBridge to use as a
dead-letter queue (DLQ).

The type should really be

object({
arn = optional(string)
})

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | 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 | | [ecs\_pass\_role\_resources](#input\_ecs\_pass\_role\_resources) | List of approved roles to be passed | `list(string)` | `[]` | no | @@ -135,14 +135,14 @@ No resources. | [number\_of\_policy\_jsons](#input\_number\_of\_policy\_jsons) | Number of policies JSON to attach to IAM role | `number` | `0` | 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 | -| [permissions](#input\_permissions) | A map of objects with EventBridge Permission definitions. |
map(object({
action = optional(string)
event_bus_name = optional(string)
condition_org = optional(string)
}))
| `{}` | no | -| [pipes](#input\_pipes) | A map of EventBridge Pipe definitions. |
map(object({
role_arn = optional(string)
source = string
target = string
kms_key_identifier = string
description = optional(string)
desired_state = optional(string)
source_parameters = optional(any)
target_parameters = optional(any)
enrichment = optional(string)
enrichment_parameters = optional(any)
log_configuration = optional(any)
tags = optional(map(string), {})
}))
| `{}` | no | +| [permissions](#input\_permissions) | A map of objects with EventBridge Permission definitions.

The type should really be

map(object({
action = optional(string)
event\_bus\_name = optional(string)
condition\_org = optional(string)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | +| [pipes](#input\_pipes) | A map of EventBridge Pipe definitions.

The type should really be

map(object({
role\_arn = optional(string)
source = string
target = string
kms\_key\_identifier = string
description = optional(string)
desired\_state = optional(string)
source\_parameters = optional(any)
target\_parameters = optional(any)
enrichment = optional(string)
enrichment\_parameters = optional(any)
log\_configuration = optional(any)
tags = optional(map(string), {})
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | no | | [policies](#input\_policies) | List of policy statements ARN to attach to IAM role | `list(string)` | `[]` | no | | [policy](#input\_policy) | An additional policy document ARN to attach to IAM role | `string` | `null` | no | | [policy\_json](#input\_policy\_json) | An additional policy document as JSON to attach to IAM role | `string` | `null` | no | | [policy\_jsons](#input\_policy\_jsons) | List of additional policy documents as JSON to attach to IAM role | `list(string)` | `[]` | no | | [policy\_path](#input\_policy\_path) | Path of IAM policy to use for EventBridge | `string` | `null` | no | -| [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to IAM role |
map(object({
sid = optional(string)
effect = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(any)
not_principals = optional(any)
condition = optional(any)
}))
| `{}` | no | +| [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to IAM role

The type should really be

map(object({
sid = optional(string)
effect = optional(string)
actions = optional(list(string))
not\_actions = optional(list(string))
resources = optional(list(string))
not\_resources = optional(list(string))
principals = optional(any)
not\_principals = optional(any)
condition = optional(any)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | 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 | @@ -153,10 +153,10 @@ No resources. | [role\_path](#input\_role\_path) | Path of IAM role to use for EventBridge | `string` | `null` | no | | [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the IAM role used by EventBridge | `string` | `null` | no | | [role\_tags](#input\_role\_tags) | A map of tags to assign to IAM role | `map(string)` | `{}` | no | -| [rules](#input\_rules) | A map of objects with EventBridge Rule definitions. |
map(object({
name_prefix = optional(string)
description = optional(string)
event_pattern = optional(string)
schedule_expression = optional(string)
role_arn = optional(bool)
enabled = optional(bool)
state = optional(string)
force_destroy = optional(bool)
}))
| `{}` | no | +| [rules](#input\_rules) | A map of objects with EventBridge Rule definitions.

The type should really be

map(object({
name\_prefix = optional(string)
description = optional(string)
event\_pattern = optional(string)
schedule\_expression = optional(string)
role\_arn = optional(bool)
enabled = optional(bool)
state = optional(string)
force\_destroy = optional(bool)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [schedule\_group\_timeouts](#input\_schedule\_group\_timeouts) | A map of objects with EventBridge Schedule Group create and delete timeouts. | `map(string)` | `{}` | no | -| [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions. Names are derived from the object keys and cannot be overridden. |
map(object({
tags = optional(map(string), {})
}))
| `{}` | no | -| [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions. |
map(object({
arn = string
schedule_expression = string
name_prefix = optional(string)
description = optional(string)
group_name = optional(string)
start_date = optional(string)
end_date = optional(string)
kms_key_arn = string
timezone = optional(string)
state = optional(bool, true)
maximum_window_in_minutes = optional(number)
use_flexible_time_window = optional(bool, false)
role_arn = optional(string)
input = optional(string)
dead_letter_arn = optional(string)
ecs_parameters = optional(any)
eventbridge_parameters = optional(any)
partition_key = optional(string)
sagemaker_pipeline_parameters = optional(any)
message_group_id = optional(string)
retry_policy = optional(any)
}))
| `{}` | no | +| [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions.

Names are derived from the object keys and cannot be overridden.

The type should really be

map(object({
tags = optional(map(string), {})
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | no | +| [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions.

The type should really be

map(object({
arn = string
schedule\_expression = string
name\_prefix = optional(string)
description = optional(string)
group\_name = optional(string)
start\_date = optional(string)
end\_date = optional(string)
kms\_key\_arn = string
timezone = optional(string)
state = optional(bool, true)
maximum\_window\_in\_minutes = optional(number)
use\_flexible\_time\_window = optional(bool, false)
role\_arn = optional(string)
input = optional(string)
dead\_letter\_arn = optional(string)
ecs\_parameters = optional(any)
eventbridge\_parameters = optional(any)
partition\_key = optional(string)
sagemaker\_pipeline\_parameters = optional(any)
message\_group\_id = optional(string)
retry\_policy = optional(any)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [schemas\_discoverer\_description](#input\_schemas\_discoverer\_description) | Default schemas discoverer description | `string` | `"Auto schemas discoverer event"` | 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 | @@ -167,7 +167,7 @@ No resources. | [stack](#input\_stack) | ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks` | `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 | -| [targets](#input\_targets) | A map of objects with EventBridge Target definitions. |
map(list(object({
name = string
arn = optional(string)
destination = optional(string)
target_id = optional(string)
input = optional(string)
input_path = optional(string)
force_destroy = optional(bool)
attach_role_arn = optional(bool)
run_command_targets = optional(any)
ecs_target = optional(any)
batch_target = optional(any)
partition_key_path = optional(string)
message_group_id = optional(string)
http_target = optional(any)
appsync_target = optional(any)
input_transformer = optional(any)
dead_letter_arn = optional(string)
retry_policy = optional(any)
})))
| `{}` | no | +| [targets](#input\_targets) | A map of objects with EventBridge Target definitions.

The type should really be

map(list(object({
name = string
arn = optional(string)
destination = optional(string)
target\_id = optional(string)
input = optional(string)
input\_path = optional(string)
force\_destroy = optional(bool)
attach\_role\_arn = optional(bool)
run\_command\_targets = optional(any)
ecs\_target = optional(any)
batch\_target = optional(any)
partition\_key\_path = optional(string)
message\_group\_id = optional(string)
http\_target = optional(any)
appsync\_target = optional(any)
input\_transformer = optional(any)
dead\_letter\_arn = optional(string)
retry\_policy = optional(any)
})))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | 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 | | [trusted\_entities](#input\_trusted\_entities) | Additional trusted entities for assuming roles (trust relationship) | `list(string)` | `[]` | no | diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 9f1cb6f1..cf63a5af 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -183,11 +183,21 @@ variable "kms_key_identifier" { } variable "dead_letter_config" { - description = "Configuration details of the Amazon SQS queue for EventBridge to use as a dead-letter queue (DLQ)" - type = object({ - arn = optional(string) - }) - default = {} + description = <<-EOF + Configuration details of the Amazon SQS queue for EventBridge to use as a + dead-letter queue (DLQ). + + The type should really be + + object({ + arn = optional(string) + }) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = any + default = {} } variable "schemas_discoverer_description" { @@ -197,56 +207,82 @@ variable "schemas_discoverer_description" { } variable "rules" { - description = "A map of objects with EventBridge Rule definitions." - type = map(object({ - name_prefix = optional(string) - description = optional(string) - event_pattern = optional(string) - schedule_expression = optional(string) - role_arn = optional(bool) - enabled = optional(bool) - state = optional(string) - force_destroy = optional(bool) - })) - default = {} + description = <<-EOF + A map of objects with EventBridge Rule definitions. + + The type should really be + + map(object({ + name_prefix = optional(string) + description = optional(string) + event_pattern = optional(string) + schedule_expression = optional(string) + role_arn = optional(bool) + enabled = optional(bool) + state = optional(string) + force_destroy = optional(bool) + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = map(any) + default = {} } variable "targets" { - description = "A map of objects with EventBridge Target definitions." - type = map(list(object({ - name = string - arn = optional(string) - destination = optional(string) - target_id = optional(string) - input = optional(string) - input_path = optional(string) - force_destroy = optional(bool) - attach_role_arn = optional(bool) - run_command_targets = optional(any) - ecs_target = optional(any) - batch_target = optional(any) - partition_key_path = optional(string) - message_group_id = optional(string) - http_target = optional(any) - appsync_target = optional(any) - input_transformer = optional(any) - dead_letter_arn = optional(string) - retry_policy = optional(any) - }))) - default = {} + description = <<-EOF + A map of objects with EventBridge Target definitions. + + The type should really be + + map(list(object({ + name = string + arn = optional(string) + destination = optional(string) + target_id = optional(string) + input = optional(string) + input_path = optional(string) + force_destroy = optional(bool) + attach_role_arn = optional(bool) + run_command_targets = optional(any) + ecs_target = optional(any) + batch_target = optional(any) + partition_key_path = optional(string) + message_group_id = optional(string) + http_target = optional(any) + appsync_target = optional(any) + input_transformer = optional(any) + dead_letter_arn = optional(string) + retry_policy = optional(any) + }))) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = any + default = {} } variable "archives" { - description = "A map of objects with the EventBridge Archive definitions." - type = map(object({ - name = optional(string) - event_source_arn = optional(string) - description = optional(string) - event_pattern = optional(string) - retention_days = optional(number) - kms_key_identifier = string - })) - default = {} + description = <<-EOF + A map of objects with the EventBridge Archive definitions. + + The type should really be + + map(object({ + name = optional(string) + event_source_arn = optional(string) + description = optional(string) + event_pattern = optional(string) + retention_days = optional(number) + kms_key_identifier = string + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = map(any) validation { condition = alltrue([ @@ -260,25 +296,43 @@ variable "archives" { } variable "permissions" { - description = "A map of objects with EventBridge Permission definitions." - type = map(object({ - action = optional(string) - event_bus_name = optional(string) - condition_org = optional(string) - })) - default = {} + description = <<-EOF + A map of objects with EventBridge Permission definitions. + + The type should really be + + map(object({ + action = optional(string) + event_bus_name = optional(string) + condition_org = optional(string) + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = map(any) + default = {} } variable "connections" { - description = "A map of objects with EventBridge Connection definitions." - type = map(object({ - authorization_type = string - auth_parameters = any - kms_key_identifier = string - description = optional(string) - invocation_connectivity_parameters = optional(any) - })) - default = {} + description = <<-EOF + A map of objects with EventBridge Connection definitions. + + The type should really be + + map(object({ + authorization_type = string + auth_parameters = any + kms_key_identifier = string + description = optional(string) + invocation_connectivity_parameters = optional(any) + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = any + default = {} validation { condition = alltrue([ @@ -292,51 +346,80 @@ variable "connections" { } variable "api_destinations" { - description = "A map of objects with EventBridge Destination definitions." - type = map(object({ - description = optional(string) - invocation_endpoint = string - http_method = string - invocation_rate_limit_per_second = optional(number) - connection_name = optional(string) - })) - default = {} + description = <<-EOF + A map of objects with EventBridge Destination definitions. + + The type should really be + + map(object({ + description = optional(string) + invocation_endpoint = string + http_method = string + invocation_rate_limit_per_second = optional(number) + connection_name = optional(string) + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = map(any) + default = {} } variable "schedule_groups" { - description = "A map of objects with EventBridge Schedule Group definitions. Names are derived from the object keys and cannot be overridden." - type = map(object({ - tags = optional(map(string), {}) - })) - default = {} + description = <<-EOF + A map of objects with EventBridge Schedule Group definitions. + + Names are derived from the object keys and cannot be overridden. + + The type should really be + + map(object({ + tags = optional(map(string), {}) + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = any + default = {} } variable "schedules" { - description = "A map of objects with EventBridge Schedule definitions." - type = map(object({ - arn = string - schedule_expression = string - name_prefix = optional(string) - description = optional(string) - group_name = optional(string) - start_date = optional(string) - end_date = optional(string) - kms_key_arn = string - timezone = optional(string) - state = optional(bool, true) - maximum_window_in_minutes = optional(number) - use_flexible_time_window = optional(bool, false) - role_arn = optional(string) - input = optional(string) - dead_letter_arn = optional(string) - ecs_parameters = optional(any) - eventbridge_parameters = optional(any) - partition_key = optional(string) - sagemaker_pipeline_parameters = optional(any) - message_group_id = optional(string) - retry_policy = optional(any) - })) - default = {} + description = <<-EOF + A map of objects with EventBridge Schedule definitions. + + The type should really be + + map(object({ + arn = string + schedule_expression = string + name_prefix = optional(string) + description = optional(string) + group_name = optional(string) + start_date = optional(string) + end_date = optional(string) + kms_key_arn = string + timezone = optional(string) + state = optional(bool, true) + maximum_window_in_minutes = optional(number) + use_flexible_time_window = optional(bool, false) + role_arn = optional(string) + input = optional(string) + dead_letter_arn = optional(string) + ecs_parameters = optional(any) + eventbridge_parameters = optional(any) + partition_key = optional(string) + sagemaker_pipeline_parameters = optional(any) + message_group_id = optional(string) + retry_policy = optional(any) + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = map(any) + default = {} validation { condition = alltrue([ @@ -350,22 +433,31 @@ variable "schedules" { } variable "pipes" { - description = "A map of EventBridge Pipe definitions." - type = map(object({ - role_arn = optional(string) - source = string - target = string - kms_key_identifier = string - description = optional(string) - desired_state = optional(string) - source_parameters = optional(any) - target_parameters = optional(any) - enrichment = optional(string) - enrichment_parameters = optional(any) - log_configuration = optional(any) - tags = optional(map(string), {}) - })) - default = {} + description = <<-EOF + A map of EventBridge Pipe definitions. + + The type should really be + + map(object({ + role_arn = optional(string) + source = string + target = string + kms_key_identifier = string + description = optional(string) + desired_state = optional(string) + source_parameters = optional(any) + target_parameters = optional(any) + enrichment = optional(string) + enrichment_parameters = optional(any) + log_configuration = optional(any) + tags = optional(map(string), {}) + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = any + default = {} validation { condition = alltrue([ @@ -619,17 +711,26 @@ variable "policies" { } variable "policy_statements" { - description = "Map of dynamic policy statements to attach to IAM role" - type = map(object({ - sid = optional(string) - effect = optional(string) - actions = optional(list(string)) - not_actions = optional(list(string)) - resources = optional(list(string)) - not_resources = optional(list(string)) - principals = optional(any) - not_principals = optional(any) - condition = optional(any) - })) - default = {} + description = <<-EOF + Map of dynamic policy statements to attach to IAM role + + The type should really be + + map(object({ + sid = optional(string) + effect = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(any) + not_principals = optional(any) + condition = optional(any) + })) + + but it causes problems in the community module when Terraform sets + omitted fields to null. + EOF + type = any + default = {} } From 5d3395ae0d0bb2887b70125df0e385eef391944f Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Fri, 25 Sep 2026 11:29:49 +0100 Subject: [PATCH 39/64] fix: add missing default to `var.archives` --- infrastructure/modules/eventbridge/README.md | 2 +- infrastructure/modules/eventbridge/variables.tf | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index ce32cfa8..834cb99c 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -70,7 +70,7 @@ No resources. | [append\_schedule\_group\_postfix](#input\_append\_schedule\_group\_postfix) | Controls whether to append '-group' to the name of the schedule group | `bool` | `true` | no | | [append\_schedule\_postfix](#input\_append\_schedule\_postfix) | Controls whether to append '-schedule' to the name of the schedule | `bool` | `true` | no | | [application\_role](#input\_application\_role) | The role the application is performing | `string` | `"General"` | no | -| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions.

The type should really be

map(object({
name = optional(string)
event\_source\_arn = optional(string)
description = optional(string)
event\_pattern = optional(string)
retention\_days = optional(number)
kms\_key\_identifier = string
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | n/a | yes | +| [archives](#input\_archives) | A map of objects with the EventBridge Archive definitions.

The type should really be

map(object({
name = optional(string)
event\_source\_arn = optional(string)
description = optional(string)
event\_pattern = optional(string)
retention\_days = optional(number)
kms\_key\_identifier = string
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [attach\_api\_destination\_policy](#input\_attach\_api\_destination\_policy) | Controls whether the API Destination policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | | [attach\_cloudwatch\_policy](#input\_attach\_cloudwatch\_policy) | Controls whether the Cloudwatch policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | | [attach\_ecs\_policy](#input\_attach\_ecs\_policy) | Controls whether the ECS policy should be added to IAM role for EventBridge Target | `bool` | `false` | no | diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index cf63a5af..e6453f9d 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -283,6 +283,7 @@ variable "archives" { omitted fields to null. EOF type = map(any) + default = {} validation { condition = alltrue([ From dae1d25fb3eede2bb4a3094665b0e50f4bd52e9b Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Mon, 28 Sep 2026 08:28:35 +0100 Subject: [PATCH 40/64] docs: document kms keys and name mangling --- infrastructure/modules/eventbridge/README.md | 38 +++++++++----------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 834cb99c..8b79a4af 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -3,34 +3,30 @@ NHS Screening wrapper around the community [`terraform-aws-modules/terraform-aws-eventbridge`](https://registry.terraform.io/modules/terraform-aws-modules/eventbridge/aws/4.3.2) module that consumes the shared `context.tf` for naming and tagging. +Note that this provides access to AWS EventBridge Scheduler in addition +to AWS EventBridge. -DAVEH +Main differences from the wrapped module: -DAVEH: document var.bus_name +1. KMS keys must be provided. ----- + Many of the underlying resources take an optional KMS key, falling back to + using an AWS-owned key. In this module, we make it compulsory to provide + these keys. -DAVEH: document var.log_delivery +2. Names are modified in an attempt to avoid clashes when deploying into +multiple namespaces. -DAVEH: document var.connections + The underlying module names many resources based on the keys of maps passed + in as variables. This causes conflicts when a stack is deployed into + multiple workspaces in the same AWS account. -DAVEH: document var.api_destinations + This module prefixes many of these keys with the bus name. The bus name by + default includes the workspace id. -DAVEH: document var.schedule_groups - -DAVEH: document var.pipes - ----- - -DAVEH: document var.kms_key_identifier - -DAVEH: document var.connections - -DAVEH: document var.schedules - -DAVEH: document var.pipes - -DAVEH: document var.archives + It is likely that we are not yet prefixing some keys that are vulnerable to + this sort of name conflict. You should be prepared to update this module if + you encounter such a case in your deployment. From 419f474336afe6a1b801dc4dcc2f881e233d64e3 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Mon, 28 Sep 2026 08:30:21 +0100 Subject: [PATCH 41/64] chore: give up on further name mangling --- infrastructure/modules/eventbridge/locals.tf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 8a11900c..ccfc786f 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -65,10 +65,4 @@ locals { for attribute, value in v : attribute => value if value != null } } - # DAVEH: Pipe → API destination enrichment - # The wrapper prefixes API-destination keys, but leaves pipes[*].enrichment unchanged. - - # DAVEH: EventBridge target → API destination - # The wrapper prefixes api_destinations keys in locals.tf:23-26, but - # leaves targets[*].destination unchanged. } From 0f19a5f30427893fbd4aa92062e10f79bd2f77ee Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Mon, 28 Sep 2026 08:31:01 +0100 Subject: [PATCH 42/64] chore: delete temp notes --- infrastructure/modules/eventbridge/NOTES.md | 241 -------------------- 1 file changed, 241 deletions(-) delete mode 100644 infrastructure/modules/eventbridge/NOTES.md diff --git a/infrastructure/modules/eventbridge/NOTES.md b/infrastructure/modules/eventbridge/NOTES.md deleted file mode 100644 index cbca218a..00000000 --- a/infrastructure/modules/eventbridge/NOTES.md +++ /dev/null @@ -1,241 +0,0 @@ -# Implementation Notes - -DAVEH: rm or tidy - -These notes are about v4.3.2 of the underlying `terraform-aws-modules/terraform-aws-eventbridge` community module. - -The main on/off switch is called `create`. - -## Underlying resources - -### `aws_cloudwatch_event_bus.this` - -- data/resource -- gated by `var.create_bus`; can otherwise add to an existing bus -- name status: named and must be unique per AWS account and region -- named by `var.bus_name` - - default account bus (created by AWS) is named `default` -- events on the bus are encrypted by `var.kms_key_identifier` - - can be the key ARN, KeyId, key alias, or key alias ARN - -### `aws_cloudwatch_event_api_destination.this` - -- resource -- gated by `var.create_api_destinations` -- name status: named and must be unique per AWS account and region -- named by `var.api_destinations` keys - - modified if `var.append_destination_postfix` is true - -### `aws_cloudwatch_event_archive.this` - -- resource -- gated by `var.create_archives` -- name status: named and must be unique per event bus -- named by `name` field of `var.archives` sub-value, falling back to `var.archives` key -- encrypted using `kms_key_identifier` field of `var.archives` sub-value, falling back to unencrypted - -### `aws_cloudwatch_event_connection.this` - -- resource -- gated by `var.create_connections` -- name status: named and must be unique per AWS account and region -- named by the key of `var.connections` - - modified if `var.append_connection_postfix` is true -- encrypted using `kms_key_identifier` field of `var.connections` sub-value, falling back to unencrypted - -### `aws_cloudwatch_event_permission.this` - -- resource -- gated by `var.create_permissions` -- name status: unnamed; its statement ID must be unique on the event bus - -### `aws_cloudwatch_event_rule.this` - -- resource -- gated by `var.create_rules` -- name status: named and must be unique per event bus -- named by `var.rules` key - - modified if `var.append_rule_postfix` is true - -### `aws_cloudwatch_event_target.this` - -- name status: unnamed; its target ID must be unique per rule on an event bus - -### `aws_cloudwatch_log_delivery.this` - -- name status: unnamed; one delivery is allowed per source-destination pair - -### `aws_cloudwatch_log_delivery_destination.this` - -- name status: named and must be unique per AWS account -- name is obtained from the `name` field of each `var.log_delivery` sub-value, falling back to `var.bus_name` and the `var.log_delivery` map key - -### `aws_cloudwatch_log_delivery_source.this` - -- name status: named and must be unique per AWS account -- name is obtained from `var.log_delivery_source_name`, falling back to `var.bus_name` - -### `aws_iam_policy.additional_inline` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-inline` appended - -### `aws_iam_policy.additional_json` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name` - -### `aws_iam_policy.additional_jsons` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-` appended - -### `aws_iam_policy.api_destination` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-api-destination` appended - -### `aws_iam_policy.cloudwatch` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-cloudwatch` appended - -### `aws_iam_policy.ecs` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-ecs` appended - -### `aws_iam_policy.kinesis` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-kinesis` appended - -### `aws_iam_policy.kinesis_firehose` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-kinesis-firehose` appended - -### `aws_iam_policy.lambda` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-lambda` appended - -### `aws_iam_policy.service` - -- name status: named and must be unique per AWS account -- name is derived from the generated Pipe role name and the key of `var.pipes`; the role name uses `role_name_prefix` from each `var.pipes` sub-value, falling back to its map key - -### `aws_iam_policy.sfn` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-sfn` appended - -### `aws_iam_policy.sns` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-sns` appended - -### `aws_iam_policy.sqs` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-sqs` appended - -### `aws_iam_policy.tracing` - -- name status: named and must be unique per AWS account -- name is derived from `var.role_name`, falling back to `var.bus_name`, with `-tracing` appended - -### `aws_iam_policy_attachment.additional_inline` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.additional_json` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.additional_jsons` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.api_destination` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.cloudwatch` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.ecs` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.kinesis` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.kinesis_firehose` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.lambda` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.service` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.sfn` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.sns` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.sqs` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_policy_attachment.tracing` - -- name status: unnamed; this is an attachment relationship identified by the policy and target ARNs - -### `aws_iam_role.eventbridge` - -- name status: named and must be unique per AWS account -- name is obtained from `var.role_name`, falling back to `var.bus_name` - -### `aws_iam_role.eventbridge_pipe` - -- name status: named and must be unique per AWS account -- name uses `role_name_prefix` from each `var.pipes` sub-value, falling back to its map key - -### `aws_iam_role_policy_attachment.additional_many` - -- name status: unnamed; this is an attachment relationship identified by the role and policy ARNs - -### `aws_iam_role_policy_attachment.additional_one` - -- name status: unnamed; this is an attachment relationship identified by the role and policy ARNs - -### `aws_pipes_pipe.this` - -- name status: named and must be unique per AWS account and region -- name is obtained from the key of `var.pipes`, with the optional postfix controlled by `var.append_pipe_postfix` -- encrypted using `kms_key_identifier` field of `var.pipes` sub-value, falling back to unencrypted - -### `aws_scheduler_schedule.this` - -- name status: named and must be unique per schedule group -- encrypted using `kms_key_arn` field of `var.schedules` sub-value, falling back to unencrypted - -### `aws_scheduler_schedule_group.this` - -- name status: named and must be unique per AWS account and region -- name is obtained from the `name` or `name_prefix` field of each `var.schedule_groups` sub-value, falling back to its map key - -### `aws_schemas_discoverer.this` - -- name status: named and must be unique per AWS account and region -- name is generated by AWS; it is not obtained from a module input variable From 43872b62f7c0693f8442b8434a931d02a4b76c75 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Tue, 29 Sep 2026 13:07:51 +0100 Subject: [PATCH 43/64] chore: add entry to generate-available-modules.yaml --- README.md | 2 +- scripts/config/generate-available-modules.yaml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3435d779..d09e1817 100644 --- a/README.md +++ b/README.md @@ -340,7 +340,7 @@ Rules: | `ecs-service` | terraform-aws-modules/ecs/aws//modules/service | ECS service and task definition | | `efs` | terraform-aws-modules/efs/aws | EFS file system with access points and mount targets | | `elasticache` | — | ElastiCache cluster (Redis/Memcached) | -| `eventbridge` | — | — | +| `eventbridge` | terraform-aws-modules/eventbridge/aws | EventBridge buses, rules, targets, pipes, connections, and archives with KMS encryption; EventBridge Scheduler | | `github-config` | — | GitHub OIDC provider and runner configuration | | `guardduty` | — | GuardDuty threat detection | | `iam` | terraform-aws-modules/iam/aws | IAM policies and roles | diff --git a/scripts/config/generate-available-modules.yaml b/scripts/config/generate-available-modules.yaml index 26255876..81053709 100644 --- a/scripts/config/generate-available-modules.yaml +++ b/scripts/config/generate-available-modules.yaml @@ -81,6 +81,10 @@ elasticache: description: "ElastiCache cluster (Redis/Memcached)" wraps: "—" +eventbridge: + description: "EventBridge buses, rules, targets, pipes, connections, and archives with KMS encryption; EventBridge Scheduler" + wraps: "terraform-aws-modules/eventbridge/aws" + github-config: description: "GitHub OIDC provider and runner configuration" wraps: "—" From 6cb44f0407bba8658cd11c5c66adbd50776e185e Mon Sep 17 00:00:00 2001 From: Oliver Slater Date: Thu, 1 Oct 2026 00:42:58 +0100 Subject: [PATCH 44/64] docs(eventbridge): align module catalogue entry --- README.md | 2 +- scripts/config/generate-available-modules.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d09e1817..79b92134 100644 --- a/README.md +++ b/README.md @@ -340,7 +340,7 @@ Rules: | `ecs-service` | terraform-aws-modules/ecs/aws//modules/service | ECS service and task definition | | `efs` | terraform-aws-modules/efs/aws | EFS file system with access points and mount targets | | `elasticache` | — | ElastiCache cluster (Redis/Memcached) | -| `eventbridge` | terraform-aws-modules/eventbridge/aws | EventBridge buses, rules, targets, pipes, connections, and archives with KMS encryption; EventBridge Scheduler | +| `eventbridge` | terraform-aws-modules/eventbridge/aws | AWS EventBridge Resources | | `github-config` | — | GitHub OIDC provider and runner configuration | | `guardduty` | — | GuardDuty threat detection | | `iam` | terraform-aws-modules/iam/aws | IAM policies and roles | diff --git a/scripts/config/generate-available-modules.yaml b/scripts/config/generate-available-modules.yaml index 81053709..c8142179 100644 --- a/scripts/config/generate-available-modules.yaml +++ b/scripts/config/generate-available-modules.yaml @@ -82,7 +82,7 @@ elasticache: wraps: "—" eventbridge: - description: "EventBridge buses, rules, targets, pipes, connections, and archives with KMS encryption; EventBridge Scheduler" + description: "AWS EventBridge Resources" wraps: "terraform-aws-modules/eventbridge/aws" github-config: From 5bb8654dfba41a3d8075723202587fd5888d154f Mon Sep 17 00:00:00 2001 From: Oliver Slater Date: Thu, 1 Oct 2026 00:45:43 +0100 Subject: [PATCH 45/64] fix(eventbridge): preserve generated names and context role tags --- infrastructure/modules/eventbridge/locals.tf | 2 +- infrastructure/modules/eventbridge/main.tf | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index ccfc786f..cf8ae80d 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -9,7 +9,7 @@ locals { { name = "${module.this.id}-${k}" }, - v + { for attribute, value in v : attribute => value if value != null } ) } diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index f8f34fe3..d6fc15f3 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -1,3 +1,13 @@ +################################################################ +# EventBridge +# +# Thin NHS wrapper around the community EventBridge module: +# * Customer-managed KMS keys are required for encrypted resources. +# * SNS KMS permissions are restricted to explicitly approved keys. +# * Resource names and tags are derived from context.tf. +# * Creation is gated by module.this.enabled. +################################################################ + module "eventbridge" { source = "git::https://github.com/terraform-aws-modules/terraform-aws-eventbridge.git?ref=f9934726324c988f823682884b4fa003586a7b6f" # v4.3.2 @@ -51,7 +61,7 @@ module "eventbridge" { policy_path = var.policy_path role_force_detach_policies = var.role_force_detach_policies role_permissions_boundary = var.role_permissions_boundary - role_tags = var.role_tags + role_tags = merge(var.role_tags, module.this.tags) ecs_pass_role_resources = var.ecs_pass_role_resources attach_kinesis_policy = var.attach_kinesis_policy attach_kinesis_firehose_policy = var.attach_kinesis_firehose_policy From d489349083b647ba3f24b972110615900b3d5d74 Mon Sep 17 00:00:00 2001 From: Oliver Slater Date: Thu, 1 Oct 2026 00:46:57 +0100 Subject: [PATCH 46/64] docs(eventbridge): document security controls and usage --- infrastructure/modules/eventbridge/README.md | 109 ++++++++++++++++++- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 8b79a4af..360392da 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -24,9 +24,102 @@ multiple namespaces. This module prefixes many of these keys with the bus name. The bus name by default includes the workspace id. - It is likely that we are not yet prefixing some keys that are vulnerable to - this sort of name conflict. You should be prepared to update this module if - you encounter such a case in your deployment. + Not every upstream resource name is prefixed; check planned names for + collisions when deploying several workspaces into one account and region. + +## What this module enforces + +| Control | How it is enforced | +| --- | --- | +| Encryption at rest | A customer-managed KMS key is required for the bus, and for each configured archive, connection, schedule, and pipe. | +| SNS KMS access | SNS target policies require specific KMS key ARNs; wildcard keys are rejected. | +| Naming | The bus defaults to the context ID; connection, destination, pipe, schedule-group, and log-delivery names are scoped to the context. | +| Tagging | Bus resources and IAM roles receive context tags. | +| Creation gate | `module.this.enabled` controls creation of the upstream module. | + +## Usage + +### Minimal encrypted bus + +```hcl +module "events" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" + + service = "bcss" + environment = "test" + name = "events" + kms_key_identifier = module.eventbridge_kms.key_arn +} +``` + +### Production SNS target + +```hcl +module "events" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" + + service = "bcss" + environment = "production" + name = "notifications" + kms_key_identifier = module.eventbridge_kms.key_arn + + rules = { + screening_completed = { + description = "Route completed screening events" + event_pattern = jsonencode({ source = ["bcss.screening"] }) + } + } + targets = { + screening_completed = [{ name = "notifications", arn = aws_sns_topic.notifications.arn }] + } + + attach_sns_policy = true + sns_target_arns = [aws_sns_topic.notifications.arn] + sns_kms_arns = [module.sns_kms.key_arn] +} +``` + +### Advanced scheduler group + +```hcl +module "scheduled_events" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" + + service = "bcss" + environment = "production" + name = "jobs" + kms_key_identifier = module.eventbridge_kms.key_arn + + schedule_groups = { nightly = {} } + schedules = { + nightly_job = { + arn = aws_lambda_function.job.arn + schedule_expression = "cron(0 2 * * ? *)" + group_name = "nightly" + kms_key_arn = module.scheduler_kms.key_arn + } + } +} +``` + +## Conventions + +- Pass a pinned release ref and supply the required customer-managed KMS keys. +- Keys of `schedule_groups` are logical identifiers; schedule `group_name` references a key, not the prefixed AWS name. +- `log_delivery` entries default to context-prefixed names when `name` is omitted or null. +- Additional `role_tags` are combined with context tags; context values take precedence. +- Review the plan for resource names, policies, and target permissions before applying. + +## Validation + +When `attach_sns_policy` is true, provide at least one specific KMS key ARN in `sns_kms_arns`. +Wildcards and aliases are not accepted for this policy. + +## What this module does NOT do + +- It does not create KMS keys, SNS topics, target resources, or their resource policies. +- It does not guarantee unique names for every upstream resource or configure all target permissions. +- It does not keep connection credentials out of Terraform state; use encrypted remote state and limit access to plans and state. @@ -40,7 +133,9 @@ multiple namespaces. ## Providers -No providers. +| Name | Version | +| ---- | ------- | +| [terraform](#provider\_terraform) | n/a | ## Modules @@ -51,7 +146,9 @@ No providers. ## Resources -No resources. +| Name | Type | +| ---- | ---- | +| [terraform_data.validations](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | ## Inputs @@ -157,7 +254,7 @@ No resources. | [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 | | [sfn\_target\_arns](#input\_sfn\_target\_arns) | The Amazon Resource Name (ARN) of the StepFunctions you want to use as EventBridge targets | `list(string)` | `[]` | no | -| [sns\_kms\_arns](#input\_sns\_kms\_arns) | The Amazon Resource Name (ARN) of the AWS KMS's configured for AWS SNS you want Decrypt/GenerateDataKey for | `list(string)` |
[
"*"
]
| no | +| [sns\_kms\_arns](#input\_sns\_kms\_arns) | Specific customer-managed KMS key ARNs used by SNS targets; required when attach\_sns\_policy is enabled | `list(string)` | `[]` | no | | [sns\_target\_arns](#input\_sns\_target\_arns) | The Amazon Resource Name (ARN) of the AWS SNS's you want to use as EventBridge targets | `list(string)` | `[]` | no | | [sqs\_target\_arns](#input\_sqs\_target\_arns) | The Amazon Resource Name (ARN) of the AWS SQS Queues you want to use as EventBridge targets | `list(string)` | `[]` | no | | [stack](#input\_stack) | ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks` | `string` | `null` | no | From 68c5bace6b44af04493989a0aa2c850e576b2c8d Mon Sep 17 00:00:00 2001 From: Oliver Slater Date: Thu, 1 Oct 2026 00:49:58 +0100 Subject: [PATCH 47/64] fix(eventbridge): require specific SNS KMS key ARNs --- infrastructure/modules/eventbridge/validations.tf | 10 ++++++++++ infrastructure/modules/eventbridge/variables.tf | 12 ++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 infrastructure/modules/eventbridge/validations.tf diff --git a/infrastructure/modules/eventbridge/validations.tf b/infrastructure/modules/eventbridge/validations.tf new file mode 100644 index 00000000..3ecc4ad5 --- /dev/null +++ b/infrastructure/modules/eventbridge/validations.tf @@ -0,0 +1,10 @@ +resource "terraform_data" "validations" { + count = module.this.enabled ? 1 : 0 + + lifecycle { + precondition { + condition = !var.attach_sns_policy || length(var.sns_kms_arns) > 0 + error_message = "attach_sns_policy requires at least one specific sns_kms_arns entry." + } + } +} diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index e6453f9d..5641f757 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -610,9 +610,17 @@ variable "sns_target_arns" { } variable "sns_kms_arns" { - description = "The Amazon Resource Name (ARN) of the AWS KMS's configured for AWS SNS you want Decrypt/GenerateDataKey for" + description = "Specific customer-managed KMS key ARNs used by SNS targets; required when attach_sns_policy is enabled" type = list(string) - default = ["*"] + default = [] + + validation { + condition = alltrue([ + for arn in var.sns_kms_arns : + can(regex("^arn:[^:]+:kms:[^:]+:[0-9]{12}:key/[A-Za-z0-9-]+$", arn)) + ]) + error_message = "sns_kms_arns must contain only specific KMS key ARNs (not aliases or wildcards)." + } } variable "ecs_target_arns" { From 0942658501f4f6a86de29a439c10d9e5008e6b4e Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 11:38:40 +0100 Subject: [PATCH 48/64] fix: disallow connections --- infrastructure/modules/eventbridge/README.md | 99 +++++++++---------- infrastructure/modules/eventbridge/locals.tf | 17 +--- infrastructure/modules/eventbridge/main.tf | 6 +- .../modules/eventbridge/variables.tf | 45 +-------- 4 files changed, 52 insertions(+), 115 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 360392da..e4dd87dc 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -27,80 +27,78 @@ multiple namespaces. Not every upstream resource name is prefixed; check planned names for collisions when deploying several workspaces into one account and region. +3. Connections are disallowed, as the underlying module does not redact +credentials contained therein from the terraform state. + ## What this module enforces | Control | How it is enforced | | --- | --- | -| Encryption at rest | A customer-managed KMS key is required for the bus, and for each configured archive, connection, schedule, and pipe. | +| Encryption at rest | A customer-managed KMS key is required for the bus, and for each configured archive, schedule, and pipe. | | SNS KMS access | SNS target policies require specific KMS key ARNs; wildcard keys are rejected. | | Naming | The bus defaults to the context ID; connection, destination, pipe, schedule-group, and log-delivery names are scoped to the context. | | Tagging | Bus resources and IAM roles receive context tags. | | Creation gate | `module.this.enabled` controls creation of the upstream module. | +| Secrets protection | Connections are disallowed, as the wrapped module doesn't handle them safely | ## Usage ### Minimal encrypted bus -```hcl -module "events" { - source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" + module "events" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" - service = "bcss" - environment = "test" - name = "events" - kms_key_identifier = module.eventbridge_kms.key_arn -} -``` + service = "bcss" + environment = "test" + name = "events" + kms_key_identifier = module.eventbridge_kms.key_arn + } ### Production SNS target -```hcl -module "events" { - source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" + module "events" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" - service = "bcss" - environment = "production" - name = "notifications" - kms_key_identifier = module.eventbridge_kms.key_arn + service = "bcss" + environment = "production" + name = "notifications" + kms_key_identifier = module.eventbridge_kms.key_arn - rules = { - screening_completed = { - description = "Route completed screening events" - event_pattern = jsonencode({ source = ["bcss.screening"] }) + rules = { + screening_completed = { + description = "Route completed screening events" + event_pattern = jsonencode({ source = ["bcss.screening"] }) + } + } + targets = { + screening_completed = [{ name = "notifications", arn = aws_sns_topic.notifications.arn }] } - } - targets = { - screening_completed = [{ name = "notifications", arn = aws_sns_topic.notifications.arn }] - } - attach_sns_policy = true - sns_target_arns = [aws_sns_topic.notifications.arn] - sns_kms_arns = [module.sns_kms.key_arn] -} -``` + attach_sns_policy = true + sns_target_arns = [aws_sns_topic.notifications.arn] + sns_kms_arns = [module.sns_kms.key_arn] + } ### Advanced scheduler group -```hcl -module "scheduled_events" { - source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" - - service = "bcss" - environment = "production" - name = "jobs" - kms_key_identifier = module.eventbridge_kms.key_arn - - schedule_groups = { nightly = {} } - schedules = { - nightly_job = { - arn = aws_lambda_function.job.arn - schedule_expression = "cron(0 2 * * ? *)" - group_name = "nightly" - kms_key_arn = module.scheduler_kms.key_arn + module "scheduled_events" { + source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/eventbridge?ref=" + + service = "bcss" + environment = "production" + name = "jobs" + kms_key_identifier = module.eventbridge_kms.key_arn + + schedule_groups = { nightly = {} } + schedules = { + nightly_job = { + arn = aws_lambda_function.job.arn + schedule_expression = "cron(0 2 * * ? *)" + group_name = "nightly" + kms_key_arn = module.scheduler_kms.key_arn + } } } -} -``` ## Conventions @@ -155,8 +153,7 @@ Wildcards and aliases are not accepted for this policy. | 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 | -| [api\_destinations](#input\_api\_destinations) | A map of objects with EventBridge Destination definitions.

The type should really be

map(object({
description = optional(string)
invocation\_endpoint = string
http\_method = string
invocation\_rate\_limit\_per\_second = optional(number)
connection\_name = optional(string)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | -| [append\_connection\_postfix](#input\_append\_connection\_postfix) | Controls whether to append '-connection' to the name of the connection | `bool` | `true` | no | +| [api\_destinations](#input\_api\_destinations) | A map of objects with EventBridge Destination definitions.

The type should really be

map(object({
description = optional(string)
invocation\_endpoint = string
http\_method = string
invocation\_rate\_limit\_per\_second = optional(number)
connection\_name = optional(string) # but connections have been removed from this wrapper anyway
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [append\_destination\_postfix](#input\_append\_destination\_postfix) | Controls whether to append '-destination' to the name of the destination | `bool` | `true` | no | | [append\_pipe\_postfix](#input\_append\_pipe\_postfix) | Controls whether to append '-pipe' to the name of the pipe | `bool` | `true` | no | | [append\_rule\_postfix](#input\_append\_rule\_postfix) | Controls whether to append '-rule' to the name of the rule | `bool` | `true` | no | @@ -184,12 +181,10 @@ Wildcards and aliases are not accepted for this policy. | [bus\_description](#input\_bus\_description) | Event bus description | `string` | `null` | no | | [bus\_name](#input\_bus\_name) | A unique name for your EventBridge Bus. Must be unique per AWS account and region. Defaults to whatever the tags module produces | `string` | `null` | no | | [cloudwatch\_target\_arns](#input\_cloudwatch\_target\_arns) | The Amazon Resource Name (ARN) of the Cloudwatch Log Streams you want to use as EventBridge targets | `list(string)` | `[]` | no | -| [connections](#input\_connections) | A map of objects with EventBridge Connection definitions.

The type should really be

map(object({
authorization\_type = string
auth\_parameters = any
kms\_key\_identifier = string
description = optional(string)
invocation\_connectivity\_parameters = optional(any)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | 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 | | [create\_api\_destinations](#input\_create\_api\_destinations) | Controls whether EventBridge Destination resources should be created | `bool` | `false` | no | | [create\_archives](#input\_create\_archives) | Controls whether EventBridge Archive resources should be created | `bool` | `false` | no | | [create\_bus](#input\_create\_bus) | Controls whether EventBridge Bus resource should be created | `bool` | `true` | no | -| [create\_connections](#input\_create\_connections) | Controls whether EventBridge Connection resources should be created | `bool` | `false` | no | | [create\_log\_delivery](#input\_create\_log\_delivery) | Controls whether EventBridge log delivery resources should be created | `bool` | `true` | no | | [create\_log\_delivery\_source](#input\_create\_log\_delivery\_source) | Controls whether EventBridge log delivery source resource should be created | `bool` | `true` | no | | [create\_permissions](#input\_create\_permissions) | Controls whether EventBridge Permission resources should be created | `bool` | `true` | no | diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index cf8ae80d..08b6b86f 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -13,25 +13,10 @@ locals { ) } - # connection names must be unique per AWS account and region - # prefix provided names with the module ID to ensure uniqueness - connections = { - for k, v in var.connections : "${module.this.id}-${k}" => v - } - # API destination names must be unique per AWS account and region. # We prefix provided names with the module ID to ensure uniqueness. - # Furthermore, `connection_name`s must match up with the keys of - # `connections`. api_destinations = { - for k, v in var.api_destinations : "${module.this.id}-${k}" => merge( - v, - can(v, "connection_name") - && v.connection_name != null - && contains(keys(var.connections), v.connection_name) - ? { connection_name = "${module.this.id}-${v.connection_name}" } - : {} - ) + for k, v in var.api_destinations : "${module.this.id}-${k}" => v } # schedule group names must be unique per AWS account and region diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index d6fc15f3..007e9515 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -6,6 +6,9 @@ # * SNS KMS permissions are restricted to explicitly approved keys. # * Resource names and tags are derived from context.tf. # * Creation is gated by module.this.enabled. +# * Connections have been removed from this wrapper, because the +# underlying community module stores them in full in the terraform +# state, including credentials. ################################################################ module "eventbridge" { @@ -17,7 +20,6 @@ module "eventbridge" { create_role = var.create_role create_pipe_role_only = var.create_pipe_role_only append_rule_postfix = var.append_rule_postfix - append_connection_postfix = var.append_connection_postfix append_destination_postfix = var.append_destination_postfix append_schedule_group_postfix = var.append_schedule_group_postfix append_schedule_postfix = var.append_schedule_postfix @@ -27,7 +29,6 @@ module "eventbridge" { create_targets = var.create_targets create_permissions = var.create_permissions create_archives = var.create_archives - create_connections = var.create_connections create_api_destinations = var.create_api_destinations create_schemas_discoverer = var.create_schemas_discoverer create_schedule_groups = var.create_schedule_groups @@ -49,7 +50,6 @@ module "eventbridge" { targets = var.targets archives = var.archives permissions = var.permissions - connections = local.connections api_destinations = local.api_destinations schedule_groups = local.schedule_groups schedules = local.schedules diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 5641f757..b7211a6c 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -16,12 +16,6 @@ variable "append_rule_postfix" { default = true } -variable "append_connection_postfix" { - description = "Controls whether to append '-connection' to the name of the connection" - type = bool - default = true -} - variable "append_destination_postfix" { description = "Controls whether to append '-destination' to the name of the destination" type = bool @@ -76,12 +70,6 @@ variable "create_archives" { default = false } -variable "create_connections" { - description = "Controls whether EventBridge Connection resources should be created" - type = bool - default = false -} - variable "create_api_destinations" { description = "Controls whether EventBridge Destination resources should be created" type = bool @@ -315,37 +303,6 @@ variable "permissions" { default = {} } -variable "connections" { - description = <<-EOF - A map of objects with EventBridge Connection definitions. - - The type should really be - - map(object({ - authorization_type = string - auth_parameters = any - kms_key_identifier = string - description = optional(string) - invocation_connectivity_parameters = optional(any) - })) - - but it causes problems in the community module when Terraform sets - omitted fields to null. - EOF - type = any - default = {} - - validation { - condition = alltrue([ - for connection in var.connections : - connection.kms_key_identifier != null && - trimspace(connection.kms_key_identifier) != "" - ]) - - error_message = "Each connection must specify a non-empty kms_key_identifier." - } -} - variable "api_destinations" { description = <<-EOF A map of objects with EventBridge Destination definitions. @@ -357,7 +314,7 @@ variable "api_destinations" { invocation_endpoint = string http_method = string invocation_rate_limit_per_second = optional(number) - connection_name = optional(string) + connection_name = optional(string) # but connections have been removed from this wrapper anyway })) but it causes problems in the community module when Terraform sets From ff85ec7181dec751a5c7de06e0ae1463920082ba Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 11:44:30 +0100 Subject: [PATCH 49/64] fix: validate var.kms_key_identifier --- infrastructure/modules/eventbridge/variables.tf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index b7211a6c..688a2128 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -168,6 +168,11 @@ variable "kms_key_identifier" { description = "The identifier of the AWS KMS customer managed key for EventBridge to use, to encrypt events on this event bus. The identifier can be the key Amazon Resource Name (ARN), KeyId, key alias, or key alias ARN." type = string nullable = false + + validation { + condition = trimspace(var.kms_key_identifier) != "" + error_message = "kms_key_identifier must not be empty" + } } variable "dead_letter_config" { From 5c721e4b8866efb45144b14a51f876d8b898641a Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 13:46:47 +0100 Subject: [PATCH 50/64] docs: explain why `role_arn` is a `bool` and not an arn --- infrastructure/modules/eventbridge/README.md | 2 +- infrastructure/modules/eventbridge/variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index e4dd87dc..845f5f37 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -241,7 +241,7 @@ Wildcards and aliases are not accepted for this policy. | [role\_path](#input\_role\_path) | Path of IAM role to use for EventBridge | `string` | `null` | no | | [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the IAM role used by EventBridge | `string` | `null` | no | | [role\_tags](#input\_role\_tags) | A map of tags to assign to IAM role | `map(string)` | `{}` | no | -| [rules](#input\_rules) | A map of objects with EventBridge Rule definitions.

The type should really be

map(object({
name\_prefix = optional(string)
description = optional(string)
event\_pattern = optional(string)
schedule\_expression = optional(string)
role\_arn = optional(bool)
enabled = optional(bool)
state = optional(string)
force\_destroy = optional(bool)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | +| [rules](#input\_rules) | A map of objects with EventBridge Rule definitions.

The type should really be

map(object({
name\_prefix = optional(string)
description = optional(string)
event\_pattern = optional(string)
schedule\_expression = optional(string)
role\_arn = optional(bool) # the underlying module uses the role created by the wrapped module if true, or null if false
enabled = optional(bool)
state = optional(string)
force\_destroy = optional(bool)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [schedule\_group\_timeouts](#input\_schedule\_group\_timeouts) | A map of objects with EventBridge Schedule Group create and delete timeouts. | `map(string)` | `{}` | no | | [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions.

Names are derived from the object keys and cannot be overridden.

The type should really be

map(object({
tags = optional(map(string), {})
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | no | | [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions.

The type should really be

map(object({
arn = string
schedule\_expression = string
name\_prefix = optional(string)
description = optional(string)
group\_name = optional(string)
start\_date = optional(string)
end\_date = optional(string)
kms\_key\_arn = string
timezone = optional(string)
state = optional(bool, true)
maximum\_window\_in\_minutes = optional(number)
use\_flexible\_time\_window = optional(bool, false)
role\_arn = optional(string)
input = optional(string)
dead\_letter\_arn = optional(string)
ecs\_parameters = optional(any)
eventbridge\_parameters = optional(any)
partition\_key = optional(string)
sagemaker\_pipeline\_parameters = optional(any)
message\_group\_id = optional(string)
retry\_policy = optional(any)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 688a2128..fb26bb4f 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -210,7 +210,7 @@ variable "rules" { description = optional(string) event_pattern = optional(string) schedule_expression = optional(string) - role_arn = optional(bool) + role_arn = optional(bool) # the underlying module uses the role created by the wrapped module if true, or null if false enabled = optional(bool) state = optional(string) force_destroy = optional(bool) From f8f05402c20e90b6a41c87945058a205abcc1b6c Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 13:53:46 +0100 Subject: [PATCH 51/64] docs: explain why `state` is a `bool` and not a string (enum really) --- infrastructure/modules/eventbridge/README.md | 2 +- infrastructure/modules/eventbridge/variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 845f5f37..46c32e9f 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -244,7 +244,7 @@ Wildcards and aliases are not accepted for this policy. | [rules](#input\_rules) | A map of objects with EventBridge Rule definitions.

The type should really be

map(object({
name\_prefix = optional(string)
description = optional(string)
event\_pattern = optional(string)
schedule\_expression = optional(string)
role\_arn = optional(bool) # the underlying module uses the role created by the wrapped module if true, or null if false
enabled = optional(bool)
state = optional(string)
force\_destroy = optional(bool)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [schedule\_group\_timeouts](#input\_schedule\_group\_timeouts) | A map of objects with EventBridge Schedule Group create and delete timeouts. | `map(string)` | `{}` | no | | [schedule\_groups](#input\_schedule\_groups) | A map of objects with EventBridge Schedule Group definitions.

Names are derived from the object keys and cannot be overridden.

The type should really be

map(object({
tags = optional(map(string), {})
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `any` | `{}` | no | -| [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions.

The type should really be

map(object({
arn = string
schedule\_expression = string
name\_prefix = optional(string)
description = optional(string)
group\_name = optional(string)
start\_date = optional(string)
end\_date = optional(string)
kms\_key\_arn = string
timezone = optional(string)
state = optional(bool, true)
maximum\_window\_in\_minutes = optional(number)
use\_flexible\_time\_window = optional(bool, false)
role\_arn = optional(string)
input = optional(string)
dead\_letter\_arn = optional(string)
ecs\_parameters = optional(any)
eventbridge\_parameters = optional(any)
partition\_key = optional(string)
sagemaker\_pipeline\_parameters = optional(any)
message\_group\_id = optional(string)
retry\_policy = optional(any)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | +| [schedules](#input\_schedules) | A map of objects with EventBridge Schedule definitions.

The type should really be

map(object({
arn = string
schedule\_expression = string
name\_prefix = optional(string)
description = optional(string)
group\_name = optional(string)
start\_date = optional(string)
end\_date = optional(string)
kms\_key\_arn = string
timezone = optional(string)
state = optional(bool, true) # the underlying module turns this into "ENABLED" if true or "DISABLED" if false
maximum\_window\_in\_minutes = optional(number)
use\_flexible\_time\_window = optional(bool, false)
role\_arn = optional(string)
input = optional(string)
dead\_letter\_arn = optional(string)
ecs\_parameters = optional(any)
eventbridge\_parameters = optional(any)
partition\_key = optional(string)
sagemaker\_pipeline\_parameters = optional(any)
message\_group\_id = optional(string)
retry\_policy = optional(any)
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [schemas\_discoverer\_description](#input\_schemas\_discoverer\_description) | Default schemas discoverer description | `string` | `"Auto schemas discoverer event"` | 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 | diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index fb26bb4f..7dbede6b 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -364,7 +364,7 @@ variable "schedules" { end_date = optional(string) kms_key_arn = string timezone = optional(string) - state = optional(bool, true) + state = optional(bool, true) # the underlying module turns this into "ENABLED" if true or "DISABLED" if false maximum_window_in_minutes = optional(number) use_flexible_time_window = optional(bool, false) role_arn = optional(string) From 77639b7dcc203b04d0762f73dd78ffd972203056 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 14:49:44 +0100 Subject: [PATCH 52/64] fix: disable schema discovery --- infrastructure/modules/eventbridge/README.md | 3 ++- infrastructure/modules/eventbridge/main.tf | 1 - infrastructure/modules/eventbridge/variables.tf | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 46c32e9f..4f8e040c 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -14,6 +14,8 @@ Main differences from the wrapped module: using an AWS-owned key. In this module, we make it compulsory to provide these keys. + As this is incompatible with schema discovery, we disable schema discovery. + 2. Names are modified in an attempt to avoid clashes when deploying into multiple namespaces. @@ -194,7 +196,6 @@ Wildcards and aliases are not accepted for this policy. | [create\_rules](#input\_create\_rules) | Controls whether EventBridge Rule resources should be created | `bool` | `true` | no | | [create\_schedule\_groups](#input\_create\_schedule\_groups) | Controls whether EventBridge Schedule Group resources should be created | `bool` | `true` | no | | [create\_schedules](#input\_create\_schedules) | Controls whether EventBridge Schedule resources should be created | `bool` | `true` | no | -| [create\_schemas\_discoverer](#input\_create\_schemas\_discoverer) | Controls whether default schemas discoverer should be created | `bool` | `false` | no | | [create\_targets](#input\_create\_targets) | Controls whether EventBridge Target resources should be created | `bool` | `true` | 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 | diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 007e9515..2b5c9e87 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -30,7 +30,6 @@ module "eventbridge" { create_permissions = var.create_permissions create_archives = var.create_archives create_api_destinations = var.create_api_destinations - create_schemas_discoverer = var.create_schemas_discoverer create_schedule_groups = var.create_schedule_groups create_schedules = var.create_schedules create_pipes = var.create_pipes diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 7dbede6b..c937ecde 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -76,12 +76,6 @@ variable "create_api_destinations" { default = false } -variable "create_schemas_discoverer" { - description = "Controls whether default schemas discoverer should be created" - type = bool - default = false -} - variable "create_schedule_groups" { description = "Controls whether EventBridge Schedule Group resources should be created" type = bool From a761cc16d675b9d931d971079240a4ffb8fdfca6 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 14:57:03 +0100 Subject: [PATCH 53/64] =?UTF-8?q?fix:=20rm=20api=5Fdestinations,=20as=20it?= =?UTF-8?q?=E2=80=99s=20unless=20without=20connections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infrastructure/modules/eventbridge/README.md | 2 -- infrastructure/modules/eventbridge/locals.tf | 6 ----- infrastructure/modules/eventbridge/main.tf | 2 -- .../modules/eventbridge/variables.tf | 27 ------------------- 4 files changed, 37 deletions(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index 4f8e040c..a8fdc7b8 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -155,7 +155,6 @@ Wildcards and aliases are not accepted for this policy. | 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 | -| [api\_destinations](#input\_api\_destinations) | A map of objects with EventBridge Destination definitions.

The type should really be

map(object({
description = optional(string)
invocation\_endpoint = string
http\_method = string
invocation\_rate\_limit\_per\_second = optional(number)
connection\_name = optional(string) # but connections have been removed from this wrapper anyway
}))

but it causes problems in the community module when Terraform sets
omitted fields to null. | `map(any)` | `{}` | no | | [append\_destination\_postfix](#input\_append\_destination\_postfix) | Controls whether to append '-destination' to the name of the destination | `bool` | `true` | no | | [append\_pipe\_postfix](#input\_append\_pipe\_postfix) | Controls whether to append '-pipe' to the name of the pipe | `bool` | `true` | no | | [append\_rule\_postfix](#input\_append\_rule\_postfix) | Controls whether to append '-rule' to the name of the rule | `bool` | `true` | no | @@ -184,7 +183,6 @@ Wildcards and aliases are not accepted for this policy. | [bus\_name](#input\_bus\_name) | A unique name for your EventBridge Bus. Must be unique per AWS account and region. Defaults to whatever the tags module produces | `string` | `null` | no | | [cloudwatch\_target\_arns](#input\_cloudwatch\_target\_arns) | The Amazon Resource Name (ARN) of the Cloudwatch Log Streams you want to use as EventBridge targets | `list(string)` | `[]` | 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 | -| [create\_api\_destinations](#input\_create\_api\_destinations) | Controls whether EventBridge Destination resources should be created | `bool` | `false` | no | | [create\_archives](#input\_create\_archives) | Controls whether EventBridge Archive resources should be created | `bool` | `false` | no | | [create\_bus](#input\_create\_bus) | Controls whether EventBridge Bus resource should be created | `bool` | `true` | no | | [create\_log\_delivery](#input\_create\_log\_delivery) | Controls whether EventBridge log delivery resources should be created | `bool` | `true` | no | diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 08b6b86f..26190018 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -13,12 +13,6 @@ locals { ) } - # API destination names must be unique per AWS account and region. - # We prefix provided names with the module ID to ensure uniqueness. - api_destinations = { - for k, v in var.api_destinations : "${module.this.id}-${k}" => v - } - # schedule group names must be unique per AWS account and region # prefix provided names with the module ID to ensure uniqueness # disallow explicitly setting `name` or `name_prefix` diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index 2b5c9e87..b6950135 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -29,7 +29,6 @@ module "eventbridge" { create_targets = var.create_targets create_permissions = var.create_permissions create_archives = var.create_archives - create_api_destinations = var.create_api_destinations create_schedule_groups = var.create_schedule_groups create_schedules = var.create_schedules create_pipes = var.create_pipes @@ -49,7 +48,6 @@ module "eventbridge" { targets = var.targets archives = var.archives permissions = var.permissions - api_destinations = local.api_destinations schedule_groups = local.schedule_groups schedules = local.schedules pipes = local.pipes diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index c937ecde..3a2ce476 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -70,12 +70,6 @@ variable "create_archives" { default = false } -variable "create_api_destinations" { - description = "Controls whether EventBridge Destination resources should be created" - type = bool - default = false -} - variable "create_schedule_groups" { description = "Controls whether EventBridge Schedule Group resources should be created" type = bool @@ -302,27 +296,6 @@ variable "permissions" { default = {} } -variable "api_destinations" { - description = <<-EOF - A map of objects with EventBridge Destination definitions. - - The type should really be - - map(object({ - description = optional(string) - invocation_endpoint = string - http_method = string - invocation_rate_limit_per_second = optional(number) - connection_name = optional(string) # but connections have been removed from this wrapper anyway - })) - - but it causes problems in the community module when Terraform sets - omitted fields to null. - EOF - type = map(any) - default = {} -} - variable "schedule_groups" { description = <<-EOF A map of objects with EventBridge Schedule Group definitions. From 8e2ce5f5ad0e13f71ae1b6589960ff1a9b1e02c6 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 15:24:46 +0100 Subject: [PATCH 54/64] fix: handle missing properties properly in `local.schedules` --- infrastructure/modules/eventbridge/locals.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 26190018..f20e2fc1 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -26,7 +26,7 @@ locals { # name in schedule_groups schedules = { for k, v in var.schedules : k => ( - contains(keys(v), "group_name") && local.schedule_groups[v.group_name] != null + try(local.schedule_groups[v.group_name], null) != null # either property could be absent ? merge( v, { From 0ecabeb7903c9a6b89eb98ec0c8ec9f68b11d8bd Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 15:39:38 +0100 Subject: [PATCH 55/64] fix: handle missing properties properly in variable validation --- infrastructure/modules/eventbridge/variables.tf | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 3a2ce476..fc363990 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -269,8 +269,7 @@ variable "archives" { validation { condition = alltrue([ for archive in var.archives : - archive.kms_key_identifier != null && - trimspace(archive.kms_key_identifier) != "" + try(trimspace(archive.kms_key_identifier), "") != "" ]) error_message = "Each archive must specify a non-empty kms_key_identifier." @@ -354,8 +353,7 @@ variable "schedules" { validation { condition = alltrue([ for schedule in var.schedules : - schedule.kms_key_arn != null && - trimspace(schedule.kms_key_arn) != "" + try(trimspace(schedule.kms_key_arn), "") != "" ]) error_message = "Each schedule must specify a non-empty kms_key_arn." @@ -392,8 +390,7 @@ variable "pipes" { validation { condition = alltrue([ for pipe in var.pipes : - pipe.kms_key_identifier != null && - trimspace(pipe.kms_key_identifier) != "" + try(trimspace(pipe.kms_key_identifier), "") != "" ]) error_message = "Each pipe must specify a non-empty kms_key_identifier." From 502985f71ae937084e60023925c015e3221ae903 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 16:12:49 +0100 Subject: [PATCH 56/64] fix: prevent var.bus_name from being "" --- infrastructure/modules/eventbridge/locals.tf | 6 +++++- infrastructure/modules/eventbridge/variables.tf | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index f20e2fc1..ba5741da 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -1,6 +1,10 @@ locals { # allow bus name to be overridden without touching tags - bus_name = coalesce(var.bus_name, module.this.id) + bus_name = coalesce( + var.bus_name, + # `module.this.id` is `""` when `var.enabled` is false + var.enabled ? module.this.id : null, + ) # log delivery names must be unique per AWS account # provide a default name based on the module ID diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index fc363990..ab88bdc3 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -105,6 +105,11 @@ variable "bus_name" { type = string nullable = true default = null + + validation { + condition = try(trimspace(var.bus_name), "null") != "" + error_message = "bus_name must not be empty" + } } variable "bus_description" { From fdc138ca65c918e87dce798adf0e14a82f833b1d Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 17:04:54 +0100 Subject: [PATCH 57/64] =?UTF-8?q?fix:=20validate=20that=20var.schedule=5Fg?= =?UTF-8?q?roups=20doesn=E2=80=99t=20contain=20name=20or=20name=5Fprefix?= =?UTF-8?q?=20properties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infrastructure/modules/eventbridge/locals.tf | 2 +- infrastructure/modules/eventbridge/variables.tf | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index ba5741da..99a20671 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -19,7 +19,7 @@ locals { # schedule group names must be unique per AWS account and region # prefix provided names with the module ID to ensure uniqueness - # disallow explicitly setting `name` or `name_prefix` + # validation prevents explicitly setting `name` or `name_prefix` schedule_groups = { for k, v in var.schedule_groups : k => ( merge(v, { name = "${module.this.id}-${k}" }) diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index ab88bdc3..f05d82f9 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -317,6 +317,23 @@ variable "schedule_groups" { EOF type = any default = {} + + validation { + condition = alltrue([ + for k, v in var.schedule_groups : + try( + !( + contains(keys(v), "name") || contains(keys(v), "name_prefix") + ), + false, + ) + ]) + + error_message = <<-EOF + To ensure uniqueness, we disallow setting the `name` or `name_prefix` + attributes in `schedule_groups`. + EOF + } } variable "schedules" { From db123fce447badf638f078d097ec21ddf8e9920d Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 17:22:19 +0100 Subject: [PATCH 58/64] fix: make precondition on var.attach_sns_policy / var.sns_kms_arns more ergonomic --- infrastructure/modules/eventbridge/validations.tf | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/validations.tf b/infrastructure/modules/eventbridge/validations.tf index 3ecc4ad5..4212281d 100644 --- a/infrastructure/modules/eventbridge/validations.tf +++ b/infrastructure/modules/eventbridge/validations.tf @@ -3,7 +3,12 @@ resource "terraform_data" "validations" { lifecycle { precondition { - condition = !var.attach_sns_policy || length(var.sns_kms_arns) > 0 + condition = anytrue([ + !var.create_role, + !var.attach_sns_policy, + length(var.sns_kms_arns) > 0, + ]) + error_message = "attach_sns_policy requires at least one specific sns_kms_arns entry." } } From d7512445db2adf8028618e796b388639c0660574 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 17:36:43 +0100 Subject: [PATCH 59/64] fix: make var.kms_key_identifier validation more ergonomic --- infrastructure/modules/eventbridge/validations.tf | 9 +++++++++ infrastructure/modules/eventbridge/variables.tf | 5 ----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/infrastructure/modules/eventbridge/validations.tf b/infrastructure/modules/eventbridge/validations.tf index 4212281d..f2b13fbd 100644 --- a/infrastructure/modules/eventbridge/validations.tf +++ b/infrastructure/modules/eventbridge/validations.tf @@ -11,5 +11,14 @@ resource "terraform_data" "validations" { error_message = "attach_sns_policy requires at least one specific sns_kms_arns entry." } + + precondition { + condition = anytrue([ + !var.create_bus, + try(trimspace(var.kms_key_identifier) != "", false), + ]) + + error_message = "kms_key_identifier must not be empty when creating the bus" + } } } diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index f05d82f9..76b01f9f 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -161,11 +161,6 @@ variable "kms_key_identifier" { description = "The identifier of the AWS KMS customer managed key for EventBridge to use, to encrypt events on this event bus. The identifier can be the key Amazon Resource Name (ARN), KeyId, key alias, or key alias ARN." type = string nullable = false - - validation { - condition = trimspace(var.kms_key_identifier) != "" - error_message = "kms_key_identifier must not be empty" - } } variable "dead_letter_config" { From 64415ebf8dfb033082bcd3d5c3c90d8b08741eb3 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 17:38:38 +0100 Subject: [PATCH 60/64] =?UTF-8?q?docs:=20clarify=20what=20we=20don?= =?UTF-8?q?=E2=80=99t=20do?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infrastructure/modules/eventbridge/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/README.md b/infrastructure/modules/eventbridge/README.md index a8fdc7b8..f2a37b5b 100644 --- a/infrastructure/modules/eventbridge/README.md +++ b/infrastructure/modules/eventbridge/README.md @@ -119,7 +119,7 @@ Wildcards and aliases are not accepted for this policy. - It does not create KMS keys, SNS topics, target resources, or their resource policies. - It does not guarantee unique names for every upstream resource or configure all target permissions. -- It does not keep connection credentials out of Terraform state; use encrypted remote state and limit access to plans and state. +- It does not support EventBridge connections or API destinations From 27de7ad19a1f722884d46b393c9c4d78d4c89696 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Thu, 1 Oct 2026 17:40:32 +0100 Subject: [PATCH 61/64] chore: point maintainers to validations.tf --- infrastructure/modules/eventbridge/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/modules/eventbridge/main.tf b/infrastructure/modules/eventbridge/main.tf index b6950135..8324e018 100644 --- a/infrastructure/modules/eventbridge/main.tf +++ b/infrastructure/modules/eventbridge/main.tf @@ -6,6 +6,7 @@ # * SNS KMS permissions are restricted to explicitly approved keys. # * Resource names and tags are derived from context.tf. # * Creation is gated by module.this.enabled. +# * Cross-variable input constraints are enforced in validations.tf. # * Connections have been removed from this wrapper, because the # underlying community module stores them in full in the terraform # state, including credentials. From 6d26a587462c01e98f094ae055d259c880c66d70 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Fri, 2 Oct 2026 08:16:34 +0100 Subject: [PATCH 62/64] fix: `var.enabled` could be null, so use `module.this.enabled` in conditional --- infrastructure/modules/eventbridge/locals.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index 99a20671..da3780a3 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -3,7 +3,7 @@ locals { bus_name = coalesce( var.bus_name, # `module.this.id` is `""` when `var.enabled` is false - var.enabled ? module.this.id : null, + module.this.enabled ? module.this.id : null, ) # log delivery names must be unique per AWS account From 6a0c8d401838b03affadbc666b03829bfc9d199a Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Fri, 2 Oct 2026 08:21:12 +0100 Subject: [PATCH 63/64] docs: add section headers to variables.tf --- .../modules/eventbridge/variables.tf | 84 ++++++++++++------- 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/infrastructure/modules/eventbridge/variables.tf b/infrastructure/modules/eventbridge/variables.tf index 76b01f9f..29606d17 100644 --- a/infrastructure/modules/eventbridge/variables.tf +++ b/infrastructure/modules/eventbridge/variables.tf @@ -1,3 +1,7 @@ +################################################################ +# Creation controls +################################################################ + variable "create_role" { description = "Controls whether IAM roles should be created" type = bool @@ -10,36 +14,6 @@ variable "create_pipe_role_only" { default = false } -variable "append_rule_postfix" { - description = "Controls whether to append '-rule' to the name of the rule" - type = bool - default = true -} - -variable "append_destination_postfix" { - description = "Controls whether to append '-destination' to the name of the destination" - type = bool - default = true -} - -variable "append_schedule_group_postfix" { - description = "Controls whether to append '-group' to the name of the schedule group" - type = bool - default = true -} - -variable "append_schedule_postfix" { - description = "Controls whether to append '-schedule' to the name of the schedule" - type = bool - default = true -} - -variable "append_pipe_postfix" { - description = "Controls whether to append '-pipe' to the name of the pipe" - type = bool - default = true -} - variable "create_bus" { description = "Controls whether EventBridge Bus resource should be created" type = bool @@ -100,6 +74,44 @@ variable "create_log_delivery" { default = true } +################################################################ +# Naming +################################################################ + +variable "append_rule_postfix" { + description = "Controls whether to append '-rule' to the name of the rule" + type = bool + default = true +} + +variable "append_destination_postfix" { + description = "Controls whether to append '-destination' to the name of the destination" + type = bool + default = true +} + +variable "append_schedule_group_postfix" { + description = "Controls whether to append '-group' to the name of the schedule group" + type = bool + default = true +} + +variable "append_schedule_postfix" { + description = "Controls whether to append '-schedule' to the name of the schedule" + type = bool + default = true +} + +variable "append_pipe_postfix" { + description = "Controls whether to append '-pipe' to the name of the pipe" + type = bool + default = true +} + +################################################################ +# Bus and logging +################################################################ + variable "bus_name" { description = "A unique name for your EventBridge Bus. Must be unique per AWS account and region. Defaults to whatever the tags module produces" type = string @@ -187,6 +199,10 @@ variable "schemas_discoverer_description" { default = "Auto schemas discoverer event" } +################################################################ +# EventBridge resources +################################################################ + variable "rules" { description = <<-EOF A map of objects with EventBridge Rule definitions. @@ -420,6 +436,10 @@ variable "schedule_group_timeouts" { default = {} } +################################################################ +# IAM role +################################################################ + variable "role_name" { description = "Name of IAM role to use for EventBridge" type = string @@ -468,6 +488,10 @@ variable "ecs_pass_role_resources" { default = [] } +################################################################ +# Target policies +################################################################ + variable "attach_kinesis_policy" { description = "Controls whether the Kinesis policy should be added to IAM role for EventBridge Target" type = bool From 4ea2a459239d4cd818e91b98ee76887ec03f15d3 Mon Sep 17 00:00:00 2001 From: Dave Hinton Date: Fri, 2 Oct 2026 08:34:14 +0100 Subject: [PATCH 64/64] fix: coalesce() errors if all arguments are null! --- infrastructure/modules/eventbridge/locals.tf | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/infrastructure/modules/eventbridge/locals.tf b/infrastructure/modules/eventbridge/locals.tf index da3780a3..afd037fd 100644 --- a/infrastructure/modules/eventbridge/locals.tf +++ b/infrastructure/modules/eventbridge/locals.tf @@ -1,9 +1,14 @@ locals { # allow bus name to be overridden without touching tags - bus_name = coalesce( - var.bus_name, - # `module.this.id` is `""` when `var.enabled` is false - module.this.enabled ? module.this.id : null, + bus_name = ( + # `module.this.id` is `""` (an error) when `module.this.enabled` is false + # `coalesce()` errors if all arguments are null + module.this.enabled + ? coalesce( + var.bus_name, + module.this.enabled ? module.this.id : null, + ) + : null ) # log delivery names must be unique per AWS account