diff --git a/.env.template b/.env.template new file mode 100644 index 000000000..dfc86b8cc --- /dev/null +++ b/.env.template @@ -0,0 +1,7 @@ +# Copy this to .env, and edit to your taste +# Please keep in alphabetical order + +# Deployment related + +GITHUB_TOKEN=Github Personal Access Token with packages:read permissions +GITHUB_ACTOR=Github username associated with the token diff --git a/.github/workflows/pr_destroy_dynamic_env.yaml b/.github/workflows/pr_destroy_dynamic_env.yaml index 2290aa8d0..dacfb10ef 100644 --- a/.github/workflows/pr_destroy_dynamic_env.yaml +++ b/.github/workflows/pr_destroy_dynamic_env.yaml @@ -54,5 +54,5 @@ jobs: --targetWorkflow "dispatch-deploy-dynamic-env.yaml" \ --targetEnvironment "pr${{ github.event.number }}" \ --targetAccountGroup "nhs-notify-template-management-dev" \ - --targetComponent "sandbox" \ + --targetComponent "sbx" \ --terraformAction "destroy" diff --git a/.github/workflows/stage-4-acceptance.yaml b/.github/workflows/stage-4-acceptance.yaml index 7ce8fda61..29d18160a 100644 --- a/.github/workflows/stage-4-acceptance.yaml +++ b/.github/workflows/stage-4-acceptance.yaml @@ -31,7 +31,7 @@ jobs: --targetWorkflow "dispatch-deploy-dynamic-env.yaml" \ --targetEnvironment "pr${{ inputs.pr_number }}" \ --targetAccountGroup "nhs-notify-template-management-dev" \ - --targetComponent "sandbox" \ + --targetComponent "sbx" \ --terraformAction "apply" - name: Trigger Acceptance Tests @@ -46,4 +46,4 @@ jobs: --targetWorkflow "dispatch-contextual-tests-dynamic-env.yaml" \ --targetEnvironment "pr${{ inputs.pr_number }}" \ --targetAccountGroup "nhs-notify-template-management-dev" \ - --targetComponent "sandbox" + --targetComponent "sbx" diff --git a/README.md b/README.md index 1adb5643f..14d812a9d 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,29 @@ npm install ``` +### Environment Variables and .env.template + +This repository provides a `.env.template` file at the root. This file contains example environment variables required for local development and deployment. + +**How to use:** + +1. Copy `.env.template` to a new file named `.env` in the same directory: + + ```shell + cp .env.template .env + ``` + +2. Open `.env` and update the values as needed for your environment. For example, set your GitHub personal access token and username: + + - `GITHUB_TOKEN` – Your GitHub Personal Access Token with packages:read permissions. + - `GITHUB_ACTOR` – Your GitHub username associated with the token + +3. Save the file. The application and scripts will now use these environment variables. + +**Tip:** + +- Keep `.env` files out of version control. The `.env.template` is provided as a reference for required variables. + ### Running the project locally 1. To create a Terraform backend sandbox, run: diff --git a/infrastructure/terraform/components/acct/ecr_repository_main.tf b/infrastructure/terraform/components/acct/ecr_repository_main.tf index fdfd5da7e..1daace585 100644 --- a/infrastructure/terraform/components/acct/ecr_repository_main.tf +++ b/infrastructure/terraform/components/acct/ecr_repository_main.tf @@ -11,3 +11,67 @@ resource "aws_ecr_repository" "main" { scan_on_push = true } } + +resource "aws_ecr_lifecycle_policy" "main" { + repository = aws_ecr_repository.main.name + + policy = <&2 + exit 1 + fi +} echo "Running app pre.sh" +echo "REGION=$REGION" +echo "ENVIRONMENT=$ENVIRONMENT" +echo "ACTION=$ACTION" # change to monorepo root cd $(git rev-parse --show-toplevel) -npm ci - -npm run generate-dependencies --workspaces --if-present - -npm run lambda-build --workspaces --if-present - -lambdas/layers/pdfjs/build.sh +run_or_fail npm ci +run_or_fail npm run generate-dependencies --workspaces --if-present +run_or_fail npm run lambda-build --workspaces --if-present +run_or_fail lambdas/layers/pdfjs/build.sh # revert back to original directory cd - diff --git a/infrastructure/terraform/components/sandbox/.tool-versions b/infrastructure/terraform/components/sbx/.tool-versions similarity index 100% rename from infrastructure/terraform/components/sandbox/.tool-versions rename to infrastructure/terraform/components/sbx/.tool-versions diff --git a/infrastructure/terraform/components/sandbox/README.md b/infrastructure/terraform/components/sbx/README.md similarity index 100% rename from infrastructure/terraform/components/sandbox/README.md rename to infrastructure/terraform/components/sbx/README.md diff --git a/infrastructure/terraform/components/sandbox/aws_cognito_user_pool_client_sandbox.tf b/infrastructure/terraform/components/sbx/aws_cognito_user_pool_client_sandbox.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/aws_cognito_user_pool_client_sandbox.tf rename to infrastructure/terraform/components/sbx/aws_cognito_user_pool_client_sandbox.tf diff --git a/infrastructure/terraform/components/sandbox/aws_cognito_user_pool_sandbox.tf b/infrastructure/terraform/components/sbx/aws_cognito_user_pool_sandbox.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/aws_cognito_user_pool_sandbox.tf rename to infrastructure/terraform/components/sbx/aws_cognito_user_pool_sandbox.tf diff --git a/infrastructure/terraform/components/sandbox/data_acct_kms_key.tf b/infrastructure/terraform/components/sbx/data_acct_kms_key.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/data_acct_kms_key.tf rename to infrastructure/terraform/components/sbx/data_acct_kms_key.tf diff --git a/infrastructure/terraform/components/sandbox/locals.tf b/infrastructure/terraform/components/sbx/locals.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/locals.tf rename to infrastructure/terraform/components/sbx/locals.tf diff --git a/infrastructure/terraform/components/sandbox/locals_remote_state.tf b/infrastructure/terraform/components/sbx/locals_remote_state.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/locals_remote_state.tf rename to infrastructure/terraform/components/sbx/locals_remote_state.tf diff --git a/infrastructure/terraform/components/sandbox/locals_tfscaffold.tf b/infrastructure/terraform/components/sbx/locals_tfscaffold.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/locals_tfscaffold.tf rename to infrastructure/terraform/components/sbx/locals_tfscaffold.tf diff --git a/infrastructure/terraform/components/sandbox/module_backend_api.tf b/infrastructure/terraform/components/sbx/module_backend_api.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/module_backend_api.tf rename to infrastructure/terraform/components/sbx/module_backend_api.tf diff --git a/infrastructure/terraform/components/sandbox/module_cognito_triggers.tf b/infrastructure/terraform/components/sbx/module_cognito_triggers.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/module_cognito_triggers.tf rename to infrastructure/terraform/components/sbx/module_cognito_triggers.tf diff --git a/infrastructure/terraform/components/sandbox/module_eventpub.tf b/infrastructure/terraform/components/sbx/module_eventpub.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/module_eventpub.tf rename to infrastructure/terraform/components/sbx/module_eventpub.tf diff --git a/infrastructure/terraform/components/sandbox/outputs.tf b/infrastructure/terraform/components/sbx/outputs.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/outputs.tf rename to infrastructure/terraform/components/sbx/outputs.tf diff --git a/infrastructure/terraform/components/sandbox/pre.sh b/infrastructure/terraform/components/sbx/pre.sh similarity index 68% rename from infrastructure/terraform/components/sandbox/pre.sh rename to infrastructure/terraform/components/sbx/pre.sh index ebec8fafa..83d78bd8a 100755 --- a/infrastructure/terraform/components/sandbox/pre.sh +++ b/infrastructure/terraform/components/sbx/pre.sh @@ -1,9 +1,17 @@ +# pre.sh runs in the same shell as terraform.sh, not in a subshell +# any variables set or changed, and change of directory will persist once this script exits and returns control to terraform.sh REGION=$1 ENVIRONMENT=$2 ACTION=$3 -# pre.sh runs in the same shell as terraform.sh, not in a subshell -# any variables set or changed, and change of directory will persist once this script exits and returns control to terraform.sh +# Helper function for error handling +run_or_fail() { + "$@" + if [ $? -ne 0 ]; then + echo "$* failed!" >&2 + exit 1 + fi +} echo "Running sandbox pre.sh" echo "REGION=$REGION" @@ -18,16 +26,15 @@ if [ "${ACTION}" == "apply" ]; then if [[ -z $SKIP_SANDBOX_INSTALL ]]; then echo "Installing dependencies" - npm ci; + run_or_fail npm ci; else echo "Skipping dependency installation" fi - npm run generate-dependencies --workspaces --if-present - - npm run lambda-build --workspaces --if-present + run_or_fail npm run generate-dependencies --workspaces --if-present + run_or_fail npm run lambda-build --workspaces --if-present + run_or_fail lambdas/layers/pdfjs/build.sh - lambdas/layers/pdfjs/build.sh else echo "Skipping lambda build for action $ACTION" fi diff --git a/infrastructure/terraform/components/sandbox/provider_aws.tf b/infrastructure/terraform/components/sbx/provider_aws.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/provider_aws.tf rename to infrastructure/terraform/components/sbx/provider_aws.tf diff --git a/infrastructure/terraform/components/sandbox/ses_receipt_rule.tf b/infrastructure/terraform/components/sbx/ses_receipt_rule.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/ses_receipt_rule.tf rename to infrastructure/terraform/components/sbx/ses_receipt_rule.tf diff --git a/infrastructure/terraform/components/sandbox/sns_topic_events.tf b/infrastructure/terraform/components/sbx/sns_topic_events.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/sns_topic_events.tf rename to infrastructure/terraform/components/sbx/sns_topic_events.tf diff --git a/infrastructure/terraform/components/sandbox/variables.tf b/infrastructure/terraform/components/sbx/variables.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/variables.tf rename to infrastructure/terraform/components/sbx/variables.tf diff --git a/infrastructure/terraform/components/sandbox/versions.tf b/infrastructure/terraform/components/sbx/versions.tf similarity index 100% rename from infrastructure/terraform/components/sandbox/versions.tf rename to infrastructure/terraform/components/sbx/versions.tf diff --git a/infrastructure/terraform/modules/backend-api/README.md b/infrastructure/terraform/modules/backend-api/README.md index 1f4dd9a9d..e4b0634e4 100644 --- a/infrastructure/terraform/modules/backend-api/README.md +++ b/infrastructure/terraform/modules/backend-api/README.md @@ -50,6 +50,7 @@ No requirements. | [lambda\_copy\_scanned\_object\_to\_internal](#module\_lambda\_copy\_scanned\_object\_to\_internal) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | | [lambda\_delete\_failed\_scanned\_object](#module\_lambda\_delete\_failed\_scanned\_object) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | | [lambda\_event\_publisher](#module\_lambda\_event\_publisher) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | +| [lambda\_letter\_preview\_renderer](#module\_lambda\_letter\_preview\_renderer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.32/terraform-lambda.zip | n/a | | [lambda\_process\_proof](#module\_lambda\_process\_proof) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | | [lambda\_set\_file\_virus\_scan\_status\_for\_upload](#module\_lambda\_set\_file\_virus\_scan\_status\_for\_upload) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | | [lambda\_sftp\_poll](#module\_lambda\_sftp\_poll) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a | diff --git a/infrastructure/terraform/modules/backend-api/module_lambda_letter_preview_renderer_lambda.tf b/infrastructure/terraform/modules/backend-api/module_lambda_letter_preview_renderer_lambda.tf new file mode 100644 index 000000000..bcdfc3953 --- /dev/null +++ b/infrastructure/terraform/modules/backend-api/module_lambda_letter_preview_renderer_lambda.tf @@ -0,0 +1,34 @@ +module "lambda_letter_preview_renderer" { + source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.32/terraform-lambda.zip" + + project = var.project + environment = var.environment + component = var.component + aws_account_id = var.aws_account_id + region = var.region + group = var.group + + function_name = "letter-preview-renderer" + description = "Letter preview renderer Lambda" + + kms_key_arn = var.kms_key_arn + + package_type = "Image" + image_uri = "${var.aws_account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.project}-${var.parent_acct_environment}-acct@${data.aws_ecr_image.letter_preview_renderer.image_digest}" + image_repository_names = ["${var.project}-${var.parent_acct_environment}-acct"] + + memory = 128 + timeout = 3 + + + send_to_firehose = var.send_to_firehose + log_destination_arn = var.log_destination_arn + log_retention_in_days = var.log_retention_in_days + log_subscription_role_arn = var.log_subscription_role_arn +} + +data "aws_ecr_image" "letter_preview_renderer" { + registry_id = var.aws_account_id + repository_name = "${var.project}-${var.parent_acct_environment}-acct" + image_tag = "${var.project}-${var.environment}-${var.component}-letter-preview-renderer-latest" +} diff --git a/lambdas/letter-preview-renderer/.eslintignore b/lambdas/letter-preview-renderer/.eslintignore new file mode 100644 index 000000000..1521c8b76 --- /dev/null +++ b/lambdas/letter-preview-renderer/.eslintignore @@ -0,0 +1 @@ +dist diff --git a/lambdas/letter-preview-renderer/.gitignore b/lambdas/letter-preview-renderer/.gitignore new file mode 100644 index 000000000..80323f7cf --- /dev/null +++ b/lambdas/letter-preview-renderer/.gitignore @@ -0,0 +1,4 @@ +coverage +node_modules +dist +.reports diff --git a/lambdas/letter-preview-renderer/__tests__/index.test.ts b/lambdas/letter-preview-renderer/__tests__/index.test.ts new file mode 100644 index 000000000..768b0f2c8 --- /dev/null +++ b/lambdas/letter-preview-renderer/__tests__/index.test.ts @@ -0,0 +1,17 @@ +import { handler } from '../index'; +import type { Context } from 'aws-lambda'; +import { mockDeep } from 'jest-mock-extended'; + +describe('event-logging Lambda', () => { + it('logs the input event and returns 200', async () => { + const event = { foo: 'bar' }; + const context = mockDeep(); + const callback = jest.fn(); + const result = await handler(event, context, callback); + + expect(result).toEqual({ + statusCode: 200, + body: 'Event logged', + }); + }); +}); diff --git a/lambdas/letter-preview-renderer/build.sh b/lambdas/letter-preview-renderer/build.sh new file mode 100644 index 000000000..232d68e6a --- /dev/null +++ b/lambdas/letter-preview-renderer/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +rm -rf dist + +npx esbuild \ + --bundle \ + --minify \ + --sourcemap \ + --target=es2020 \ + --platform=node \ + --loader:.node=file \ + --entry-names=[name] \ + --outdir=dist \ + src/index.ts diff --git a/lambdas/letter-preview-renderer/docker/lambda/Dockerfile b/lambdas/letter-preview-renderer/docker/lambda/Dockerfile new file mode 100644 index 000000000..a771f4e3d --- /dev/null +++ b/lambdas/letter-preview-renderer/docker/lambda/Dockerfile @@ -0,0 +1,8 @@ +ARG BASE_IMAGE + +FROM ${BASE_IMAGE} + +# Copy the built output from the build context (docker.sh should have run build.sh already) +COPY dist/index.js ${LAMBDA_TASK_ROOT}/index.js + +CMD [ "index.handler" ] diff --git a/lambdas/letter-preview-renderer/index.ts b/lambdas/letter-preview-renderer/index.ts new file mode 100644 index 000000000..3b5a5bff8 --- /dev/null +++ b/lambdas/letter-preview-renderer/index.ts @@ -0,0 +1,10 @@ +// Replace me with the actual code for your Lambda function +import { Handler } from 'aws-lambda'; + +export const handler: Handler = async (event) => { + console.log('Received event:', event); + return { + statusCode: 200, + body: 'Event logged', + }; +}; diff --git a/lambdas/letter-preview-renderer/jest.config.ts b/lambdas/letter-preview-renderer/jest.config.ts new file mode 100644 index 000000000..d30f4cd1c --- /dev/null +++ b/lambdas/letter-preview-renderer/jest.config.ts @@ -0,0 +1,60 @@ +import type { Config } from 'jest'; + +export const baseJestConfig: Config = { + preset: 'ts-jest', + + // 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__/'], + transform: { '^.+\\.ts$': 'ts-jest' }, + 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: 'jsdom', +}; + +const utilsJestConfig = { + ...baseJestConfig, + + testEnvironment: 'node', + + coveragePathIgnorePatterns: [ + ...(baseJestConfig.coveragePathIgnorePatterns ?? []), + 'zod-validators.ts', + ], +}; + +export default utilsJestConfig; diff --git a/lambdas/letter-preview-renderer/package.json b/lambdas/letter-preview-renderer/package.json new file mode 100644 index 000000000..f4989057c --- /dev/null +++ b/lambdas/letter-preview-renderer/package.json @@ -0,0 +1,23 @@ +{ + "dependencies": { + "esbuild": "^0.25.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/aws-lambda": "^8.10.148", + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "jest-mock-extended": "^3.0.7", + "typescript": "^5.8.2" + }, + "name": "nhs-notify-templates-letter-preview-renderer", + "private": true, + "scripts": { + "lambda-build": "../../scripts/lambda-container-build/docker.sh --base-image ghcr.io/nhsdigital/nhs-notify/libreoffice-amet-node-22:latest", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "test:unit": "jest", + "typecheck": "tsc --noEmit" + }, + "version": "0.0.1" +} diff --git a/lambdas/letter-preview-renderer/src/__tests__/index.test.ts b/lambdas/letter-preview-renderer/src/__tests__/index.test.ts new file mode 100644 index 000000000..768b0f2c8 --- /dev/null +++ b/lambdas/letter-preview-renderer/src/__tests__/index.test.ts @@ -0,0 +1,17 @@ +import { handler } from '../index'; +import type { Context } from 'aws-lambda'; +import { mockDeep } from 'jest-mock-extended'; + +describe('event-logging Lambda', () => { + it('logs the input event and returns 200', async () => { + const event = { foo: 'bar' }; + const context = mockDeep(); + const callback = jest.fn(); + const result = await handler(event, context, callback); + + expect(result).toEqual({ + statusCode: 200, + body: 'Event logged', + }); + }); +}); diff --git a/lambdas/letter-preview-renderer/src/index.ts b/lambdas/letter-preview-renderer/src/index.ts new file mode 100644 index 000000000..3b5a5bff8 --- /dev/null +++ b/lambdas/letter-preview-renderer/src/index.ts @@ -0,0 +1,10 @@ +// Replace me with the actual code for your Lambda function +import { Handler } from 'aws-lambda'; + +export const handler: Handler = async (event) => { + console.log('Received event:', event); + return { + statusCode: 200, + body: 'Event logged', + }; +}; diff --git a/lambdas/letter-preview-renderer/tsconfig.json b/lambdas/letter-preview-renderer/tsconfig.json new file mode 100644 index 000000000..ea37d6966 --- /dev/null +++ b/lambdas/letter-preview-renderer/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "include": [ + "src/**/*", + "jest.config.ts" + ] +} diff --git a/package-lock.json b/package-lock.json index 092b02142..e59725d62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "lambdas/backend-client", "lambdas/download-authorizer", "lambdas/sftp-letters", + "lambdas/letter-preview-renderer", "packages/event-schemas", "tests/accessibility", "tests/contracts/provider", @@ -662,6 +663,21 @@ "node": ">=18.0.0" } }, + "lambdas/letter-preview-renderer": { + "name": "nhs-notify-templates-letter-preview-renderer", + "version": "0.0.1", + "dependencies": { + "esbuild": "^0.25.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/aws-lambda": "^8.10.148", + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "jest-mock-extended": "^3.0.7", + "typescript": "^5.8.2" + } + }, "lambdas/sftp-letters": { "name": "nhs-notify-sftp-letters-lambdas", "version": "0.0.1", @@ -6498,7 +6514,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6515,7 +6530,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6532,7 +6546,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6549,7 +6562,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6566,7 +6578,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6583,7 +6594,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6600,7 +6610,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6617,7 +6626,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6634,7 +6642,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6651,7 +6658,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6668,7 +6674,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6685,7 +6690,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6702,7 +6706,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6719,7 +6722,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6736,7 +6738,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6753,7 +6754,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6770,7 +6770,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6787,7 +6786,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6804,7 +6802,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6821,7 +6818,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6838,7 +6834,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6855,7 +6850,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6872,7 +6866,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6889,7 +6882,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6906,7 +6898,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6923,7 +6914,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15459,7 +15449,6 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -21650,6 +21639,10 @@ "resolved": "lambdas/event-publisher", "link": true }, + "node_modules/nhs-notify-templates-letter-preview-renderer": { + "resolved": "lambdas/letter-preview-renderer", + "link": true + }, "node_modules/nhs-notify-web-template-management-accessibility-test": { "resolved": "tests/accessibility", "link": true diff --git a/package.json b/package.json index ce1fcde82..7da29c9ad 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "lambdas/backend-client", "lambdas/download-authorizer", "lambdas/sftp-letters", + "lambdas/letter-preview-renderer", "packages/event-schemas", "tests/accessibility", "tests/contracts/provider", diff --git a/scripts/create_backend_sandbox.sh b/scripts/create_backend_sandbox.sh index f85082e6b..1f5b8994b 100755 --- a/scripts/create_backend_sandbox.sh +++ b/scripts/create_backend_sandbox.sh @@ -8,12 +8,15 @@ if [ $# -ne 1 ]; then exit 2 fi -ENVIRONMENT=$1 -COMPONENT="sandbox" -AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)" -AWS_REGION="eu-west-2" -PROJECT="nhs-notify" -GROUP="nhs-notify-template-management-dev" +export ENVIRONMENT=$1 +export COMPONENT="sbx" +export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)" +export AWS_REGION="eu-west-2" +export PROJECT="nhs-notify" +export ACTION="apply" +export GROUP="nhs-notify-template-management-dev" + + root_dir=$(git rev-parse --show-toplevel) terraform_dir=$root_dir/infrastructure/terraform @@ -29,7 +32,7 @@ cd $terraform_dir --component $COMPONENT \ --environment $ENVIRONMENT \ --group $GROUP \ - --action apply \ + --action $ACTION \ -- \ -var aws_account_id=$AWS_ACCOUNT_ID \ -var region=$AWS_REGION \ diff --git a/scripts/destroy_backend_sandbox.sh b/scripts/destroy_backend_sandbox.sh index d3a862d58..69504e151 100755 --- a/scripts/destroy_backend_sandbox.sh +++ b/scripts/destroy_backend_sandbox.sh @@ -25,7 +25,7 @@ cd $terraform_dir ./bin/terraform.sh \ --project $PROJECT \ --region $AWS_REGION \ - --component sandbox \ + --component sbx \ --environment $identifier \ --group $GROUP \ --action destroy \ diff --git a/scripts/lambda-container-build/docker.sh b/scripts/lambda-container-build/docker.sh new file mode 100755 index 000000000..b86a87493 --- /dev/null +++ b/scripts/lambda-container-build/docker.sh @@ -0,0 +1,129 @@ +#!/bin/bash + +# Fail fast on errors, unset variables, and pipeline failures. +set -euo pipefail + +# Ensure build.sh is executable and build the lambda artifacts before producing the Docker image. +chmod +x ./build.sh +./build.sh + + +# Parse arguments +BASE_IMAGE="" +while [[ $# -gt 0 ]]; do + case $1 in + --base-image) + BASE_IMAGE="$2" + shift 2 + ;; + *) + echo "Unknown argument: $1" >&2 + exit 1 + ;; + esac +done + +if [[ -z "$BASE_IMAGE" ]]; then + echo "Error: --base-image parameter is required." >&2 + exit 1 +fi + +CSI="${PROJECT}-${ENVIRONMENT}-${COMPONENT}" +ECR_REPO="${ECR_REPO:-nhs-notify-main-acct}" +GHCR_LOGIN_TOKEN="${GITHUB_TOKEN}" +GHCR_LOGIN_USER="${GITHUB_ACTOR}" +LAMBDA_NAME="${LAMBDA_NAME:-$(basename "$PWD")}" + +## Set IMAGE_TAG_SUFFIX based on git tag or short SHA for unique lambda image tagging in ECR. +#This ensures that each build produces a uniquely identifiable image, and tagged releases are easily traceable. +echo "Checking if current commit is a tag..." +GIT_TAG="$(git describe --tags --exact-match 2>/dev/null || true)" +if [ -n "$GIT_TAG" ]; then + TAGGED="tag-$GIT_TAG" + echo "On tag: $GIT_TAG, exporting IMAGE_TAG_SUFFIX as tag: $TAGGED" + export IMAGE_TAG_SUFFIX="$TAGGED" + +else + SHORT_SHA="sha-$(git rev-parse --short HEAD)" + echo "Not on a tag, exporting IMAGE_TAG_SUFFIX as short SHA: $SHORT_SHA" + export IMAGE_TAG_SUFFIX="$SHORT_SHA" +fi + +## Check if we are running in the context of a Terraform apply or plan, and set PUBLISH_LAMBDA_IMAGE accordingly. We only want to push images to ECR on apply, not on plan. +echo "Checking if ACTION is 'apply' to set PUBLISH_LAMBDA_IMAGE..." +if [ "$ACTION" = "apply" ]; then + echo "Setting PUBLISH_LAMBDA_IMAGE to true for apply action" + export PUBLISH_LAMBDA_IMAGE="true" +else + echo "Not setting PUBLISH_LAMBDA_IMAGE for action ($ACTION)" +fi + +# Ensure required AWS/ECR configuration is present. +echo "BASE_IMAGE: ${BASE_IMAGE:-}" +echo "AWS_ACCOUNT_ID: ${AWS_ACCOUNT_ID:-}" +echo "AWS_REGION: ${AWS_REGION:-}" +echo "COMPONENT: ${COMPONENT:-}" +echo "CSI: ${CSI:-}" +echo "ECR_REPO: ${ECR_REPO:-}" +echo "ENVIRONMENT: ${ENVIRONMENT:-}" +echo "GHCR_LOGIN_TOKEN: ${GHCR_LOGIN_TOKEN:-}" +echo "GHCR_LOGIN_USER: ${GHCR_LOGIN_USER:-}" +echo "IMAGE_TAG_SUFFIX: ${IMAGE_TAG_SUFFIX:-}" +echo "LAMBDA_NAME: ${LAMBDA_NAME:-}" + +# Authenticate Docker with AWS ECR using an ephemeral login token. +aws ecr get-login-password --region "${AWS_REGION}" | docker login --username AWS --password-stdin "${AWS_ACCOUNT_ID}".dkr.ecr."${AWS_REGION}".amazonaws.com + +# Authenticate to GitHub Container Registry for base images. +if [ -n "${GHCR_LOGIN_USER:-}" ] && [ -n "${GHCR_LOGIN_TOKEN:-}" ]; then + echo "Attempting GHCR login as ${GHCR_LOGIN_USER}..." + if echo "${GHCR_LOGIN_TOKEN}" | docker login ghcr.io --username "${GHCR_LOGIN_USER}" --password-stdin; then + echo "GHCR login successful." + else + echo "GHCR login failed!" >&2 + fi +fi + +# Namespace tag by CSI and lambda name to avoid cross-environment collisions. +IMAGE_TAG="${CSI}-${LAMBDA_NAME}" + +# Compose the full ECR image references. +ECR_REPO_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}" + +# Final tag names we will produce + +IMAGE_TAG_LATEST="${ECR_REPO_URI}:${IMAGE_TAG}-latest" +IMAGE_TAG_SUFFIXED="${ECR_REPO_URI}:${IMAGE_TAG}-${IMAGE_TAG_SUFFIX}" + +echo "Will build and tag images:" +echo " LATEST -> ${IMAGE_TAG_LATEST}" +echo " SUFFIXED -> ${IMAGE_TAG_SUFFIXED}" + +# Build and tag the Docker image for the lambda. +# --load makes the built image available to the local docker daemon (single-platform). +docker buildx build \ + -f docker/lambda/Dockerfile \ + --platform=linux/amd64 \ + --provenance=false \ + --sbom=false \ + --build-arg BASE_IMAGE="${BASE_IMAGE}" \ + -t "${IMAGE_TAG_LATEST}" \ + -t "${IMAGE_TAG_SUFFIXED}" \ + --load \ + . + +# Push the image tag(s) to ECR on apply only. The Terraform configuration will reference image digest. +if [ "${PUBLISH_LAMBDA_IMAGE:-false}" = "true" ]; then + echo "PUBLISH_LAMBDA_IMAGE is set to true. Pushing Docker images to ECR..." + + + for TAG in "${IMAGE_TAG_LATEST}" "${IMAGE_TAG_SUFFIXED}"; do + echo "Pushing ${TAG}..." + docker push "${TAG}" + done + + echo "Push complete." +else + echo "PUBLISH_LAMBDA_IMAGE is not set to true (likely TF Plan). Skipping Docker push." + exit 0 +fi