diff --git a/.tool-versions b/.tool-versions index 4ca52a65..f098ca89 100644 --- a/.tool-versions +++ b/.tool-versions @@ -5,7 +5,7 @@ nodejs 24.14.1 pnpm 10.33.0 pre-commit 3.6.0 ruby 3.3.6 -terraform 1.10.1 +terraform 1.14.3 terraform-docs 0.19.0 #trivy 0.61.0 - TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549 vale 3.6.0 diff --git a/infrastructure/terraform/components/callback-clients/.tool-versions b/infrastructure/terraform/components/callback-clients/.tool-versions new file mode 100644 index 00000000..52428ded --- /dev/null +++ b/infrastructure/terraform/components/callback-clients/.tool-versions @@ -0,0 +1 @@ +terraform 1.14.3 diff --git a/infrastructure/terraform/components/callback-clients/README.md b/infrastructure/terraform/components/callback-clients/README.md new file mode 100644 index 00000000..ebb2ba89 --- /dev/null +++ b/infrastructure/terraform/components/callback-clients/README.md @@ -0,0 +1,29 @@ + + + + +## Requirements + +No requirements. +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes | +| [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no | +| [environment](#input\_environment) | The name of the tfscaffold environment | `string` | n/a | yes | +| [force\_lambda\_code\_deploy](#input\_force\_lambda\_code\_deploy) | If the lambda package in s3 has the same commit id tag as the terraform build branch, the lambda will not update automatically. Set to True if making changes to Lambda code from on the same commit for example during development | `bool` | `false` | no | +| [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | n/a | yes | +| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite | `number` | `0` | no | +| [parent\_callbacks\_environment](#input\_parent\_callbacks\_environment) | The name of the environment which deployed the parent Amplify resource. Used to identify the appropriate state file. | `string` | `"main"` | no | +| [project](#input\_project) | The name of the tfscaffold project | `string` | n/a | yes | +| [region](#input\_region) | The AWS Region | `string` | n/a | yes | +## Modules + +No modules. +## Outputs + +No outputs. + + + diff --git a/infrastructure/terraform/components/callback-clients/cloudwatch_event_rule.tf b/infrastructure/terraform/components/callback-clients/cloudwatch_event_rule.tf new file mode 100644 index 00000000..6d36322a --- /dev/null +++ b/infrastructure/terraform/components/callback-clients/cloudwatch_event_rule.tf @@ -0,0 +1,16 @@ +resource "aws_cloudwatch_event_rule" "main" { + name = "${local.csi}-callback-rule" + event_bus_name = local.callbacks.eventbus_name.name # Reference the event bus from callbacks component + + event_pattern = jsonencode({ + source = [{ prefix = "" }] # Your event pattern here this is effectively "*" + }) +} + +# resource "aws_cloudwatch_event_target" "main" { +# rule = aws_cloudwatch_event_rule.main.name +# event_bus_name = local.callbacks.eventbus_name.name # Same event bus reference +# target_id = "callback-target" +# arn = # Your target ARN (Lambda, SNS, etc.) +# # Additional target configuration... +# } diff --git a/infrastructure/terraform/components/callback-clients/locals_remote_state.tf b/infrastructure/terraform/components/callback-clients/locals_remote_state.tf new file mode 100644 index 00000000..bc81cbf4 --- /dev/null +++ b/infrastructure/terraform/components/callback-clients/locals_remote_state.tf @@ -0,0 +1,21 @@ +locals { + callbacks = data.terraform_remote_state.callbacks.outputs +} + +data "terraform_remote_state" "callbacks" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + + key = format( + "%s/%s/%s/%s/callbacks.tfstate", + var.project, + var.aws_account_id, + "eu-west-2", + var.parent_callbacks_environment + ) + + region = "eu-west-2" + } +} diff --git a/infrastructure/terraform/components/callback-clients/locals_tfscaffold.tf b/infrastructure/terraform/components/callback-clients/locals_tfscaffold.tf new file mode 100644 index 00000000..5c28416c --- /dev/null +++ b/infrastructure/terraform/components/callback-clients/locals_tfscaffold.tf @@ -0,0 +1,46 @@ +locals { + component = "cbc" + + terraform_state_bucket = format( + "%s-tfscaffold-%s-%s", + var.project, + var.aws_account_id, + var.region, + ) + + csi = replace( + format( + "%s-%s-%s", + var.project, + var.environment, + local.component, + ), + "_", + "", + ) + + # CSI for use in resources with a global namespace, i.e. S3 Buckets + csi_global = replace( + format( + "%s-%s-%s-%s-%s", + var.project, + var.aws_account_id, + var.region, + var.environment, + local.component, + ), + "_", + "", + ) + + default_tags = merge( + var.default_tags, + { + Project = var.project + Environment = var.environment + Component = local.component + Group = var.group + Name = local.csi + }, + ) +} diff --git a/infrastructure/terraform/components/callback-clients/outputs.tf b/infrastructure/terraform/components/callback-clients/outputs.tf new file mode 100644 index 00000000..9dcc2f39 --- /dev/null +++ b/infrastructure/terraform/components/callback-clients/outputs.tf @@ -0,0 +1 @@ +# Define the outputs for the component. The outputs may well be referenced by other component in the same or different environments using terraform_remote_state data sources... diff --git a/infrastructure/terraform/components/callback-clients/pre.sh.disabled b/infrastructure/terraform/components/callback-clients/pre.sh.disabled new file mode 100755 index 00000000..f34040cf --- /dev/null +++ b/infrastructure/terraform/components/callback-clients/pre.sh.disabled @@ -0,0 +1,81 @@ +#!/bin/bash + +## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## +## This script is currently disabled (renamed to pre.sh.disabled) as it is only needed for +## Terraform components that need build and publish container images or lambda packageses. +## +## It is left in place for reference and future use when adding new components that require this functionality. +## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## + + +# This script is run before Terraform executable commands. +# It ensures all Node.js dependencies are installed, generates any required dependencies, +# and builds all Lambda functions in the workspace before Terraform provisions infrastructure. +# pre.sh runs in the same shell as terraform.sh, not in a subshell + +: "${PROJECT:?PROJECT is required}" +: "${REGION:?REGION is required}" +: "${COMPONENT:?COMPONENT is required}" +: "${ENVIRONMENT:?ENVIRONMENT is required}" +: "${AWS_ACCOUNT_ID:?AWS_ACCOUNT_ID is required}" +: "${ACTION:?ACTION is required}" + +echo "Running app pre.sh" +echo "ENVIRONMENT=$ENVIRONMENT" +echo "ACTION=$ACTION" +echo "PROJECT=$PROJECT" +echo "COMPONENT=$COMPONENT" +echo "AWS_REGION=$REGION" +echo "AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID" + +# Calculate container image prefix from PROJECT, ENVIRONMENT, COMPONENT +CONTAINER_IMAGE_PREFIX="${PROJECT}-${ENVIRONMENT}-${COMPONENT}" +echo "CONTAINER_IMAGE_PREFIX: ${CONTAINER_IMAGE_PREFIX}" + +# Translate ACTION to PUBLISH_CONTAINER_IMAGE (build) +if [ "${ACTION}" = "plan" ]; then + PUBLISH_CONTAINER_IMAGE="false" +else + PUBLISH_CONTAINER_IMAGE="true" +fi + +# Helper function for error handling +run_or_fail() { + "$@" + if [ $? -ne 0 ]; then + echo "$* failed!" >&2 + exit 1 + fi +} + +# Switch to repo root +pushd "$(git rev-parse --show-toplevel)" || exit 1 + +# Calculate git-based version suffix +SHORT_SHA="$(git rev-parse --short HEAD)" +GIT_TAG="$(git describe --tags --exact-match 2>/dev/null || true)" + +if [ -n "${GIT_TAG}" ]; then + RELEASE_VERSION="${GIT_TAG#v}" + CONTAINER_IMAGE_SUFFIX="release-${RELEASE_VERSION}-${SHORT_SHA}" + echo "On tag: $GIT_TAG, image suffix: ${CONTAINER_IMAGE_SUFFIX}" +else + CONTAINER_IMAGE_SUFFIX="sha-${SHORT_SHA}" + echo "Not on a tag, image suffix: ${CONTAINER_IMAGE_SUFFIX}" +fi + +# Export for Terraform +export TF_VAR_container_image_tag_suffix="${CONTAINER_IMAGE_SUFFIX}" + +run_or_fail pnpm install --frozen-lockfile +run_or_fail pnpm -r run --if-present generate-dependencies +run_or_fail pnpm -r run --if-present build:archive +run_or_fail env \ + CONTAINER_IMAGE_PREFIX="${CONTAINER_IMAGE_PREFIX}" \ + CONTAINER_IMAGE_SUFFIX="${CONTAINER_IMAGE_SUFFIX}" \ + AWS_ACCOUNT_ID="${AWS_ACCOUNT_ID}" \ + AWS_REGION="${REGION}" \ + PUBLISH_CONTAINER_IMAGE="${PUBLISH_CONTAINER_IMAGE}" \ + pnpm -r run --if-present build:container + +popd || exit 1 # Return to working directory diff --git a/infrastructure/terraform/components/callback-clients/variables.tf b/infrastructure/terraform/components/callback-clients/variables.tf new file mode 100644 index 00000000..51c06517 --- /dev/null +++ b/infrastructure/terraform/components/callback-clients/variables.tf @@ -0,0 +1,65 @@ +## +# Basic Required Variables for tfscaffold Components +## + +variable "project" { + type = string + description = "The name of the tfscaffold project" +} + +variable "environment" { + type = string + description = "The name of the tfscaffold environment" +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +variable "region" { + type = string + description = "The AWS Region" +} + +variable "group" { + type = string + description = "The group variables are being inherited from (often synonmous with account short-name)" +} + +## +# tfscaffold variables specific to this component +## + +# This is the only primary variable to have its value defined as +# a default within its declaration in this file, because the variables +# purpose is as an identifier unique to this component, rather +# then to the environment from where all other variables come. + +variable "default_tags" { + type = map(string) + description = "A map of default tags to apply to all taggable resources within the component" + default = {} +} + +## +# Variables specific to the component +## + +variable "log_retention_in_days" { + type = number + description = "The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite" + default = 0 +} + +variable "force_lambda_code_deploy" { + type = bool + description = "If the lambda package in s3 has the same commit id tag as the terraform build branch, the lambda will not update automatically. Set to True if making changes to Lambda code from on the same commit for example during development" + default = false +} + +variable "parent_callbacks_environment" { + type = string + description = "The name of the environment which deployed the parent Amplify resource. Used to identify the appropriate state file." + default = "main" +} diff --git a/infrastructure/terraform/components/callbacks/.tool-versions b/infrastructure/terraform/components/callbacks/.tool-versions index 3dd74c72..52428ded 100644 --- a/infrastructure/terraform/components/callbacks/.tool-versions +++ b/infrastructure/terraform/components/callbacks/.tool-versions @@ -1 +1 @@ -terraform 1.10.1 +terraform 1.14.3 diff --git a/infrastructure/terraform/components/callbacks/README.md b/infrastructure/terraform/components/callbacks/README.md index b1587725..d3b9949c 100644 --- a/infrastructure/terraform/components/callbacks/README.md +++ b/infrastructure/terraform/components/callbacks/README.md @@ -15,7 +15,6 @@ |------|-------------|------|---------|:--------:| | [applications\_map\_parameter\_name](#input\_applications\_map\_parameter\_name) | SSM Parameter Store path for the clientId-to-applicationData map, where applicationData is currently only the applicationId | `string` | `null` | no | | [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes | -| [component](#input\_component) | The variable encapsulating the name of this component | `string` | `"callbacks"` | no | | [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no | | [deploy\_mock\_clients](#input\_deploy\_mock\_clients) | Flag to deploy mock webhook lambda for integration testing (test/dev environments only) | `bool` | `false` | no | | [enable\_event\_anomaly\_detection](#input\_enable\_event\_anomaly\_detection) | Enable CloudWatch anomaly detection alarm for inbound event queue message reception | `bool` | `true` | no | @@ -55,6 +54,7 @@ | Name | Description | |------|-------------| | [deployment](#output\_deployment) | Deployment details used for post-deployment scripts | +| [eventbus\_name](#output\_eventbus\_name) | Name of the EventBridge event bus for callback events | | [mock\_webhook\_lambda\_log\_group\_name](#output\_mock\_webhook\_lambda\_log\_group\_name) | CloudWatch log group name for mock webhook lambda (for integration test queries) | diff --git a/infrastructure/terraform/components/callbacks/locals.tf b/infrastructure/terraform/components/callbacks/locals.tf index f4707154..dff65db0 100644 --- a/infrastructure/terraform/components/callbacks/locals.tf +++ b/infrastructure/terraform/components/callbacks/locals.tf @@ -62,5 +62,5 @@ locals { } ]...) - applications_map_parameter_name = coalesce(var.applications_map_parameter_name, "/${var.project}/${var.environment}/${var.component}/applications-map") + applications_map_parameter_name = coalesce(var.applications_map_parameter_name, "/${var.project}/${var.environment}/${local.component}/applications-map") } diff --git a/infrastructure/terraform/components/callbacks/locals_tfscaffold.tf b/infrastructure/terraform/components/callbacks/locals_tfscaffold.tf index b7cf3217..4d68787a 100644 --- a/infrastructure/terraform/components/callbacks/locals_tfscaffold.tf +++ b/infrastructure/terraform/components/callbacks/locals_tfscaffold.tf @@ -1,4 +1,6 @@ locals { + component = "cb" + terraform_state_bucket = format( "%s-tfscaffold-%s-%s", var.project, @@ -11,7 +13,7 @@ locals { "%s-%s-%s", var.project, var.environment, - var.component, + local.component, ), "_", "", @@ -25,7 +27,7 @@ locals { var.aws_account_id, var.region, var.environment, - var.component, + local.component, ), "_", "", @@ -36,7 +38,7 @@ locals { { Project = var.project Environment = var.environment - Component = var.component + Component = local.component Group = var.group Name = local.csi }, diff --git a/infrastructure/terraform/components/callbacks/module_client_destination.tf b/infrastructure/terraform/components/callbacks/module_client_destination.tf index 21800e94..7be7545e 100644 --- a/infrastructure/terraform/components/callbacks/module_client_destination.tf +++ b/infrastructure/terraform/components/callbacks/module_client_destination.tf @@ -4,7 +4,7 @@ module "client_destination" { project = var.project aws_account_id = var.aws_account_id region = var.region - component = var.component + component = local.component environment = var.environment client_bus_name = aws_cloudwatch_event_bus.main.name diff --git a/infrastructure/terraform/components/callbacks/module_kms.tf b/infrastructure/terraform/components/callbacks/module_kms.tf index 327b5641..117a0106 100644 --- a/infrastructure/terraform/components/callbacks/module_kms.tf +++ b/infrastructure/terraform/components/callbacks/module_kms.tf @@ -2,7 +2,7 @@ module "kms" { source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.7/terraform-kms.zip" aws_account_id = var.aws_account_id - component = var.component + component = local.component environment = var.environment project = var.project region = var.region diff --git a/infrastructure/terraform/components/callbacks/module_mock_webhook_lambda.tf b/infrastructure/terraform/components/callbacks/module_mock_webhook_lambda.tf index b951351e..9d5ac0bd 100644 --- a/infrastructure/terraform/components/callbacks/module_mock_webhook_lambda.tf +++ b/infrastructure/terraform/components/callbacks/module_mock_webhook_lambda.tf @@ -6,7 +6,7 @@ module "mock_webhook_lambda" { description = "Mock webhook endpoint for integration testing - logs received callbacks to CloudWatch" aws_account_id = var.aws_account_id - component = var.component + component = local.component environment = var.environment project = var.project region = var.region diff --git a/infrastructure/terraform/components/callbacks/module_sqs_inbound_event.tf b/infrastructure/terraform/components/callbacks/module_sqs_inbound_event.tf index 2e3080fe..2a15e357 100644 --- a/infrastructure/terraform/components/callbacks/module_sqs_inbound_event.tf +++ b/infrastructure/terraform/components/callbacks/module_sqs_inbound_event.tf @@ -2,7 +2,7 @@ module "sqs_inbound_event" { source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.7/terraform-sqs.zip" aws_account_id = var.aws_account_id - component = var.component + component = local.component environment = var.environment project = var.project region = var.region @@ -33,7 +33,7 @@ data "aws_iam_policy_document" "sqs_inbound_event" { ] resources = [ - "arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-${var.component}-inbound-event-queue" + "arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-${local.component}-inbound-event-queue" ] condition { diff --git a/infrastructure/terraform/components/callbacks/module_transform_filter_lambda.tf b/infrastructure/terraform/components/callbacks/module_transform_filter_lambda.tf index fb1313f8..e6c0f20e 100644 --- a/infrastructure/terraform/components/callbacks/module_transform_filter_lambda.tf +++ b/infrastructure/terraform/components/callbacks/module_transform_filter_lambda.tf @@ -5,7 +5,7 @@ module "client_transform_filter_lambda" { description = "Lambda function that transforms and filters events coming to through the eventpipe" aws_account_id = var.aws_account_id - component = var.component + component = local.component environment = var.environment project = var.project region = var.region diff --git a/infrastructure/terraform/components/callbacks/outputs.tf b/infrastructure/terraform/components/callbacks/outputs.tf index 1ca00df8..88cbcb59 100644 --- a/infrastructure/terraform/components/callbacks/outputs.tf +++ b/infrastructure/terraform/components/callbacks/outputs.tf @@ -10,10 +10,23 @@ output "deployment" { project = var.project environment = var.environment group = var.group - component = var.component + component = local.component } } +## +# EventBridge Event Bus Outputs +## + +output "eventbus_name" { + description = "Name of the EventBridge event bus for callback events" + value = { + name = aws_cloudwatch_event_bus.main.name + arn = aws_cloudwatch_event_bus.main.arn + } +} + + ## # Mock Webhook Lambda Outputs (test/dev environments only). ## diff --git a/infrastructure/terraform/components/callbacks/s3_bucket_client_config.tf b/infrastructure/terraform/components/callbacks/s3_bucket_client_config.tf index 8bf25c83..dabc95d1 100644 --- a/infrastructure/terraform/components/callbacks/s3_bucket_client_config.tf +++ b/infrastructure/terraform/components/callbacks/s3_bucket_client_config.tf @@ -17,7 +17,7 @@ module "client_config_bucket" { name = "subscription-config" aws_account_id = var.aws_account_id - component = var.component + component = local.component environment = var.environment project = var.project region = var.region diff --git a/infrastructure/terraform/components/callbacks/variables.tf b/infrastructure/terraform/components/callbacks/variables.tf index 74a72d24..59b858bc 100644 --- a/infrastructure/terraform/components/callbacks/variables.tf +++ b/infrastructure/terraform/components/callbacks/variables.tf @@ -35,11 +35,7 @@ variable "group" { # a default within its declaration in this file, because the variables # purpose is as an identifier unique to this component, rather # then to the environment from where all other variables come. -variable "component" { - type = string - description = "The variable encapsulating the name of this component" - default = "callbacks" -} + variable "default_tags" { type = map(string)