diff --git a/infrastructure/terraform/components/api/README.md b/infrastructure/terraform/components/api/README.md index 00196a0b..baec57b9 100644 --- a/infrastructure/terraform/components/api/README.md +++ b/infrastructure/terraform/components/api/README.md @@ -78,7 +78,9 @@ No requirements. | [sqs\_alarms](#module\_sqs\_alarms) | ../../modules/alarms-sqs | n/a | | [sqs\_letter\_updates](#module\_sqs\_letter\_updates) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a | | [sqs\_supplier\_allocator](#module\_sqs\_supplier\_allocator) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a | +| [sqs\_supplier\_config](#module\_sqs\_supplier\_config) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip | n/a | | [supplier\_allocator](#module\_supplier\_allocator) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | +| [supplier\_config\_ingress](#module\_supplier\_config\_ingress) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | | [supplier\_ssl](#module\_supplier\_ssl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-ssl.zip | n/a | | [update\_letter\_queue](#module\_update\_letter\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | | [upsert\_letter](#module\_upsert\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | diff --git a/infrastructure/terraform/components/api/lambda_event_source_mapping_supplier_config_ingress.tf b/infrastructure/terraform/components/api/lambda_event_source_mapping_supplier_config_ingress.tf new file mode 100644 index 00000000..891e9012 --- /dev/null +++ b/infrastructure/terraform/components/api/lambda_event_source_mapping_supplier_config_ingress.tf @@ -0,0 +1,9 @@ +resource "aws_lambda_event_source_mapping" "supplier_config_ingress" { + event_source_arn = module.sqs_supplier_config.sqs_queue_arn + function_name = module.supplier_config_ingress.function_name + batch_size = 10 + maximum_batching_window_in_seconds = 5 + function_response_types = [ + "ReportBatchItemFailures" + ] +} diff --git a/infrastructure/terraform/components/api/module_lambda_supplier_config_ingress.tf b/infrastructure/terraform/components/api/module_lambda_supplier_config_ingress.tf new file mode 100644 index 00000000..b11740b8 --- /dev/null +++ b/infrastructure/terraform/components/api/module_lambda_supplier_config_ingress.tf @@ -0,0 +1,78 @@ + + + + + + + + + +module "supplier_config_ingress" { + source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip" + + function_name = "supplier-config-ingress" + description = "Persist supplier config changes" + + aws_account_id = var.aws_account_id + component = var.component + environment = var.environment + project = var.project + region = var.region + group = var.group + + log_retention_in_days = var.log_retention_in_days + kms_key_arn = module.kms.key_arn + + iam_policy_document = { + body = data.aws_iam_policy_document.supplier_config_ingress_lambda.json + } + + function_s3_bucket = local.acct.s3_buckets["lambda_function_artefacts"]["id"] + function_code_base_path = local.aws_lambda_functions_dir_path + function_code_dir = "supplier-config-ingress/dist" + function_include_common = true + handler_function_name = "supplierConfigHandler" + runtime = "nodejs22.x" + memory = 512 + timeout = 29 + log_level = var.log_level + + force_lambda_code_deploy = var.force_lambda_code_deploy + enable_lambda_insights = false + + log_destination_arn = local.destination_arn + log_subscription_role_arn = local.acct.log_subscription_role_arn + + lambda_env_vars = merge(local.common_lambda_env_vars, {}) +} + +data "aws_iam_policy_document" "supplier_config_ingress_lambda" { + statement { + sid = "KMSPermissions" + effect = "Allow" + + actions = [ + "kms:Decrypt", + "kms:GenerateDataKey", + ] + + resources = [ + module.kms.key_arn, + ] + } + + statement { + sid = "AllowSQSRead" + effect = "Allow" + + actions = [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ] + + resources = [ + module.sqs_supplier_config.sqs_queue_arn + ] + } +} diff --git a/infrastructure/terraform/components/api/module_sqs_supplier_config.tf b/infrastructure/terraform/components/api/module_sqs_supplier_config.tf new file mode 100644 index 00000000..e3c9d51f --- /dev/null +++ b/infrastructure/terraform/components/api/module_sqs_supplier_config.tf @@ -0,0 +1,48 @@ +module "sqs_supplier_config" { + source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.6/terraform-sqs.zip" + + aws_account_id = var.aws_account_id + component = var.component + environment = var.environment + project = var.project + region = var.region + name = "supplier-config" + + sqs_kms_key_arn = module.kms.key_arn + + visibility_timeout_seconds = 60 + + create_dlq = true + sqs_policy_overload = data.aws_iam_policy_document.supplier_config_queue_policy.json +} + +data "aws_iam_policy_document" "supplier_config_queue_policy" { + version = "2012-10-17" + + statement { + sid = "AllowSNSPermissions" + effect = "Allow" + + principals { + type = "Service" + identifiers = ["sns.amazonaws.com"] + } + + actions = [ + "sqs:SendMessage", + "sqs:ListQueueTags", + "sqs:GetQueueUrl", + "sqs:GetQueueAttributes", + ] + + resources = [ + "arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-${var.component}-supplier-config-queue" + ] + + condition { + test = "ArnEquals" + variable = "aws:SourceArn" + values = [module.eventsub.sns_topic.arn] + } + } +} diff --git a/infrastructure/terraform/components/api/sns_topic_subscription_eventsub_sqs_supplier_config.tf b/infrastructure/terraform/components/api/sns_topic_subscription_eventsub_sqs_supplier_config.tf new file mode 100644 index 00000000..306cb4f2 --- /dev/null +++ b/infrastructure/terraform/components/api/sns_topic_subscription_eventsub_sqs_supplier_config.tf @@ -0,0 +1,11 @@ +resource "aws_sns_topic_subscription" "eventsub_sqs_supplier_config" { + topic_arn = module.eventsub.sns_topic.arn + protocol = "sqs" + endpoint = module.sqs_supplier_config.sqs_queue_arn + raw_message_delivery = true + + filter_policy_scope = "MessageBody" + filter_policy = jsonencode({ + type = [{ prefix = "uk.nhs.notify.supplier-config" }] + }) +} diff --git a/lambdas/supplier-config-ingress/.gitignore b/lambdas/supplier-config-ingress/.gitignore new file mode 100644 index 00000000..80323f7c --- /dev/null +++ b/lambdas/supplier-config-ingress/.gitignore @@ -0,0 +1,4 @@ +coverage +node_modules +dist +.reports diff --git a/lambdas/supplier-config-ingress/jest.config.ts b/lambdas/supplier-config-ingress/jest.config.ts new file mode 100644 index 00000000..2708bc41 --- /dev/null +++ b/lambdas/supplier-config-ingress/jest.config.ts @@ -0,0 +1,55 @@ +const baseJestConfig = { + preset: "ts-jest", + extensionsToTreatAsEsm: [".ts"], + transform: { + "^.+\\.ts$": [ + "ts-jest", + { + useESM: true, + }, + ], + }, + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // The directory where Jest should output its coverage files + coverageDirectory: "./.reports/unit/coverage", + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "babel", + + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: -10, + }, + }, + + coveragePathIgnorePatterns: ["/__tests__/"], + testPathIgnorePatterns: [".build"], + testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"], + + // Use this configuration option to add custom reporters to Jest + reporters: [ + "default", + [ + "jest-html-reporter", + { + pageTitle: "Test Report", + outputPath: "./.reports/unit/test-report.html", + includeFailureMsg: true, + }, + ], + ], + + // The test environment that will be used for testing + testEnvironment: "node", +}; + +export default baseJestConfig; diff --git a/lambdas/supplier-config-ingress/package.json b/lambdas/supplier-config-ingress/package.json new file mode 100644 index 00000000..b3ad67bb --- /dev/null +++ b/lambdas/supplier-config-ingress/package.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "@types/aws-lambda": "^8.10.148", + "esbuild": "^0.27.2" + }, + "name": "nhs-notify-supplier-api-supplier-config-ingress", + "private": true, + "scripts": { + "lambda-build": "rm -rf dist && npx esbuild --bundle --minify --sourcemap --target=es2020 --platform=node --loader:.node=file --entry-names=[name] --outdir=dist src/index.ts", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "test:unit": "jest", + "typecheck": "tsc --noEmit" + }, + "version": "0.0.1" +} diff --git a/lambdas/supplier-config-ingress/src/__tests__/index.test.ts b/lambdas/supplier-config-ingress/src/__tests__/index.test.ts new file mode 100644 index 00000000..390ea3dc --- /dev/null +++ b/lambdas/supplier-config-ingress/src/__tests__/index.test.ts @@ -0,0 +1,12 @@ +import type { SQSEvent } from "aws-lambda"; +import { supplierConfigHandler } from ".."; + +describe("supplierConfigHandler", () => { + it("returns an empty batchItemFailures list", async () => { + const event = { Records: [] } as unknown as SQSEvent; + + const result = await supplierConfigHandler(event); + + expect(result).toEqual({ batchItemFailures: [] }); + }); +}); diff --git a/lambdas/supplier-config-ingress/src/index.ts b/lambdas/supplier-config-ingress/src/index.ts new file mode 100644 index 00000000..70af21c8 --- /dev/null +++ b/lambdas/supplier-config-ingress/src/index.ts @@ -0,0 +1,9 @@ +import type { SQSBatchResponse, SQSEvent } from "aws-lambda"; + +// eslint-disable-next-line import-x/prefer-default-export +export const supplierConfigHandler = async ( + _event: SQSEvent, +): Promise => { + // Implementation to be done under CCM-17379 + return { batchItemFailures: [] }; +}; diff --git a/lambdas/supplier-config-ingress/tsconfig.json b/lambdas/supplier-config-ingress/tsconfig.json new file mode 100644 index 00000000..528c0c8b --- /dev/null +++ b/lambdas/supplier-config-ingress/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "types": [ + "jest", + "node" + ] + }, + "extends": "../../tsconfig.base.json", + "include": [ + "src/**/*", + "jest.config.ts" + ] +} diff --git a/package-lock.json b/package-lock.json index b43a56c1..4c80085c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -281,6 +281,14 @@ "pino": "bin.js" } }, + "lambdas/supplier-config-ingress": { + "name": "nhs-notify-supplier-api-supplier-config-ingress", + "version": "0.0.1", + "dependencies": { + "@types/aws-lambda": "^8.10.148", + "esbuild": "^0.27.2" + } + }, "lambdas/update-letter-queue": { "name": "nhs-notify-supplier-api-update-letter-queue", "version": "0.0.1", @@ -17678,6 +17686,10 @@ "resolved": "lambdas/mi-updates-transformer", "link": true }, + "node_modules/nhs-notify-supplier-api-supplier-config-ingress": { + "resolved": "lambdas/supplier-config-ingress", + "link": true + }, "node_modules/nhs-notify-supplier-api-suppliers-data-utility": { "resolved": "scripts/utilities/supplier-data", "link": true