@cumulus/async-operations 9.8.0-alpha.0 → 10.0.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/LICENSE +60 -0
- package/dist/async_operations.d.ts +10 -3
- package/dist/async_operations.d.ts.map +1 -1
- package/dist/async_operations.js +28 -11
- package/dist/async_operations.js.map +1 -1
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.js +3 -0
- package/package.json +8 -7
- package/src/async_operations.ts +54 -28
- package/src/index.ts +2 -2
- package/tests/test-async_operations.js +62 -13
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cumulus/async-operations",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "Cumulus Core internal async operations module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,15 +28,16 @@
|
|
|
28
28
|
"author": "Cumulus Authors",
|
|
29
29
|
"license": "Apache-2.0",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@cumulus/aws-client": "
|
|
32
|
-
"@cumulus/db": "
|
|
33
|
-
"@cumulus/errors": "
|
|
34
|
-
"@cumulus/types": "
|
|
31
|
+
"@cumulus/aws-client": "10.0.0",
|
|
32
|
+
"@cumulus/db": "10.0.0",
|
|
33
|
+
"@cumulus/errors": "10.0.0",
|
|
34
|
+
"@cumulus/types": "10.0.0",
|
|
35
35
|
"uuid": "8.3.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@cumulus/common": "
|
|
38
|
+
"@cumulus/common": "10.0.0",
|
|
39
39
|
"@types/aws-sdk": "2.7.0",
|
|
40
40
|
"@types/uuid": "^8.0.0"
|
|
41
|
-
}
|
|
41
|
+
},
|
|
42
|
+
"gitHead": "7c6d2d1cd79b57d6943bbc3d898d0cf975b543b1"
|
|
42
43
|
}
|
package/src/async_operations.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ECS } from 'aws-sdk';
|
|
1
|
+
import { ECS, Lambda } from 'aws-sdk';
|
|
2
2
|
import { ecs, s3, lambda } from '@cumulus/aws-client/services';
|
|
3
3
|
import { EnvironmentVariables } from 'aws-sdk/clients/lambda';
|
|
4
4
|
import {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
translateApiAsyncOperationToPostgresAsyncOperation,
|
|
7
7
|
AsyncOperationPgModel,
|
|
8
8
|
createRejectableTransaction,
|
|
9
|
+
Knex,
|
|
9
10
|
} from '@cumulus/db';
|
|
10
11
|
import { ApiAsyncOperation, AsyncOperationType } from '@cumulus/types/api/async_operations';
|
|
11
12
|
import { v4 as uuidv4 } from 'uuid';
|
|
@@ -14,22 +15,24 @@ import type { PromiseResult } from 'aws-sdk/lib/request';
|
|
|
14
15
|
|
|
15
16
|
import type { AsyncOperationModelClass } from './types';
|
|
16
17
|
|
|
17
|
-
const { EcsStartTaskError } = require('@cumulus/errors');
|
|
18
|
+
const { EcsStartTaskError, MissingRequiredArgument } = require('@cumulus/errors');
|
|
18
19
|
|
|
19
20
|
type StartEcsTaskReturnType = Promise<PromiseResult<ECS.RunTaskResponse, AWSError>>;
|
|
20
21
|
|
|
21
|
-
export const
|
|
22
|
+
export const getLambdaConfiguration = async (
|
|
22
23
|
functionName: string
|
|
23
|
-
): Promise<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
): Promise<Lambda.FunctionConfiguration> => lambda().getFunctionConfiguration({
|
|
25
|
+
FunctionName: functionName,
|
|
26
|
+
}).promise();
|
|
27
|
+
|
|
28
|
+
export const getLambdaEnvironmentVariables = (
|
|
29
|
+
configuration: Lambda.FunctionConfiguration
|
|
30
|
+
): EnvironmentVariables[] => Object.entries(configuration?.Environment?.Variables ?? {}).map(
|
|
31
|
+
(obj) => ({
|
|
29
32
|
name: obj[0],
|
|
30
33
|
value: obj[1],
|
|
31
|
-
})
|
|
32
|
-
|
|
34
|
+
})
|
|
35
|
+
);
|
|
33
36
|
|
|
34
37
|
/**
|
|
35
38
|
* Start an ECS task for the async operation.
|
|
@@ -37,6 +40,8 @@ export const getLambdaEnvironmentVariables = async (
|
|
|
37
40
|
* @param {Object} params
|
|
38
41
|
* @param {string} params.asyncOperationTaskDefinition - ARN for the task definition
|
|
39
42
|
* @param {string} params.cluster - ARN for the ECS cluster to use for the task
|
|
43
|
+
* @param {string} params.callerlambdaName
|
|
44
|
+
* Environment variable for Lambda name that is initiating the ECS task
|
|
40
45
|
* @param {string} params.lambdaName
|
|
41
46
|
* Environment variable for Lambda name that will be run by the ECS task
|
|
42
47
|
* @param {string} params.id - the Async operation ID
|
|
@@ -52,6 +57,7 @@ export const getLambdaEnvironmentVariables = async (
|
|
|
52
57
|
export const startECSTask = async ({
|
|
53
58
|
asyncOperationTaskDefinition,
|
|
54
59
|
cluster,
|
|
60
|
+
callerLambdaName,
|
|
55
61
|
lambdaName,
|
|
56
62
|
id,
|
|
57
63
|
payloadBucket,
|
|
@@ -61,6 +67,7 @@ export const startECSTask = async ({
|
|
|
61
67
|
}: {
|
|
62
68
|
asyncOperationTaskDefinition: string,
|
|
63
69
|
cluster: string,
|
|
70
|
+
callerLambdaName: string,
|
|
64
71
|
lambdaName: string,
|
|
65
72
|
id: string,
|
|
66
73
|
payloadBucket: string,
|
|
@@ -76,15 +83,25 @@ export const startECSTask = async ({
|
|
|
76
83
|
] as EnvironmentVariables[];
|
|
77
84
|
let taskVars = envVars;
|
|
78
85
|
|
|
86
|
+
const callerLambdaConfig = await getLambdaConfiguration(callerLambdaName);
|
|
87
|
+
|
|
79
88
|
if (useLambdaEnvironmentVariables) {
|
|
80
|
-
const
|
|
89
|
+
const lambdaConfig = await getLambdaConfiguration(lambdaName);
|
|
90
|
+
const lambdaVars = getLambdaEnvironmentVariables(lambdaConfig);
|
|
81
91
|
taskVars = envVars.concat(lambdaVars);
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
return ecs().runTask({
|
|
85
95
|
cluster,
|
|
86
96
|
taskDefinition: asyncOperationTaskDefinition,
|
|
87
|
-
launchType: '
|
|
97
|
+
launchType: 'FARGATE',
|
|
98
|
+
networkConfiguration: {
|
|
99
|
+
awsvpcConfiguration: {
|
|
100
|
+
subnets: callerLambdaConfig?.VpcConfig?.SubnetIds ?? [],
|
|
101
|
+
assignPublicIp: 'DISABLED',
|
|
102
|
+
securityGroups: callerLambdaConfig?.VpcConfig?.SecurityGroupIds ?? [],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
88
105
|
overrides: {
|
|
89
106
|
containerOverrides: [
|
|
90
107
|
{
|
|
@@ -107,6 +124,7 @@ export const startECSTask = async ({
|
|
|
107
124
|
* @param {string} params.dynamoTableName - the dynamo async operations table to
|
|
108
125
|
* write records to
|
|
109
126
|
* @param {Object} params.knexConfig - Object with Knex configuration keys
|
|
127
|
+
* @param {string} params.callerLambdaName - the name of the Lambda initiating the ECS task
|
|
110
128
|
* @param {string} params.lambdaName - the name of the Lambda task to be run
|
|
111
129
|
* @param {string} params.operationType - the type of async operation to run
|
|
112
130
|
* @param {Object|Array} params.payload - the event to be passed to the lambda task.
|
|
@@ -121,20 +139,23 @@ export const startECSTask = async ({
|
|
|
121
139
|
* @returns {Promise<Object>} - an AsyncOperation record
|
|
122
140
|
* @memberof AsyncOperation
|
|
123
141
|
*/
|
|
124
|
-
export const startAsyncOperation = async (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
export const startAsyncOperation = async (
|
|
143
|
+
params: {
|
|
144
|
+
asyncOperationTaskDefinition: string,
|
|
145
|
+
cluster: string,
|
|
146
|
+
description: string,
|
|
147
|
+
dynamoTableName: string,
|
|
148
|
+
knexConfig?: NodeJS.ProcessEnv,
|
|
149
|
+
callerLambdaName: string,
|
|
150
|
+
lambdaName: string,
|
|
151
|
+
operationType: AsyncOperationType,
|
|
152
|
+
payload: unknown,
|
|
153
|
+
stackName: string,
|
|
154
|
+
systemBucket: string,
|
|
155
|
+
useLambdaEnvironmentVariables?: boolean,
|
|
156
|
+
startEcsTaskFunc?: () => StartEcsTaskReturnType
|
|
157
|
+
},
|
|
158
|
+
AsyncOperation: AsyncOperationModelClass
|
|
138
159
|
): Promise<Partial<ApiAsyncOperation>> => {
|
|
139
160
|
const {
|
|
140
161
|
description,
|
|
@@ -143,10 +164,15 @@ export const startAsyncOperation = async (params: {
|
|
|
143
164
|
systemBucket,
|
|
144
165
|
stackName,
|
|
145
166
|
dynamoTableName,
|
|
167
|
+
callerLambdaName,
|
|
146
168
|
knexConfig = process.env,
|
|
147
169
|
startEcsTaskFunc = startECSTask,
|
|
148
170
|
} = params;
|
|
149
171
|
|
|
172
|
+
if (!callerLambdaName) {
|
|
173
|
+
throw new MissingRequiredArgument(`callerLambdaName must be specified to start new async operation, received: ${callerLambdaName}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
150
176
|
const id = uuidv4();
|
|
151
177
|
// Store the payload to S3
|
|
152
178
|
const payloadBucket = systemBucket;
|
|
@@ -180,7 +206,7 @@ export const startAsyncOperation = async (params: {
|
|
|
180
206
|
const asyncOperationPgModel = new AsyncOperationPgModel();
|
|
181
207
|
|
|
182
208
|
const knex = await getKnexClient({ env: knexConfig });
|
|
183
|
-
return createRejectableTransaction<ApiAsyncOperation>(knex, async (trx:
|
|
209
|
+
return createRejectableTransaction<ApiAsyncOperation>(knex, async (trx: Knex.Transaction) => {
|
|
184
210
|
const createObject: ApiAsyncOperation = {
|
|
185
211
|
id,
|
|
186
212
|
status: 'RUNNING',
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
module.exports =
|
|
1
|
+
import * as asyncOperations from './async_operations';
|
|
2
|
+
module.exports = asyncOperations;
|
|
@@ -18,9 +18,13 @@ const {
|
|
|
18
18
|
AsyncOperationPgModel,
|
|
19
19
|
migrationDir,
|
|
20
20
|
} = require('@cumulus/db');
|
|
21
|
-
const { EcsStartTaskError } = require('@cumulus/errors');
|
|
21
|
+
const { EcsStartTaskError, MissingRequiredArgument } = require('@cumulus/errors');
|
|
22
22
|
|
|
23
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
getLambdaConfiguration,
|
|
25
|
+
getLambdaEnvironmentVariables,
|
|
26
|
+
startAsyncOperation,
|
|
27
|
+
} = require('../dist/async_operations');
|
|
24
28
|
|
|
25
29
|
const dynamoTableName = 'notUsedDynamoTableName';
|
|
26
30
|
|
|
@@ -56,15 +60,17 @@ test.before(async (t) => {
|
|
|
56
60
|
};
|
|
57
61
|
};
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
AsyncOperationsTable: 'async-operations-table',
|
|
65
|
-
},
|
|
63
|
+
t.context.functionConfig = {
|
|
64
|
+
Environment: {
|
|
65
|
+
Variables: {
|
|
66
|
+
ES_HOST: 'es-host',
|
|
67
|
+
AsyncOperationsTable: 'async-operations-table',
|
|
66
68
|
},
|
|
67
|
-
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
sinon.stub(lambda(), 'getFunctionConfiguration').returns({
|
|
73
|
+
promise: () => Promise.resolve(t.context.functionConfig),
|
|
68
74
|
});
|
|
69
75
|
|
|
70
76
|
t.context.asyncOperationPgModel = new AsyncOperationPgModel();
|
|
@@ -96,6 +102,7 @@ test.serial('startAsyncOperation uploads the payload to S3', async (t) => {
|
|
|
96
102
|
const { id } = await startAsyncOperation({
|
|
97
103
|
asyncOperationTaskDefinition: randomString(),
|
|
98
104
|
cluster: randomString(),
|
|
105
|
+
callerLambdaName: randomString(),
|
|
99
106
|
lambdaName: randomString(),
|
|
100
107
|
description: randomString(),
|
|
101
108
|
operationType: 'ES Index',
|
|
@@ -128,6 +135,7 @@ test.serial('The AsyncOperation start method starts an ECS task with the correct
|
|
|
128
135
|
|
|
129
136
|
const asyncOperationTaskDefinition = randomString();
|
|
130
137
|
const cluster = randomString();
|
|
138
|
+
const callerLambdaName = randomString();
|
|
131
139
|
const lambdaName = randomString();
|
|
132
140
|
const payload = { x: randomString() };
|
|
133
141
|
const stackName = randomString();
|
|
@@ -136,6 +144,7 @@ test.serial('The AsyncOperation start method starts an ECS task with the correct
|
|
|
136
144
|
asyncOperationTaskDefinition,
|
|
137
145
|
cluster,
|
|
138
146
|
lambdaName,
|
|
147
|
+
callerLambdaName,
|
|
139
148
|
description: randomString(),
|
|
140
149
|
operationType: 'ES Index',
|
|
141
150
|
payload,
|
|
@@ -148,7 +157,7 @@ test.serial('The AsyncOperation start method starts an ECS task with the correct
|
|
|
148
157
|
|
|
149
158
|
t.is(stubbedEcsRunTaskParams.cluster, cluster);
|
|
150
159
|
t.is(stubbedEcsRunTaskParams.taskDefinition, asyncOperationTaskDefinition);
|
|
151
|
-
t.is(stubbedEcsRunTaskParams.launchType, '
|
|
160
|
+
t.is(stubbedEcsRunTaskParams.launchType, 'FARGATE');
|
|
152
161
|
|
|
153
162
|
const environmentOverrides = {};
|
|
154
163
|
stubbedEcsRunTaskParams.overrides.containerOverrides[0].environment.forEach((env) => {
|
|
@@ -176,6 +185,7 @@ test.serial('The startAsyncOperation method throws error if it is unable to crea
|
|
|
176
185
|
await t.throwsAsync(startAsyncOperation({
|
|
177
186
|
asyncOperationTaskDefinition: randomString(),
|
|
178
187
|
cluster: randomString(),
|
|
188
|
+
callerLambdaName: randomString(),
|
|
179
189
|
lambdaName: randomString(),
|
|
180
190
|
description: randomString(),
|
|
181
191
|
operationType: 'ES Index',
|
|
@@ -206,6 +216,7 @@ test('startAsyncOperation calls Dynamo model create method', async (t) => {
|
|
|
206
216
|
const result = await startAsyncOperation({
|
|
207
217
|
asyncOperationTaskDefinition: randomString(),
|
|
208
218
|
cluster: randomString(),
|
|
219
|
+
callerLambdaName: randomString(),
|
|
209
220
|
lambdaName: randomString(),
|
|
210
221
|
description,
|
|
211
222
|
operationType: 'ES Index',
|
|
@@ -248,6 +259,7 @@ test.serial('The startAsyncOperation writes records to the databases', async (t)
|
|
|
248
259
|
const { id } = await startAsyncOperation({
|
|
249
260
|
asyncOperationTaskDefinition: randomString(),
|
|
250
261
|
cluster: randomString(),
|
|
262
|
+
callerLambdaName: randomString(),
|
|
251
263
|
lambdaName: randomString(),
|
|
252
264
|
description,
|
|
253
265
|
operationType,
|
|
@@ -296,6 +308,7 @@ test.serial('The startAsyncOperation writes records to the databases with correc
|
|
|
296
308
|
const { id } = await startAsyncOperation({
|
|
297
309
|
asyncOperationTaskDefinition: randomString(),
|
|
298
310
|
cluster: randomString(),
|
|
311
|
+
callerLambdaName: randomString(),
|
|
299
312
|
lambdaName: randomString(),
|
|
300
313
|
description,
|
|
301
314
|
operationType,
|
|
@@ -332,6 +345,7 @@ test.serial('The startAsyncOperation method returns the newly-generated record',
|
|
|
332
345
|
const results = await startAsyncOperation({
|
|
333
346
|
asyncOperationTaskDefinition: randomString(),
|
|
334
347
|
cluster: randomString(),
|
|
348
|
+
callerLambdaName: randomString(),
|
|
335
349
|
lambdaName: randomString(),
|
|
336
350
|
description: randomString(),
|
|
337
351
|
operationType: 'ES Index',
|
|
@@ -345,8 +359,42 @@ test.serial('The startAsyncOperation method returns the newly-generated record',
|
|
|
345
359
|
t.is(results.taskArn, taskArn);
|
|
346
360
|
});
|
|
347
361
|
|
|
348
|
-
test('
|
|
349
|
-
const
|
|
362
|
+
test.serial('The startAsyncOperation method throws error if callerLambdaName parameter is missing', async (t) => {
|
|
363
|
+
const stubbedAsyncOperationsModel = class {
|
|
364
|
+
create = sinon.stub();
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
stubbedEcsRunTaskParams = {};
|
|
368
|
+
stubbedEcsRunTaskResult = {
|
|
369
|
+
tasks: [{ taskArn: randomString() }],
|
|
370
|
+
failures: [],
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
await t.throwsAsync(
|
|
374
|
+
startAsyncOperation({
|
|
375
|
+
asyncOperationTaskDefinition: randomString(),
|
|
376
|
+
cluster: randomString,
|
|
377
|
+
lambdaName: randomString,
|
|
378
|
+
description: randomString(),
|
|
379
|
+
operationType: 'ES Index',
|
|
380
|
+
payload: { x: randomString() },
|
|
381
|
+
stackName: randomString,
|
|
382
|
+
dynamoTableName: dynamoTableName,
|
|
383
|
+
knexConfig: knexConfig,
|
|
384
|
+
systemBucket,
|
|
385
|
+
useLambdaEnvironmentVariables: true,
|
|
386
|
+
}, stubbedAsyncOperationsModel),
|
|
387
|
+
{ instanceOf: MissingRequiredArgument }
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('getLambdaConfiguration returns expected configuration', async (t) => {
|
|
392
|
+
const config = await getLambdaConfiguration('name');
|
|
393
|
+
t.deepEqual(config, t.context.functionConfig);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test('getLambdaEnvironmentVariables returns expected environment variables', (t) => {
|
|
397
|
+
const vars = getLambdaEnvironmentVariables(t.context.functionConfig);
|
|
350
398
|
|
|
351
399
|
t.deepEqual(new Set(vars), new Set([
|
|
352
400
|
{ name: 'ES_HOST', value: 'es-host' },
|
|
@@ -370,6 +418,7 @@ test.serial('ECS task params contain lambda environment variables when useLambda
|
|
|
370
418
|
await startAsyncOperation({
|
|
371
419
|
asyncOperationTaskDefinition: randomString(),
|
|
372
420
|
cluster: randomString(),
|
|
421
|
+
callerLambdaName: randomString(),
|
|
373
422
|
lambdaName: randomString(),
|
|
374
423
|
description: randomString(),
|
|
375
424
|
operationType: 'ES Index',
|