@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.
@@ -0,0 +1,9 @@
1
+ export interface AsyncOperationModelClass {
2
+ new (params: {
3
+ stackName: string;
4
+ systemBucket: string;
5
+ tableName?: string;
6
+ }): any;
7
+ create(...args: any): any | any[];
8
+ }
9
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cumulus/async-operations",
3
- "version": "9.8.0-alpha.0",
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": "9.7.0",
32
- "@cumulus/db": "9.8.0-alpha.0",
33
- "@cumulus/errors": "9.7.0",
34
- "@cumulus/types": "9.7.0",
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": "9.7.0",
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
  }
@@ -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 getLambdaEnvironmentVariables = async (
22
+ export const getLambdaConfiguration = async (
22
23
  functionName: string
23
- ): Promise<EnvironmentVariables[]> => {
24
- const lambdaConfig = await lambda().getFunctionConfiguration({
25
- FunctionName: functionName,
26
- }).promise();
27
-
28
- return Object.entries(lambdaConfig?.Environment?.Variables ?? {}).map((obj) => ({
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 lambdaVars = await getLambdaEnvironmentVariables(lambdaName);
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: 'EC2',
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 (params: {
125
- asyncOperationTaskDefinition: string,
126
- cluster: string,
127
- description: string,
128
- dynamoTableName: string,
129
- knexConfig?: NodeJS.ProcessEnv,
130
- lambdaName: string,
131
- operationType: AsyncOperationType,
132
- payload: unknown,
133
- stackName: string,
134
- systemBucket: string,
135
- useLambdaEnvironmentVariables?: boolean,
136
- startEcsTaskFunc?: () => StartEcsTaskReturnType
137
- }, AsyncOperation: AsyncOperationModelClass
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: any) => {
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 { startAsyncOperation } from './async_operations';
2
- module.exports = { startAsyncOperation };
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 { getLambdaEnvironmentVariables, startAsyncOperation } = require('../dist/async_operations');
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
- sinon.stub(lambda(), 'getFunctionConfiguration').returns({
60
- promise: () => Promise.resolve({
61
- Environment: {
62
- Variables: {
63
- ES_HOST: 'es-host',
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, 'EC2');
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('getLambdaEnvironmentVariables returns expected environment variables', async (t) => {
349
- const vars = await getLambdaEnvironmentVariables('name');
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',