Skip to content

Commit 40b3c43

Browse files
CCM-8861: Use events for each supplier
1 parent b1f44e5 commit 40b3c43

File tree

14 files changed

+72
-335
lines changed

14 files changed

+72
-335
lines changed

infrastructure/terraform/modules/backend-api/cloudwatch_event_rule_sftp_poll.tf

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
resource "aws_cloudwatch_event_rule" "sftp_poll" {
2-
name = "${local.csi}-sftp-poll"
2+
for_each = { for k, v in var.letter_suppliers : k => v if v.polling_enabled }
3+
4+
name = "${local.csi}-sftp-poll-${lower(each.key)}"
35
schedule_expression = "rate(1 hour)" # Runs at the top of every hour
46
}
57

68
resource "aws_cloudwatch_event_target" "sftp_poll" {
9+
for_each = { for k, v in var.letter_suppliers : k => v if v.polling_enabled }
710
rule = aws_cloudwatch_event_rule.sftp_poll.name
811
arn = module.lambda_sftp_poll.function_arn
12+
13+
input = {
14+
supplier: each.key
15+
}
916
}
1017

1118
resource "aws_lambda_permission" "allow_cloudwatch" {
19+
for_each = { for k, v in var.letter_suppliers : k => v if v.polling_enabled }
1220
statement_id = "AllowExecutionFromCloudWatch"
1321
action = "lambda:InvokeFunction"
1422
function_name = module.lambda_sftp_poll.function_name

infrastructure/terraform/modules/backend-api/module_lambda_send_letter_proof.tf

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ module "lambda_send_letter_proof" {
1717
environment_variables = {
1818
CREDENTIALS_TTL_SECONDS = 900
1919
CSI = local.csi
20-
ENVIRONMENT = var.environment
21-
QUARANTINE_BUCKET_NAME = module.s3bucket_quarantine.id
2220
INTERNAL_BUCKET_NAME = module.s3bucket_internal.id
2321
NODE_OPTIONS = "--enable-source-maps",
2422
REGION = var.region

infrastructure/terraform/modules/backend-api/module_lambda_sftp_poll.tf

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,61 +14,41 @@ module "lambda_sftp_poll" {
1414
environment_variables = {
1515
CREDENTIALS_TTL_MS = 900 * 1000
1616
CSI = local.csi
17-
DEFAULT_LETTER_SUPPLIER = local.default_letter_supplier.name
18-
ENVIRONMENT = var.environment
1917
QUARANTINE_BUCKET_NAME = module.s3bucket_quarantine.id
20-
INTERNAL_BUCKET_NAME = module.s3bucket_internal.id
2118
NODE_OPTIONS = "--enable-source-maps",
2219
REGION = var.region
23-
SEND_LOCK_TTL_MS = 50 * 1000 // visibility timeout 60s
2420
SFTP_ENVIRONMENT = local.sftp_environment
25-
TEMPLATES_TABLE_NAME = aws_dynamodb_table.templates.name
2621
}
2722

2823
timeout = 20
2924
}
3025

3126
data "aws_iam_policy_document" "sftp_poll" {
32-
statement {
33-
sid = "AllowDynamoAccess"
34-
effect = "Allow"
35-
36-
actions = [
37-
"dynamodb:UpdateItem",
38-
]
39-
40-
resources = [
41-
aws_dynamodb_table.templates.arn,
42-
]
43-
}
4427

4528
statement {
4629
sid = "AllowS3"
4730
effect = "Allow"
4831

4932
actions = [
5033
"s3:PutObject",
51-
"s3:ListBucket",
5234
]
5335

54-
resources = [module.s3bucket_quarantine.arn, "${module.s3bucket_quarantine.arn}/*"]
36+
resources = ["${module.s3bucket_quarantine.arn}/*"]
5537
}
5638

5739
statement {
5840
sid = "AllowSSMParameterRead"
5941
effect = "Allow"
6042
actions = [
6143
"ssm:GetParameter",
62-
"ssm:GetParametersByPath",
6344
]
6445
resources = [
6546
"arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${local.csi}/sftp-config/*",
66-
"arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${local.csi}/sftp-config",
6747
]
6848
}
6949

7050
statement {
71-
sid = "AllowKMSDynamoAccess"
51+
sid = "AllowKMSAccess"
7252
effect = "Allow"
7353

7454
actions = [

lambdas/sftp-letters/src/__tests__/app/poll.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,12 @@ test('polls SFTP clients', async () => {
9292
});
9393

9494
const sftpSupplierClientRepository = mockDeep<SftpSupplierClientRepository>({
95-
listClients: async () => [
96-
{
97-
sftpClient,
98-
baseUploadDir: 'upload-dir',
99-
baseDownloadDir: 'download-dir',
100-
name: 'supplier',
101-
},
102-
],
95+
getClient: async () => ({
96+
sftpClient,
97+
baseUploadDir: 'upload-dir',
98+
baseDownloadDir: 'download-dir',
99+
name: 'supplier',
100+
}),
103101
});
104102

105103
const mockLogger = mockDeep<Logger>();
@@ -112,7 +110,7 @@ test('polls SFTP clients', async () => {
112110
'sftp-environment'
113111
);
114112

115-
await app.poll();
113+
await app.poll('supplier');
116114

117115
expect(s3Repository.putRawData).toHaveBeenCalledTimes(3);
118116
expect(s3Repository.putRawData).toHaveBeenCalledWith(

lambdas/sftp-letters/src/__tests__/infra/sftp-supplier-client-repository.test.ts

Lines changed: 4 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
2+
import { createMockLogger } from 'nhs-notify-web-template-management-test-helper-utils/mock-logger';
3+
import { ICache } from 'nhs-notify-web-template-management-utils';
4+
import { mock } from 'jest-mock-extended';
15
import 'aws-sdk-client-mock-jest';
26
import { mockClient } from 'aws-sdk-client-mock';
37
import { ZodError } from 'zod';
@@ -36,113 +40,6 @@ function setup() {
3640
return { mocks };
3741
}
3842

39-
describe('listClients', () => {
40-
test('returns client list', async () => {
41-
const {
42-
mocks: { logger, environment, ssmClient, cache },
43-
} = setup();
44-
45-
ssmClient.on(GetParametersByPathCommand).resolvesOnce({
46-
Parameters: [
47-
{
48-
Name: 'supplier-1',
49-
Value: JSON.stringify({
50-
host: 'testHost',
51-
username: 'testUser',
52-
privateKey: 'testKey',
53-
hostKey: 'hostKey',
54-
baseUploadDir: 'upload/dir',
55-
baseDownloadDir: 'download/dir',
56-
}),
57-
},
58-
{
59-
Name: 'supplier-2',
60-
Value: JSON.stringify({
61-
host: 'testHost',
62-
username: 'testUser',
63-
privateKey: 'testKey',
64-
hostKey: 'hostKey',
65-
baseUploadDir: 'upload/dir',
66-
baseDownloadDir: 'download/dir',
67-
}),
68-
},
69-
],
70-
});
71-
72-
const sftpClientRepository = new SftpSupplierClientRepository(
73-
environment,
74-
ssmClient as unknown as SSMClient,
75-
cache,
76-
logger
77-
);
78-
79-
const clients = await sftpClientRepository.listClients();
80-
81-
const mockSftpClients = jest.mocked(SftpClient).mock.instances;
82-
83-
expect(clients).toEqual([
84-
{
85-
sftpClient: mockSftpClients[0],
86-
baseUploadDir: 'upload/dir',
87-
baseDownloadDir: 'download/dir',
88-
name: 'supplier-1',
89-
},
90-
{
91-
sftpClient: mockSftpClients[1],
92-
baseUploadDir: 'upload/dir',
93-
baseDownloadDir: 'download/dir',
94-
name: 'supplier-2',
95-
},
96-
]);
97-
});
98-
99-
test('returns empty array if no parameters are found', async () => {
100-
const {
101-
mocks: { logger, environment, ssmClient, cache },
102-
} = setup();
103-
104-
ssmClient.on(GetParametersByPathCommand).resolvesOnce({
105-
Parameters: undefined,
106-
});
107-
108-
const sftpClientRepository = new SftpSupplierClientRepository(
109-
environment,
110-
ssmClient as unknown as SSMClient,
111-
cache,
112-
logger
113-
);
114-
115-
const clients = await sftpClientRepository.listClients();
116-
117-
expect(clients).toEqual([]);
118-
});
119-
120-
test('throws error if malformed SSM response is found', async () => {
121-
const {
122-
mocks: { logger, environment, ssmClient, cache },
123-
} = setup();
124-
125-
ssmClient.on(GetParametersByPathCommand).resolvesOnce({
126-
Parameters: [
127-
{
128-
Name: 'name',
129-
},
130-
],
131-
});
132-
133-
const sftpClientRepository = new SftpSupplierClientRepository(
134-
environment,
135-
ssmClient as unknown as SSMClient,
136-
cache,
137-
logger
138-
);
139-
140-
await expect(() => sftpClientRepository.listClients()).rejects.toThrow(
141-
'SFTP credentials for name are undefined'
142-
);
143-
});
144-
});
145-
14643
describe('getClient', () => {
14744
test('Returns client when credentials are not cached', async () => {
14845
const {
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import type { Container } from '../container-poll';
22

3+
type EventBridgeEvent = {
4+
event: {
5+
supplier: string;
6+
};
7+
};
8+
39
export const createHandler =
410
({ app }: Container) =>
5-
() =>
6-
app.poll();
11+
({ event: { supplier } }: EventBridgeEvent) =>
12+
app.poll(supplier);

lambdas/sftp-letters/src/app/poll.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,26 +91,25 @@ export class App {
9191
}
9292
}
9393

94-
async poll() {
95-
const sftpConfigs = await this.sftpSupplierClientRepository.listClients();
94+
async poll(supplier: string) {
95+
const { sftpClient, baseDownloadDir } =
96+
await this.sftpSupplierClientRepository.getClient(supplier);
9697

97-
for (const { sftpClient, baseDownloadDir, name } of sftpConfigs) {
98-
this.logger.info('Polling SFTP');
99-
await sftpClient.connect();
98+
this.logger.info('Polling SFTP');
99+
await sftpClient.connect();
100100

101-
this.logger.info(
102-
`Copying files from folder ${baseDownloadDir} to proofs/${name}`
103-
);
101+
this.logger.info(
102+
`Copying files from folder ${baseDownloadDir}/${this.sftpEnvironment}/proofs to proofs`
103+
);
104104

105-
await this.copyFolder(
106-
sftpClient,
107-
`${baseDownloadDir}/${this.sftpEnvironment}/proofs`,
108-
'proofs'
109-
);
105+
await this.copyFolder(
106+
sftpClient,
107+
`${baseDownloadDir}/${this.sftpEnvironment}/proofs`,
108+
'proofs'
109+
);
110110

111-
await sftpClient.end();
111+
await sftpClient.end();
112112

113-
this.logger.info('Finished polling SFTP');
114-
}
113+
this.logger.info('Finished polling SFTP');
115114
}
116115
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import z from 'zod';
2+
3+
export function loadConfig() {
4+
return z
5+
.object({
6+
CREDENTIALS_TTL_MS: z.string().pipe(z.coerce.number()),
7+
CSI: z.string(),
8+
QUARANTINE_BUCKET_NAME: z.string(),
9+
REGION: z.string(),
10+
SFTP_ENVIRONMENT: z.string(),
11+
})
12+
.transform((e) => ({
13+
credentialsTtlMs: e.CREDENTIALS_TTL_MS,
14+
csi: e.CSI,
15+
quarantineBucketName: e.QUARANTINE_BUCKET_NAME,
16+
region: e.REGION,
17+
sftpEnvironment: e.SFTP_ENVIRONMENT,
18+
}))
19+
.parse(process.env);
20+
}
21+
22+
export type Config = ReturnType<typeof loadConfig>;

lambdas/sftp-letters/src/config/config.ts renamed to lambdas/sftp-letters/src/config/config-send.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ export function loadConfig() {
2222
}))
2323
.parse(process.env);
2424
}
25+
26+
export type Config = ReturnType<typeof loadConfig>;

lambdas/sftp-letters/src/container-poll.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { S3Client } from '@aws-sdk/client-s3';
22
import { SSMClient } from '@aws-sdk/client-ssm';
33
import { SftpSupplierClientRepository } from './infra/sftp-supplier-client-repository';
4-
import { loadConfig } from './config/config';
4+
import { loadConfig } from './config/config-poll';
55
import { App } from './app/poll';
66
import { logger } from 'nhs-notify-web-template-management-utils/logger';
77
import {

0 commit comments

Comments
 (0)