Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion .github/workflows/pr_destroy_dynamic_env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 2 additions & 2 deletions .github/workflows/stage-4-acceptance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
64 changes: 64 additions & 0 deletions infrastructure/terraform/components/acct/ecr_repository_main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <<EOF
{
"rules": [
{
"rulePriority": 1,
"description": "Archive commit images after 30 days (commit tags use suffix '-sha-')",
"selection": {
"tagStatus": "tagged",
"tagPatternList": ["*-sha-*"],
"countType": "sinceImagePushed",
"countNumber": 30
},
"action": {
"type": "transition",
"targetStorageClass": "archive"
}
},
{
"rulePriority": 2,
"description": "Expire (delete) commit images 60 days after push (only those matched as commit images)",
"selection": {
"tagStatus": "tagged",
"tagPatternList": ["*-sha-*"],
"countType": "sinceImagePushed",
"countNumber": 60
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 3,
"description": "Expire (delete) untagged images 7 days after push",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countNumber": 7
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 10,
"description": "Archive tagged images (semantic-version tags) after 90 days — do not expire them (no delete)",
"selection": {
"tagStatus": "tagged",
"tagPatternList": ["*-tag-*"],
"countType": "sinceImagePushed",
"countNumber": 90
},
"action": {
"type": "transition",
"targetStorageClass": "archive"
}
}
]
}
EOF
}
26 changes: 19 additions & 7 deletions infrastructure/terraform/components/app/pre.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
# pre.sh runs in the same shell as terraform.sh, not in a subshell
# any variables set or changed, any change of directory will persist once this script exits and returns control to terraform.sh
REGION=$1
ENVIRONMENT=$2
ACTION=$3

# Helper function for error handling
run_or_fail() {
"$@"
if [ $? -ne 0 ]; then
echo "$* failed!" >&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 -
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions infrastructure/terraform/modules/backend-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ No requirements.
| <a name="module_lambda_copy_scanned_object_to_internal"></a> [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 |
| <a name="module_lambda_delete_failed_scanned_object"></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 |
| <a name="module_lambda_event_publisher"></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 |
| <a name="module_lambda_letter_preview_renderer"></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 |
| <a name="module_lambda_process_proof"></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 |
| <a name="module_lambda_set_file_virus_scan_status_for_upload"></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 |
| <a name="module_lambda_sftp_poll"></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 |
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
1 change: 1 addition & 0 deletions lambdas/letter-preview-renderer/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
4 changes: 4 additions & 0 deletions lambdas/letter-preview-renderer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
coverage
node_modules
dist
.reports
17 changes: 17 additions & 0 deletions lambdas/letter-preview-renderer/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -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<Context>();
const callback = jest.fn();
const result = await handler(event, context, callback);

expect(result).toEqual({
statusCode: 200,
body: 'Event logged',
});
});
});
16 changes: 16 additions & 0 deletions lambdas/letter-preview-renderer/build.sh
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions lambdas/letter-preview-renderer/docker/lambda/Dockerfile
Original file line number Diff line number Diff line change
@@ -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" ]
10 changes: 10 additions & 0 deletions lambdas/letter-preview-renderer/index.ts
Original file line number Diff line number Diff line change
@@ -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',
};
};
60 changes: 60 additions & 0 deletions lambdas/letter-preview-renderer/jest.config.ts
Original file line number Diff line number Diff line change
@@ -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;
23 changes: 23 additions & 0 deletions lambdas/letter-preview-renderer/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
Loading
Loading