@defra-fish/fulfilment-job 1.55.0 → 1.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,21 +19,16 @@ provider.
19
19
 
20
20
  # Environment variables
21
21
 
22
- | name | description | required | default | valid | notes |
23
- | ----------------------------------- | ----------------------------------------------------------------------------------------- | :------: | ------- | ----------------------------------------------------------------------- | ----- |
24
- | NODE_ENV | Node environment | no | | development, test, production | |
25
- | FULFILMENT_FILE_SIZE | The maximum number of records written to an aggregated fulfilment file | yes | | | |
26
- | FULFILMENT_FTP_HOST | The hostname of the target FTP server | yes | | | |
27
- | FULFILMENT_FTP_PORT | The port of the FTP service on the target server | yes | | | |
28
- | FULFILMENT_FTP_PATH | The base path under which files should be written to the FTP server | yes | | | |
29
- | FULFILMENT_FTP_USERNAME | The username used to authenticate with the FTP server | yes | | | |
30
- | FULFILMENT_FTP_KEY_SECRET_ID | The ID of the secret in AWS secrets manager which contains the SSH key for authentication | yes | | | |
31
- | FULFILMENT_S3_BUCKET | The name of the AWS S3 bucket in which to stage and aggregate fulfilment data | yes | | | |
32
- | FULFILMENT_SEND_UNENCRYPTED_FILE | Flag for whether to send the unencrypted fulfilment file | no | false | true, false, 0, 1 | |
33
- | FULFILMENT_PGP_PUBLIC_KEY_SECRET_ID | The secret id for the file encryption public key | yes | | | |
34
- | DEBUG | Use to enable output of debug information to the console | yes | | fulfilment:\*, fulfilment:staging, fulfilment:transport, fulfilment:ftp | |
35
- | AIRBRAKE_HOST | URL of airbrake host | no | | | |
36
- | AIRBRAKE_PROJECT_KEY | Project key for airbrake logging | no | | | |
22
+ | name | description | required | default | valid | notes |
23
+ | ----------------------------------- | ----------------------------------------------------------------------------- | :------: | ------- | ----------------------------------------------------------------------- | ----- |
24
+ | NODE_ENV | Node environment | no | | development, test, production | |
25
+ | FULFILMENT_FILE_SIZE | The maximum number of records written to an aggregated fulfilment file | yes | | | |
26
+ | FULFILMENT_S3_BUCKET | The name of the AWS S3 bucket in which to stage and aggregate fulfilment data | yes | | | |
27
+ | FULFILMENT_SEND_UNENCRYPTED_FILE | Flag for whether to send the unencrypted fulfilment file | no | false | true, false, 0, 1 | |
28
+ | FULFILMENT_PGP_PUBLIC_KEY_SECRET_ID | The secret id for the file encryption public key | yes | | | |
29
+ | DEBUG | Use to enable output of debug information to the console | yes | | fulfilment:\*, fulfilment:staging, fulfilment:transport, fulfilment:ftp | |
30
+ | AIRBRAKE_HOST | URL of airbrake host | no | | | |
31
+ | AIRBRAKE_PROJECT_KEY | Project key for airbrake logging | no | | | |
37
32
 
38
33
  ### See also:
39
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/fulfilment-job",
3
- "version": "1.55.0",
3
+ "version": "1.56.0",
4
4
  "description": "Rod Licensing Sales Fulfilment Job",
5
5
  "type": "module",
6
6
  "engines": {
@@ -35,15 +35,14 @@
35
35
  "test": "echo \"Error: run tests from root\" && exit 1"
36
36
  },
37
37
  "dependencies": {
38
- "@defra-fish/connectors-lib": "1.55.0",
39
- "@defra-fish/dynamics-lib": "1.55.0",
38
+ "@defra-fish/connectors-lib": "1.56.0",
39
+ "@defra-fish/dynamics-lib": "1.56.0",
40
40
  "commander": "^7.2.0",
41
41
  "debug": "^4.3.3",
42
42
  "merge2": "^1.4.1",
43
43
  "moment": "^2.29.1",
44
44
  "openpgp": "^5.0.0-1",
45
- "pluralize": "^8.0.0",
46
- "ssh2-sftp-client": "^6.0.1"
45
+ "pluralize": "^8.0.0"
47
46
  },
48
- "gitHead": "3405322b81a2016ba22e530de8d096a49be0bea4"
47
+ "gitHead": "4edcdd349b077ad0bdb3e7b4029df3aba7c04b49"
49
48
  }
@@ -17,11 +17,6 @@ const clearEnvVars = () => {
17
17
 
18
18
  const envVars = Object.freeze({
19
19
  FULFILMENT_FILE_SIZE: 1234,
20
- FULFILMENT_FTP_HOST: 'test-host',
21
- FULFILMENT_FTP_PORT: 2222,
22
- FULFILMENT_FTP_PATH: '/remote/share',
23
- FULFILMENT_FTP_USERNAME: 'test-user',
24
- FULFILMENT_FTP_KEY_SECRET_ID: 'test-secret-id',
25
20
  FULFILMENT_S3_BUCKET: 'test-bucket',
26
21
  FULFILMENT_PGP_PUBLIC_KEY_SECRET_ID: 'pgp-key-secret-id',
27
22
  FULFILMENT_SEND_UNENCRYPTED_FILE: 'false'
@@ -44,32 +39,6 @@ describe('config', () => {
44
39
  })
45
40
  })
46
41
 
47
- describe('ftp', () => {
48
- it('provides properties relating the use of SFTP', async () => {
49
- expect(config.ftp).toEqual(
50
- expect.objectContaining({
51
- host: 'test-host',
52
- port: '2222',
53
- path: '/remote/share',
54
- username: 'test-user',
55
- privateKey: 'test-ssh-key',
56
- algorithms: { cipher: expect.any(Array), kex: expect.any(Array) },
57
- // Wait up to 60 seconds for the SSH handshake
58
- readyTimeout: expect.any(Number),
59
- // Retry 5 times over a minute
60
- retries: expect.any(Number),
61
- retry_minTimeout: expect.any(Number),
62
- debug: expect.any(Function)
63
- })
64
- )
65
- })
66
- it('defaults the sftp port to 22 if the environment variable is not configured', async () => {
67
- delete process.env.FULFILMENT_FTP_PORT
68
- await config.initialise()
69
- expect(config.ftp.port).toEqual('22')
70
- })
71
- })
72
-
73
42
  describe('s3', () => {
74
43
  it('provides properties relating the use of Amazon S3', async () => {
75
44
  expect(config.s3.bucket).toEqual('test-bucket')
@@ -79,7 +48,7 @@ describe('config', () => {
79
48
 
80
49
  describe('pgp config', () => {
81
50
  const init = async (samplePublicKey = 'sample-pgp-key') => {
82
- AwsMock.SecretsManager.__setNextResponses('getSecretValue', { SecretString: 'test-ssh-key' }, { SecretString: samplePublicKey })
51
+ AwsMock.SecretsManager.__setNextResponses('getSecretValue', { SecretString: samplePublicKey })
83
52
  await config.initialise()
84
53
  }
85
54
  beforeAll(setEnvVars)
package/src/config.js CHANGED
@@ -1,45 +1,6 @@
1
1
  import { AWS } from '@defra-fish/connectors-lib'
2
- import db from 'debug'
3
- const { secretsManager } = AWS()
4
2
 
5
- /**
6
- * Key exchange algorithms for public key authentication - in descending order of priority
7
- * @type {string[]}
8
- */
9
- export const SFTP_KEY_EXCHANGE_ALGORITHMS = [
10
- 'curve25519-sha256@libssh.org',
11
- 'curve25519-sha256',
12
- 'ecdh-sha2-nistp521',
13
- 'ecdh-sha2-nistp384',
14
- 'ecdh-sha2-nistp256',
15
- 'diffie-hellman-group-exchange-sha256',
16
- 'diffie-hellman-group14-sha256',
17
- 'diffie-hellman-group16-sha512',
18
- 'diffie-hellman-group18-sha512',
19
- 'diffie-hellman-group14-sha1',
20
- 'diffie-hellman-group-exchange-sha1',
21
- 'diffie-hellman-group1-sha1'
22
- ]
23
- /**
24
- * Ciphers for SFTP support - in descending order of priority
25
- * @type {string[]}
26
- */
27
- export const SFTP_CIPHERS = [
28
- // http://tools.ietf.org/html/rfc4344#section-4
29
- 'aes256-ctr',
30
- 'aes192-ctr',
31
- 'aes128-ctr',
32
- 'aes256-gcm',
33
- 'aes256-gcm@openssh.com',
34
- 'aes128-gcm',
35
- 'aes128-gcm@openssh.com',
36
- 'aes256-cbc',
37
- 'aes192-cbc',
38
- 'aes128-cbc',
39
- 'blowfish-cbc',
40
- '3des-cbc',
41
- 'cast128-cbc'
42
- ]
3
+ const { secretsManager } = AWS()
43
4
  const falseRegEx = /(false|0)/i
44
5
  const trueRegEx = /(true|1)/i
45
6
  const toBoolean = val => {
@@ -54,7 +15,6 @@ const toBoolean = val => {
54
15
 
55
16
  class Config {
56
17
  _file
57
- _ftp
58
18
  _s3
59
19
  _pgp
60
20
 
@@ -68,20 +28,6 @@ class Config {
68
28
  */
69
29
  partFileSize: Math.min(Number.parseInt(process.env.FULFILMENT_FILE_SIZE), 999)
70
30
  }
71
- this.ftp = {
72
- host: process.env.FULFILMENT_FTP_HOST,
73
- port: process.env.FULFILMENT_FTP_PORT || '22',
74
- path: process.env.FULFILMENT_FTP_PATH,
75
- username: process.env.FULFILMENT_FTP_USERNAME,
76
- privateKey: (await secretsManager.getSecretValue({ SecretId: process.env.FULFILMENT_FTP_KEY_SECRET_ID }).promise()).SecretString,
77
- algorithms: { cipher: SFTP_CIPHERS, kex: SFTP_KEY_EXCHANGE_ALGORITHMS },
78
- // Wait up to 60 seconds for the SSH handshake
79
- readyTimeout: 60000,
80
- // Retry 5 times over a minute
81
- retries: 5,
82
- retry_minTimeout: 12000,
83
- debug: db('fulfilment:ftp')
84
- }
85
31
  this.s3 = {
86
32
  bucket: process.env.FULFILMENT_S3_BUCKET
87
33
  }
@@ -104,18 +50,6 @@ class Config {
104
50
  this._file = cfg
105
51
  }
106
52
 
107
- /**
108
- * FTP configuration settings
109
- * @type {object}
110
- */
111
- get ftp () {
112
- return this._ftp
113
- }
114
-
115
- set ftp (cfg) {
116
- this._ftp = cfg
117
- }
118
-
119
53
  /**
120
54
  * S3 configuration settings
121
55
  * @type {object}
@@ -1,7 +1,6 @@
1
1
  import { Readable, PassThrough, Writable } from 'stream'
2
2
  import { deliverFulfilmentFiles } from '../deliver-fulfilment-files.js'
3
3
  import { createS3WriteStream, readS3PartFiles } from '../../transport/s3.js'
4
- import { createFtpWriteStream } from '../../transport/ftp.js'
5
4
  import { FULFILMENT_FILE_STATUS_OPTIONSET, getOptionSetEntry } from '../staging-common.js'
6
5
  import { FulfilmentRequestFile, executeQuery, persist } from '@defra-fish/dynamics-lib'
7
6
  import openpgp from 'openpgp'
@@ -10,7 +9,6 @@ import streamHelper from '../streamHelper.js'
10
9
  import merge2 from 'merge2'
11
10
 
12
11
  jest.mock('../../transport/s3.js')
13
- jest.mock('../../transport/ftp.js')
14
12
  jest.mock('openpgp', () => ({
15
13
  readKey: jest.fn(() => ({})),
16
14
  encrypt: jest.fn(({ message: readableStream }) => readableStream),
@@ -46,20 +44,10 @@ describe('deliverFulfilmentFiles', () => {
46
44
  executeQuery.mockResolvedValue([{ entity: mockFulfilmentRequestFile2 }, { entity: mockFulfilmentRequestFile1 }])
47
45
 
48
46
  // Streams for file1
49
- const {
50
- s3DataStreamFile: s3DataStreamFile1,
51
- ftpDataStreamFile: ftpDataStreamFile1,
52
- s3HashStreamFile: s3HashStreamFile1,
53
- ftpHashStreamFile: ftpHashStreamFile1
54
- } = createMockFileStreams()
47
+ const { s3DataStreamFile: s3DataStreamFile1, s3HashStreamFile: s3HashStreamFile1 } = createMockFileStreams()
55
48
 
56
49
  // Streams for file2
57
- const {
58
- s3DataStreamFile: s3DataStreamFile2,
59
- ftpDataStreamFile: ftpDataStreamFile2,
60
- s3HashStreamFile: s3HashStreamFile2,
61
- ftpHashStreamFile: ftpHashStreamFile2
62
- } = createMockFileStreams()
50
+ const { s3DataStreamFile: s3DataStreamFile2, s3HashStreamFile: s3HashStreamFile2 } = createMockFileStreams()
63
51
 
64
52
  // Run the delivery
65
53
  await expect(deliverFulfilmentFiles()).resolves.toBeUndefined()
@@ -69,22 +57,14 @@ describe('deliverFulfilmentFiles', () => {
69
57
  // File 1 expectations
70
58
  expect(createS3WriteStream).toHaveBeenNthCalledWith(1, 'EAFF202006180001.json')
71
59
  expect(createS3WriteStream).toHaveBeenNthCalledWith(3, 'EAFF202006180001.json.sha256')
72
- expect(createFtpWriteStream).toHaveBeenNthCalledWith(1, 'EAFF202006180001.json')
73
- expect(createFtpWriteStream).toHaveBeenNthCalledWith(3, 'EAFF202006180001.json.sha256')
74
60
  expect(JSON.parse(s3DataStreamFile1.dataProcessed)).toEqual({ licences: [{ part: 0 }, { part: 1 }] })
75
- expect(JSON.parse(ftpDataStreamFile1.dataProcessed)).toEqual({ licences: [{ part: 0 }, { part: 1 }] })
76
61
  expect(s3HashStreamFile1.dataProcessed).toEqual(fileShaHash) // validated
77
- expect(ftpHashStreamFile1.dataProcessed).toEqual(fileShaHash) // validated
78
62
 
79
63
  // File 2 expectations
80
64
  expect(createS3WriteStream).toHaveBeenNthCalledWith(4, 'EAFF202006180002.json')
81
65
  expect(createS3WriteStream).toHaveBeenNthCalledWith(6, 'EAFF202006180002.json.sha256')
82
- expect(createFtpWriteStream).toHaveBeenNthCalledWith(4, 'EAFF202006180002.json')
83
- expect(createFtpWriteStream).toHaveBeenNthCalledWith(6, 'EAFF202006180002.json.sha256')
84
66
  expect(JSON.parse(s3DataStreamFile2.dataProcessed)).toEqual({ licences: [{ part: 0 }, { part: 1 }] })
85
- expect(JSON.parse(ftpDataStreamFile2.dataProcessed)).toEqual({ licences: [{ part: 0 }, { part: 1 }] })
86
67
  expect(s3HashStreamFile2.dataProcessed).toEqual(fileShaHash) // validated
87
- expect(ftpHashStreamFile2.dataProcessed).toEqual(fileShaHash) // validated
88
68
 
89
69
  // Persist to dynamics for file 1
90
70
  expect(persist).toHaveBeenNthCalledWith(1, [
@@ -197,10 +177,7 @@ describe('deliverFulfilmentFiles', () => {
197
177
  const s3 = createTestableStream()
198
178
  streamHelper.pipelinePromise.mockResolvedValue()
199
179
  openpgp.encrypt.mockResolvedValue(s2)
200
- merge2
201
- .mockReturnValueOnce(s1)
202
- .mockReturnValueOnce(s2)
203
- .mockReturnValueOnce(s3)
180
+ merge2.mockReturnValueOnce(s1).mockReturnValueOnce(s2).mockReturnValueOnce(s3)
204
181
  await mockExecuteQuery()
205
182
  createMockFileStreams()
206
183
 
@@ -227,27 +204,18 @@ const createMockFulfilmentRequestFile = async (fileName, date) =>
227
204
 
228
205
  const createMockFileStreams = () => {
229
206
  const s3DataStreamFile = createTestableStream()
230
- const ftpDataStreamFile = createTestableStream()
231
207
  createS3WriteStream.mockReturnValueOnce({ s3WriteStream: s3DataStreamFile, managedUpload: Promise.resolve() })
232
- createFtpWriteStream.mockReturnValueOnce({ ftpWriteStream: ftpDataStreamFile, managedUpload: Promise.resolve() })
233
208
 
234
209
  const s3EncryptedDataStreamFile = createTestableStream()
235
- const ftpEncryptedDataStreamFile = createTestableStream()
236
210
  createS3WriteStream.mockReturnValueOnce({ s3WriteStream: s3EncryptedDataStreamFile, managedUpload: Promise.resolve() })
237
- createFtpWriteStream.mockReturnValueOnce({ ftpWriteStream: ftpEncryptedDataStreamFile, managedUpload: Promise.resolve() })
238
211
 
239
212
  const s3HashStreamFile = createTestableStream()
240
- const ftpHashStreamFile = createTestableStream()
241
213
  createS3WriteStream.mockReturnValueOnce({ s3WriteStream: s3HashStreamFile, managedUpload: Promise.resolve() })
242
- createFtpWriteStream.mockReturnValueOnce({ ftpWriteStream: ftpHashStreamFile, managedUpload: Promise.resolve() })
243
214
 
244
215
  return {
245
216
  s3DataStreamFile,
246
- ftpDataStreamFile,
247
217
  s3EncryptedDataStreamFile,
248
- ftpEncryptedDataStreamFile,
249
- s3HashStreamFile,
250
- ftpHashStreamFile
218
+ s3HashStreamFile
251
219
  }
252
220
  }
253
221
 
@@ -4,7 +4,6 @@ import merge2 from 'merge2'
4
4
  import moment from 'moment'
5
5
  import { executeQuery, persist, findFulfilmentFiles } from '@defra-fish/dynamics-lib'
6
6
  import { createS3WriteStream, readS3PartFiles } from '../transport/s3.js'
7
- import { createFtpWriteStream } from '../transport/ftp.js'
8
7
  import { FULFILMENT_FILE_STATUS_OPTIONSET, getOptionSetEntry } from './staging-common.js'
9
8
  import db from 'debug'
10
9
  import openpgp from 'openpgp'
@@ -69,11 +68,6 @@ const createEncryptedDataReadStream = async file => {
69
68
  */
70
69
  const deliver = async (targetFileName, readableStream, ...transforms) => {
71
70
  const { s3WriteStream: s3DataStream, managedUpload: s3DataManagedUpload } = createS3WriteStream(targetFileName)
72
- const { ftpWriteStream: ftpDataStream, managedUpload: ftpDataManagedUpload } = createFtpWriteStream(targetFileName)
73
71
 
74
- await Promise.all([
75
- streamHelper.pipelinePromise([readableStream, ...transforms, s3DataStream, ftpDataStream]),
76
- s3DataManagedUpload,
77
- ftpDataManagedUpload
78
- ])
72
+ await Promise.all([streamHelper.pipelinePromise([readableStream, ...transforms, s3DataStream]), s3DataManagedUpload])
79
73
  }
@@ -1,9 +0,0 @@
1
- const ssh2sftpClient = jest.genMockFromModule('ssh2-sftp-client')
2
-
3
- export const mockedFtpMethods = {
4
- connect: jest.fn(async () => {}),
5
- put: jest.fn(async () => {}),
6
- end: jest.fn()
7
- }
8
- ssh2sftpClient.mockImplementation(() => mockedFtpMethods)
9
- export default ssh2sftpClient
@@ -1,63 +0,0 @@
1
- import { createFtpWriteStream } from '../ftp.js'
2
- import { mockedFtpMethods } from 'ssh2-sftp-client'
3
-
4
- jest.mock('stream')
5
- jest.mock('../../config.js', () => ({
6
- ftp: {
7
- host: 'testhost',
8
- port: 2222,
9
- path: 'testpath/',
10
- username: 'testusername',
11
- privateKey: 'testprivatekey'
12
- }
13
- }))
14
-
15
- describe('ftp', () => {
16
- beforeEach(() => {
17
- jest.clearAllMocks()
18
- })
19
-
20
- describe('createFtpWriteStream', () => {
21
- it('creates a stream to write to the configured FTP server', async () => {
22
- const { ftpWriteStream, managedUpload } = createFtpWriteStream('testfile.json')
23
- ftpWriteStream.write('Some data')
24
- ftpWriteStream.end()
25
- await managedUpload
26
- expect(mockedFtpMethods.connect).toHaveBeenCalledWith(
27
- expect.objectContaining({
28
- host: 'testhost',
29
- port: 2222,
30
- username: 'testusername',
31
- privateKey: 'testprivatekey'
32
- })
33
- )
34
- expect(mockedFtpMethods.put).toHaveBeenCalledWith(ftpWriteStream, 'testpath/testfile.json', {
35
- flags: 'w',
36
- encoding: 'UTF-8',
37
- autoClose: false
38
- })
39
- expect(mockedFtpMethods.end).toHaveBeenCalled()
40
- })
41
-
42
- it('rejects the managed upload promise if an FTP upload error occurs', async () => {
43
- const testError = new Error('Test error')
44
- mockedFtpMethods.put.mockImplementationOnce(() => Promise.reject(testError))
45
- const { ftpWriteStream, managedUpload } = createFtpWriteStream('testfile.json')
46
- await expect(managedUpload).rejects.toThrow('Test error')
47
- expect(mockedFtpMethods.connect).toHaveBeenCalledWith(
48
- expect.objectContaining({
49
- host: 'testhost',
50
- port: 2222,
51
- username: 'testusername',
52
- privateKey: 'testprivatekey'
53
- })
54
- )
55
- expect(mockedFtpMethods.put).toHaveBeenCalledWith(ftpWriteStream, 'testpath/testfile.json', {
56
- flags: 'w',
57
- encoding: 'UTF-8',
58
- autoClose: false
59
- })
60
- expect(mockedFtpMethods.end).toHaveBeenCalled()
61
- })
62
- })
63
- })
@@ -1,28 +0,0 @@
1
- import FtpClient from 'ssh2-sftp-client'
2
- import Path from 'path'
3
- import { PassThrough } from 'stream'
4
- import config from '../config.js'
5
- import db from 'debug'
6
- const debug = db('fulfilment:transport')
7
-
8
- /**
9
- * Create a stream to write to the configured FTP server
10
- *
11
- * @param {string} filename The name of the file to be written to the remote server
12
- * @returns {{ftpWriteStream: module:stream.internal.PassThrough, managedUpload: Promise<*>}}
13
- */
14
- export const createFtpWriteStream = filename => {
15
- const sftp = new FtpClient()
16
- const passThrough = new PassThrough()
17
- const remoteFilePath = Path.join(config.ftp.path, filename)
18
- return {
19
- ftpWriteStream: passThrough,
20
- managedUpload: sftp
21
- .connect(config.ftp)
22
- .then(() => sftp.put(passThrough, remoteFilePath, { flags: 'w', encoding: 'UTF-8', autoClose: false }))
23
- .then(() =>
24
- debug('File successfully uploaded to fulfilment provider at sftp://%s:%s%s', config.ftp.host, config.ftp.port, remoteFilePath)
25
- )
26
- .finally(() => sftp.end())
27
- }
28
- }