Skip to content

Commit 018c959

Browse files
Merge branch 'main' into feature/CCM-13116-AddTests
2 parents 06f1438 + 58e95bc commit 018c959

23 files changed

Lines changed: 364 additions & 94 deletions

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
- [ ] I have added tests to cover my changes
2626
- [ ] I have updated the documentation accordingly
2727
- [ ] This PR is a result of pair or mob programming
28-
- [ ] If I have used the 'skip-trivy-package' label I have done so responsibly and in the knowledge that this is being fixed as part of a separate ticket/PR.
28+
<!-- - [ ] If I have used the 'skip-trivy-package' label I have done so responsibly and in the knowledge that this is being fixed as part of a separate ticket/PR. TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549 -->
2929

3030
---
3131

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
name: "Trivy IaC Scan"
2-
description: "Scan Terraform IaC using Trivy"
3-
runs:
4-
using: "composite"
5-
steps:
6-
- name: "Trivy Terraform IaC Scan"
7-
shell: bash
8-
run: |
9-
components_exit_code=0
10-
modules_exit_code=0
11-
asdf plugin add trivy || true
12-
asdf install trivy || true
13-
./scripts/terraform/trivy-scan.sh --mode iac ./infrastructure/terraform/components || components_exit_code=$?
14-
./scripts/terraform/trivy-scan.sh --mode iac ./infrastructure/terraform/modules || modules_exit_code=$?
1+
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
2+
# name: "Trivy IaC Scan"
3+
# description: "Scan Terraform IaC using Trivy"
4+
# runs:
5+
# using: "composite"
6+
# steps:
7+
# - name: "Trivy Terraform IaC Scan"
8+
# shell: bash
9+
# run: |
10+
# components_exit_code=0
11+
# modules_exit_code=0
12+
# asdf plugin add trivy || true
13+
# asdf install trivy || true
14+
# ./scripts/terraform/trivy-scan.sh --mode iac ./infrastructure/terraform/components || components_exit_code=$?
15+
# ./scripts/terraform/trivy-scan.sh --mode iac ./infrastructure/terraform/modules || modules_exit_code=$?
1516

16-
if [ $components_exit_code -ne 0 ] || [ $modules_exit_code -ne 0 ]; then
17-
echo "Trivy misconfigurations detected."
18-
exit 1
19-
fi
17+
# if [ $components_exit_code -ne 0 ] || [ $modules_exit_code -ne 0 ]; then
18+
# echo "Trivy misconfigurations detected."
19+
# exit 1
20+
# fi
Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
name: "Trivy Package Scan"
2-
description: "Scan project packages using Trivy"
3-
runs:
4-
using: "composite"
5-
steps:
6-
- name: "Trivy Package Scan"
7-
shell: bash
8-
run: |
9-
exit_code=0
10-
asdf plugin add trivy || true
11-
asdf install trivy || true
12-
./scripts/terraform/trivy-scan.sh --mode package . || exit_code=$?
1+
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
2+
# name: "Trivy Package Scan"
3+
# description: "Scan project packages using Trivy"
4+
# runs:
5+
# using: "composite"
6+
# steps:
7+
# - name: "Trivy Package Scan"
8+
# shell: bash
9+
# run: |
10+
# exit_code=0
11+
# asdf plugin add trivy || true
12+
# asdf install trivy || true
13+
# ./scripts/terraform/trivy-scan.sh --mode package . || exit_code=$?
1314

14-
if [ $exit_code -ne 0 ]; then
15-
echo "Trivy has detected package vulnerablilites. Please refer to https://nhsd-confluence.digital.nhs.uk/spaces/RIS/pages/1257636917/PLAT-KOP-012+-+Trivy+Pipeline+Vulnerability+Scanning+Exemption"
16-
exit 1
17-
fi
15+
# if [ $exit_code -ne 0 ]; then
16+
# echo "Trivy has detected package vulnerablilites. Please refer to https://nhsd-confluence.digital.nhs.uk/spaces/RIS/pages/1257636917/PLAT-KOP-012+-+Trivy+Pipeline+Vulnerability+Scanning+Exemption"
17+
# exit 1
18+
# fi

.github/workflows/cicd-1-pull-request.yaml

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ jobs:
2828
is_version_prerelease: ${{ steps.variables.outputs.is_version_prerelease }}
2929
does_pull_request_exist: ${{ steps.pr_exists.outputs.does_pull_request_exist }}
3030
pr_number: ${{ steps.pr_exists.outputs.pr_number }}
31-
skip_trivy_package: ${{ steps.skip_trivy.outputs.skip_trivy_package }}
31+
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
32+
# skip_trivy_package: ${{ steps.skip_trivy.outputs.skip_trivy_package }}
3233
deploy_proxy: ${{ steps.deploy_proxy.outputs.deploy_proxy }}
3334
steps:
3435
- name: "Checkout code"
@@ -68,26 +69,27 @@ jobs:
6869
echo "does_pull_request_exist=false" >> $GITHUB_OUTPUT
6970
echo "pr_number=" >> $GITHUB_OUTPUT
7071
fi
71-
- name: "Determine if Trivy package scan should be skipped"
72-
id: skip_trivy
73-
env:
74-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75-
PR_NUMBER: ${{ steps.pr_exists.outputs.pr_number }}
76-
run: |
77-
if [[ -z "$PR_NUMBER" ]]; then
78-
echo "No pull request detected; Trivy package scan will run."
79-
echo "skip_trivy_package=false" >> $GITHUB_OUTPUT
80-
exit 0
81-
fi
72+
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
73+
# - name: "Determine if Trivy package scan should be skipped"
74+
# id: skip_trivy
75+
# env:
76+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77+
# PR_NUMBER: ${{ steps.pr_exists.outputs.pr_number }}
78+
# run: |
79+
# if [[ -z "$PR_NUMBER" ]]; then
80+
# echo "No pull request detected; Trivy package scan will run."
81+
# echo "skip_trivy_package=false" >> $GITHUB_OUTPUT
82+
# exit 0
83+
# fi
8284

83-
labels=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name')
84-
echo "Labels on PR #$PR_NUMBER: $labels"
85+
# labels=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name')
86+
# echo "Labels on PR #$PR_NUMBER: $labels"
8587

86-
if echo "$labels" | grep -Fxq 'skip-trivy-package'; then
87-
echo "skip_trivy_package=true" >> $GITHUB_OUTPUT
88-
else
89-
echo "skip_trivy_package=false" >> $GITHUB_OUTPUT
90-
fi
88+
# if echo "$labels" | grep -Fxq 'skip-trivy-package'; then
89+
# echo "skip_trivy_package=true" >> $GITHUB_OUTPUT
90+
# else
91+
# echo "skip_trivy_package=false" >> $GITHUB_OUTPUT
92+
# fi
9193
- name: "Determine if proxy should be deployed"
9294
id: deploy_proxy
9395
env:
@@ -131,7 +133,8 @@ jobs:
131133
build_epoch: "${{ needs.metadata.outputs.build_epoch }}"
132134
nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}"
133135
python_version: "${{ needs.metadata.outputs.python_version }}"
134-
skip_trivy_package: ${{ needs.metadata.outputs.skip_trivy_package == 'true' }}
136+
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
137+
# skip_trivy_package: ${{ needs.metadata.outputs.skip_trivy_package == 'true' }}
135138
terraform_version: "${{ needs.metadata.outputs.terraform_version }}"
136139
version: "${{ needs.metadata.outputs.version }}"
137140
secrets: inherit

.github/workflows/stage-1-commit.yaml

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ on:
2626
description: "Python version, set by the CI/CD pipeline workflow"
2727
required: true
2828
type: string
29-
skip_trivy_package:
30-
description: "Skip Trivy package scan when true"
31-
type: boolean
32-
default: false
29+
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
30+
# skip_trivy_package:
31+
# description: "Skip Trivy package scan when true"
32+
# type: boolean
33+
# default: false
3334
terraform_version:
3435
description: "Terraform version, set by the CI/CD pipeline workflow"
3536
required: true
@@ -155,6 +156,40 @@ jobs:
155156
uses: asdf-vm/actions/setup@1902764435ca0dd2f3388eea723a4f92a4eb8302
156157
- name: "Lint Terraform"
157158
uses: ./.github/actions/lint-terraform
159+
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
160+
# trivy-iac:
161+
# name: "Trivy IaC Scan"
162+
# permissions:
163+
# contents: read
164+
# packages: read
165+
# runs-on: ubuntu-latest
166+
# timeout-minutes: 10
167+
# needs: detect-terraform-changes
168+
# if: needs.detect-terraform-changes.outputs.terraform_changed == 'true'
169+
# env:
170+
# NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
171+
# steps:
172+
# - name: "Checkout code"
173+
# uses: actions/checkout@v4
174+
# - name: "Setup ASDF"
175+
# uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47
176+
# - name: "Trivy IaC Scan"
177+
# uses: ./.github/actions/trivy-iac
178+
# trivy-package:
179+
# if: ${{ !inputs.skip_trivy_package }}
180+
# name: "Trivy Package Scan"
181+
# permissions:
182+
# contents: read
183+
# packages: read
184+
# runs-on: ubuntu-latest
185+
# timeout-minutes: 10
186+
# steps:
187+
# - name: "Checkout code"
188+
# uses: actions/checkout@v4
189+
# - name: "Setup ASDF"
190+
# uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47
191+
# - name: "Trivy Package Scan"
192+
# uses: ./.github/actions/trivy-package
158193
count-lines-of-code:
159194
name: "Count lines of code"
160195
runs-on: ubuntu-latest

infrastructure/terraform/components/api/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ No requirements.
3535
| <a name="input_kms_deletion_window"></a> [kms\_deletion\_window](#input\_kms\_deletion\_window) | When a kms key is deleted, how long should it wait in the pending deletion state? | `string` | `"30"` | no |
3636
| <a name="input_letter_event_source"></a> [letter\_event\_source](#input\_letter\_event\_source) | Source value to use for the letter status event updates | `string` | `"/data-plane/supplier-api/nhs-supplier-api-prod/main/update-status"` | no |
3737
| <a name="input_letter_table_ttl_hours"></a> [letter\_table\_ttl\_hours](#input\_letter\_table\_ttl\_hours) | Number of hours to set as TTL on letters table | `number` | `24` | no |
38-
| <a name="input_letter_variant_map"></a> [letter\_variant\_map](#input\_letter\_variant\_map) | n/a | `map(object({ supplierId = string, specId = string }))` | <pre>{<br/> "lv1": {<br/> "specId": "spec1",<br/> "supplierId": "supplier1"<br/> },<br/> "lv2": {<br/> "specId": "spec2",<br/> "supplierId": "supplier1"<br/> },<br/> "lv3": {<br/> "specId": "spec3",<br/> "supplierId": "supplier2"<br/> }<br/>}</pre> | no |
38+
| <a name="input_letter_variant_map"></a> [letter\_variant\_map](#input\_letter\_variant\_map) | n/a | `map(object({ supplierId = string, specId = string, priority = number }))` | <pre>{<br/> "lv1": {<br/> "priority": 10,<br/> "specId": "spec1",<br/> "supplierId": "supplier1"<br/> },<br/> "lv2": {<br/> "priority": 10,<br/> "specId": "spec2",<br/> "supplierId": "supplier1"<br/> },<br/> "lv3": {<br/> "priority": 10,<br/> "specId": "spec3",<br/> "supplierId": "supplier2"<br/> }<br/>}</pre> | no |
3939
| <a name="input_log_level"></a> [log\_level](#input\_log\_level) | The log level to be used in lambda functions within the component. Any log with a lower severity than the configured value will not be logged: https://docs.python.org/3/library/logging.html#levels | `string` | `"INFO"` | no |
4040
| <a name="input_log_retention_in_days"></a> [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 |
4141
| <a name="input_manually_configure_mtls_truststore"></a> [manually\_configure\_mtls\_truststore](#input\_manually\_configure\_mtls\_truststore) | Manually manage the truststore used for API Gateway mTLS (e.g. for prod environment) | `bool` | `false` | no |

infrastructure/terraform/components/api/ddb_table_letter_queue.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ resource "aws_dynamodb_table" "letter_queue" {
1212

1313
local_secondary_index {
1414
name = "queueSortOrder-index"
15-
range_key = "queueTimestamp"
15+
range_key = "queueSortOrderSk"
1616
projection_type = "ALL"
1717
}
1818

@@ -27,7 +27,7 @@ resource "aws_dynamodb_table" "letter_queue" {
2727
}
2828

2929
attribute {
30-
name = "queueTimestamp"
30+
name = "queueSortOrderSk"
3131
type = "S"
3232
}
3333

infrastructure/terraform/components/api/variables.tf

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,11 @@ variable "eventpub_control_plane_bus_arn" {
136136
}
137137

138138
variable "letter_variant_map" {
139-
type = map(object({ supplierId = string, specId = string }))
139+
type = map(object({ supplierId = string, specId = string, priority = number }))
140140
default = {
141-
"lv1" = { supplierId = "supplier1", specId = "spec1" },
142-
"lv2" = { supplierId = "supplier1", specId = "spec2" },
143-
"lv3" = { supplierId = "supplier2", specId = "spec3" }
141+
"lv1" = { supplierId = "supplier1", specId = "spec1", priority = 10 },
142+
"lv2" = { supplierId = "supplier1", specId = "spec2", priority = 10 },
143+
"lv3" = { supplierId = "supplier2", specId = "spec3", priority = 10 }
144144
}
145145
}
146146

internal/datastore/src/__test__/db.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export async function setupDynamoDBContainer() {
2222
accessKeyId: "fakeMyKeyId",
2323
secretAccessKey: "fakeSecretAccessKey",
2424
},
25+
maxAttempts: 1,
2526
});
2627

2728
const docClient = DynamoDBDocumentClient.from(ddbClient);
@@ -132,7 +133,7 @@ const createLetterQueueTableCommand = new CreateTableCommand({
132133
IndexName: "queueSortOrder-index",
133134
KeySchema: [
134135
{ AttributeName: "supplierId", KeyType: "HASH" }, // Partition key for LSI
135-
{ AttributeName: "queueTimestamp", KeyType: "RANGE" }, // Sort key for LSI
136+
{ AttributeName: "queueSortOrderSk", KeyType: "RANGE" }, // Sort key for LSI
136137
],
137138
Projection: {
138139
ProjectionType: "ALL",
@@ -142,7 +143,7 @@ const createLetterQueueTableCommand = new CreateTableCommand({
142143
AttributeDefinitions: [
143144
{ AttributeName: "supplierId", AttributeType: "S" },
144145
{ AttributeName: "letterId", AttributeType: "S" },
145-
{ AttributeName: "queueTimestamp", AttributeType: "S" },
146+
{ AttributeName: "queueSortOrderSk", AttributeType: "S" },
146147
],
147148
});
148149

internal/datastore/src/__test__/letter-queue-repository.test.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ import { LetterAlreadyExistsError } from "../letter-already-exists-error";
1212
import { createTestLogger } from "./logs";
1313
import { LetterDoesNotExistError } from "../letter-does-not-exist-error";
1414

15-
function createLetter(letterId = "letter1"): InsertPendingLetter {
15+
function createLetter(
16+
overrides: Partial<InsertPendingLetter> = {},
17+
): InsertPendingLetter {
1618
return {
17-
letterId,
19+
letterId: "letter1",
1820
supplierId: "supplier1",
1921
specificationId: "specification1",
2022
groupId: "group1",
23+
priority: 10,
24+
...overrides,
2125
};
2226
}
2327

@@ -54,9 +58,11 @@ describe("LetterQueueRepository", () => {
5458
});
5559

5660
describe("putLetter", () => {
57-
it("adds a letter to the database", async () => {
61+
beforeEach(() => {
5862
jest.useFakeTimers().setSystemTime(new Date("2026-03-04T13:15:45.000Z"));
63+
});
5964

65+
it("adds a letter to the database", async () => {
6066
const pendingLetter =
6167
await letterQueueRepository.putLetter(createLetter());
6268

@@ -65,9 +71,32 @@ describe("LetterQueueRepository", () => {
6571
"2026-03-04T13:15:45.000Z",
6672
);
6773
expect(pendingLetter.ttl).toBe(1_772_633_745);
74+
expect(pendingLetter.queueSortOrderSk).toBe(
75+
"10-2026-03-04T13:15:45.000Z",
76+
);
6877
expect(await letterExists(db, "supplier1", "letter1")).toBe(true);
6978
});
7079

80+
it("left-pads the priority with zeros in the sort key", async () => {
81+
const pendingLetter = await letterQueueRepository.putLetter(
82+
createLetter({ priority: 5 }),
83+
);
84+
85+
expect(pendingLetter.queueSortOrderSk).toBe(
86+
"05-2026-03-04T13:15:45.000Z",
87+
);
88+
});
89+
90+
it("defaults a missing priority to 10 in the sort key", async () => {
91+
const pendingLetter = await letterQueueRepository.putLetter(
92+
createLetter({ priority: undefined }),
93+
);
94+
95+
expect(pendingLetter.queueSortOrderSk).toBe(
96+
"10-2026-03-04T13:15:45.000Z",
97+
);
98+
});
99+
71100
it("throws LetterAlreadyExistsError when creating a letter which already exists", async () => {
72101
await letterQueueRepository.putLetter(createLetter());
73102

@@ -122,16 +151,21 @@ describe("LetterQueueRepository", () => {
122151
});
123152
});
124153

125-
async function letterExists(
126-
db: DBContext,
127-
supplierId: string,
128-
letterId: string,
129-
): Promise<boolean> {
154+
async function getLetter(db: DBContext, supplierId: string, letterId: string) {
130155
const result = await db.docClient.send(
131156
new GetCommand({
132157
TableName: db.config.letterQueueTableName,
133158
Key: { supplierId, letterId },
134159
}),
135160
);
136-
return result.Item !== undefined;
161+
return result.Item;
162+
}
163+
164+
async function letterExists(
165+
db: DBContext,
166+
supplierId: string,
167+
letterId: string,
168+
): Promise<boolean> {
169+
const letter = await getLetter(db, supplierId, letterId);
170+
return letter !== undefined;
137171
}

0 commit comments

Comments
 (0)