@rlyle1179/serverless-aws-batch 1.1.19 → 1.1.20

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/lib/batchtask.js CHANGED
@@ -1,390 +1,390 @@
1
-
2
- const BbPromise = require('bluebird');
3
- const _ = require('lodash');
4
- const util = require('util');
5
- const path = require('path');
6
- const fs = require('fs');
7
- const JSZip = require("jszip");
8
-
9
- /**
10
- * @param functionName
11
- * @returns {string} The logical ID of the Job Definition resource created for the function
12
- */
13
- function getJobDefinitionLogicalId(functionName) {
14
- let logicalId = `JobDefinition${functionName.charAt(0).toUpperCase()}${functionName.slice(1)}`;
15
- return logicalId.length > 64
16
- ? logicalId.substring(0, 64)
17
- : logicalId;
18
- }
19
-
20
- /**
21
- * @returns {string} "IamRoleBatchJobExecution"
22
- */
23
- function getBatchJobExecutionRoleLogicalId(functionName) {
24
- let logicalId = `IamRoleBatchJobExecution${functionName.charAt(0).toUpperCase()}${functionName.slice(1)}`;
25
- return logicalId.length > 64
26
- ? logicalId.substring(0, 64)
27
- : logicalId;
28
- }
29
-
30
- let dependsOnFunction = undefined;
31
-
32
- /**
33
- * Transforms a function object into a "JobDefinition" object that can be used to run this function inside a Batch task
34
- */
35
- function compileBatchTask(functionName) {
36
- const functionObject = this.serverless.service.getFunction(functionName);
37
-
38
- // If this isn't a batch function, just skip it
39
- if (! functionObject.hasOwnProperty("batch")) {
40
- return BbPromise.resolve();
41
- }
42
-
43
- functionObject.getJobDefinitionName = function() {
44
- return `${this.provider.serverless.service.service}-${this.provider.getStage()}-${functionName}`
45
- }.bind(this);
46
-
47
- // Setup our new function
48
- const newFunction = {
49
- "Type": "AWS::Batch::JobDefinition",
50
- "Properties": {
51
- "JobDefinitionName": functionObject.getJobDefinitionName(),
52
- "Type": "container"
53
- }
54
- }
55
- updateContainerProperties.bind(this)(newFunction, functionObject, functionName);
56
- updateRetryStrategy.bind(this)(newFunction, functionObject);
57
- updateTimeout.bind(this)(newFunction, functionObject);
58
-
59
- const logicalId = getJobDefinitionLogicalId(functionName);
60
-
61
- // generate role for a batchjob execution based on custom iam statements
62
- const newBatchJobExecutionRoleObject = generateBatchJobExecutionRole.bind(this)(functionName, functionObject)
63
-
64
- // FIX: CloudFormation is blowing up trying to create all the JobDefinitions in parallel,
65
- // so to fix we make each JobDefinition depend on the prior one being created to solve this issue.
66
- if ( dependsOnFunction !== undefined ) {
67
- for(let k in newBatchJobExecutionRoleObject) {
68
- newBatchJobExecutionRoleObject[k]["DependsOn"] = dependsOnFunction;
69
- }
70
- newFunction["DependsOn"] = dependsOnFunction;
71
- }
72
- dependsOnFunction = logicalId;
73
-
74
- // Add it to our compiled cloud formation templates
75
- _.merge(
76
- this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
77
- {
78
- [logicalId]: newFunction
79
- },
80
- newBatchJobExecutionRoleObject
81
- );
82
-
83
- // Now replace the original lambda function with code to invoke this batch task
84
- generateLambdaScheduleArtifact.bind(this)(functionName);
85
-
86
- _.merge(
87
- this.serverless.service.functions,
88
- {
89
- [functionName]: {
90
- handler: 'handler.schedule',
91
- name: functionObject.name,
92
- memorySize: 128,
93
- timeout: 6,
94
- runtime: _.get(this.serverless.service,"custom.runtime", "nodejs18.x" ),
95
- package: {
96
- artifact: `${path.join(this.serverless.config.servicePath, '.serverless', functionName)}.zip`
97
- },
98
- environment: {
99
- EVENT_LOGGING_ENABLED: functionObject.batch.hasOwnProperty('scheduleLoggingEnabled') && functionObject.batch.scheduleLoggingEnabled,
100
- FUNCTION_NAME: functionName,
101
- JOB_DEFINITION_ARN: {
102
- 'Ref': this.provider.naming.getJobDefinitionLogicalId(functionName)
103
- },
104
- JOB_QUEUE_ARN: {
105
- 'Ref': this.provider.naming.getBatchJobQueueLogicalId()
106
- }
107
- },
108
- role: {
109
- "Fn::GetAtt": [
110
- this.provider.naming.getLambdaScheduleExecutionRoleLogicalId(),
111
- "Arn"
112
- ]
113
- }
114
- }
115
- }
116
- );
117
- _.unset(this.serverless.service.functions, `${functionName}.iamRoleStatements`)
118
- return BbPromise.resolve();
119
- }
120
-
121
- /**
122
- * Generates a container properties configuration and adds it to the newFunction object
123
- * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-containerproperties.html
124
- */
125
- function updateContainerProperties(newFunction, functionObject, functionName) {
126
- newFunction.Properties.ContainerProperties = _.merge(
127
- {},
128
- functionObject.batch.ContainerProperties
129
- );
130
-
131
- if (! newFunction.Properties.ContainerProperties.hasOwnProperty('Memory')) {
132
- const memorySize = getMemorySize(functionObject);
133
- newFunction.Properties.ContainerProperties.Memory = memorySize;
134
- }
135
-
136
- if (! newFunction.Properties.ContainerProperties.hasOwnProperty('Command')) {
137
- if (! functionObject.handler) {
138
- throw new this.serverless.classes.Error(`Could not find handler for batch task ${functionObject.name}`);
139
- }
140
- newFunction.Properties.ContainerProperties.Command = [
141
- `${functionObject.handler}`,
142
- "Ref::event"
143
- ]
144
- }
145
-
146
- if (! newFunction.Properties.ContainerProperties.hasOwnProperty('Image')) {
147
- newFunction.Properties.ContainerProperties.Image = this.provider.naming.getDockerImageName();
148
- }
149
-
150
- if (! newFunction.Properties.ContainerProperties.hasOwnProperty('Vcpus')) {
151
- newFunction.Properties.ContainerProperties.Vcpus = 1;
152
- }
153
-
154
- if (! newFunction.Properties.ContainerProperties.hasOwnProperty('JobRoleArn')) {
155
- newFunction.Properties.ContainerProperties.JobRoleArn = {
156
- Ref: this.provider.naming.getBatchJobExecutionRoleLogicalId(functionName)
157
- };
158
- }
159
-
160
- // Setup the required environment variables and any included in the serverless configuration
161
- const environment = _.merge(
162
- {
163
- 'AWS_LAMBDA_FUNCTION_TIMEOUT': getTimeout(functionObject),
164
- 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE': getMemorySize(functionObject),
165
- 'AWS_REGION': this.serverless.service.provider.region || 'us-east-1',
166
- 'AWS_SECRET_ACCESS_KEY': process.env.AWS_SECRET_ACCESS_KEY,
167
- 'AWS_ACCESS_KEY_ID': process.env.AWS_ACCESS_KEY_ID
168
- },
169
- this.serverless.service.provider.environment,
170
- functionObject.environment,
171
- functionObject.batch.ContainerProperties.Environment
172
- );
173
-
174
- let invalidEnvVar = null;
175
- _.forEach(
176
- _.keys(environment),
177
- key => { // eslint-disable-line consistent-return
178
- // taken from the bash man pages
179
- if (!key.match(/^[A-Za-z_][a-zA-Z0-9_]*$/)) {
180
- invalidEnvVar = `Invalid characters in environment variable ${key}`;
181
- return false; // break loop with lodash
182
- }
183
- const value = environment[key];
184
- if (_.isObject(value)) {
185
- const isCFRef = _.isObject(value) &&
186
- !_.some(value, (v, k) => k !== 'Ref' && !_.startsWith(k, 'Fn::'));
187
- if (!isCFRef) {
188
- invalidEnvVar = `Environment variable ${key} must contain string`;
189
- return false;
190
- }
191
- }
192
- }
193
- );
194
-
195
- if (invalidEnvVar) {
196
- throw new Error(invalidEnvVar);
197
- }
198
-
199
- newFunction.Properties.ContainerProperties.Environment = [];
200
- _.forEach(
201
- _.keys(environment),
202
- key => {
203
- newFunction.Properties.ContainerProperties.Environment.push({
204
- "Name": key,
205
- "Value": environment[key]
206
- });
207
- }
208
- );
209
-
210
- const logGroupName = _.get(this.serverless.service,"custom.awsBatch.logGroupName");
211
- if ( logGroupName ) {
212
- newFunction.Properties.ContainerProperties.LogConfiguration = {
213
- LogDriver: 'awslogs',
214
- Options: {
215
- "awslogs-group": logGroupName,
216
- "awslogs-stream-prefix": "batch"
217
- }
218
- }
219
- }
220
- }
221
-
222
- /**
223
- * Generates a retry strategy configuration and adds it to the newFunction object
224
- */
225
- function updateRetryStrategy(newFunction, functionObject) {
226
- newFunction.Properties.RetryStrategy = _.merge(
227
- {},
228
- functionObject.batch.RetryStrategy
229
- );
230
-
231
- if (! newFunction.Properties.RetryStrategy.hasOwnProperty('Attempts')) {
232
- newFunction.Properties.RetryStrategy.Attempts = 1;
233
- }
234
- }
235
-
236
- /**
237
- * Generates a timeout configuration and adds it to the newFunction object
238
- * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-timeout.html
239
- */
240
- function updateTimeout(newFunction, functionObject) {
241
- newFunction.Properties.Timeout = _.merge(
242
- {},
243
- functionObject.batch.Timeout
244
- );
245
-
246
- if (! newFunction.Properties.Timeout.hasOwnProperty('AttemptDurationSeconds')) {
247
- const timeout = getTimeout(functionObject);
248
- newFunction.Properties.Timeout.AttemptDurationSeconds = timeout;
249
- }
250
- }
251
-
252
- /**
253
- * Retrieves the memory to use for this function if it's set on the functionObject.
254
- * Otherwise defaults to 2gb.
255
- */
256
- function getMemorySize(functionObject) {
257
- return Number(functionObject.batch.memory)
258
- || Number(functionObject.memory)
259
- || 2048;
260
- }
261
-
262
- /**
263
- * Retrieves the timeout to use for this function if it's set on the functionObject.
264
- * Otherwise defaults to 300s
265
- */
266
- function getTimeout(functionObject) {
267
- return Number(functionObject.batch.Timeout ? functionObject.batch.Timeout.AttemptDurationSeconds: undefined)
268
- || Number(functionObject.timeout)
269
- || 300;
270
- }
271
-
272
-
273
- /**
274
- * Handles copying the "schedule.js" file into a zip deployment artifact for the specific batch function
275
- */
276
- async function generateLambdaScheduleArtifact(functionName) {
277
- this.serverless.cli.log(`Building lambda schedule artifact for: "${functionName}"...`);
278
-
279
- const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(functionName);
280
- const artifactFilePath = path.join(this.serverless.config.servicePath, '.serverless', functionArtifactFileName);
281
-
282
- const zip = new JSZip();
283
-
284
- // Add a top-level, arbitrary text file with contents
285
- const contents = fs.readFileSync(path.join(__dirname, 'schedule.js'));
286
- zip.file("handler.js", contents);
287
-
288
- // JSZip can generate Buffers so you can do the following
289
- const response = new Promise((resolve, reject) => {
290
- zip.generateNodeStream({type:'nodebuffer',streamFiles:true})
291
- .pipe(fs.createWriteStream(artifactFilePath))
292
- .on('finish', function () {
293
- resolve()
294
- })
295
- });
296
-
297
- return await response;
298
- }
299
- /**
300
- * Generate Batch job execution role per batch job
301
- */
302
- function generateBatchJobExecutionRole(functionName, functionObject) {
303
- const batchJobExecutionRoleName = `BatchJobRole-${functionName}-${this.provider.getRegion()}-${this.provider.getStage()}-${this.provider.serverless.service.service}`.substring(0, 64);
304
- const batchJobExecutionRoleTemplate = `
305
- {
306
- "Type": "AWS::IAM::Role",
307
- "Properties": {
308
- "RoleName": "${batchJobExecutionRoleName}",
309
- "Path": "/",
310
- "AssumeRolePolicyDocument": {
311
- "Version": "2012-10-17",
312
- "Statement": [
313
- {
314
- "Sid": "",
315
- "Effect": "Allow",
316
- "Principal": {
317
- "Service": "ecs-tasks.amazonaws.com"
318
- },
319
- "Action": "sts:AssumeRole"
320
- }
321
- ]
322
- }
323
- }
324
- }
325
- `;
326
-
327
- // Merge our iamRoleStatements into this role
328
- const batchJobExecutionRole = JSON.parse(batchJobExecutionRoleTemplate);
329
- if (this.serverless.service.provider.hasOwnProperty('iamRoleStatements')) {
330
- _.merge(
331
- batchJobExecutionRole.Properties,
332
- {
333
- "Policies": [
334
- {
335
- "PolicyName": "batch-job-execution-policies",
336
- "PolicyDocument": {
337
- "Version": "2012-10-17",
338
- "Statement": this.serverless.service.provider.iamRoleStatements
339
- }
340
- }
341
- ]
342
- }
343
- )
344
- }
345
-
346
- // Merge in custom iamRoleStatements from lambda function definition
347
- if (functionObject.hasOwnProperty('iamRoleStatements')) {
348
- _.mergeWith(
349
- batchJobExecutionRole.Properties,
350
- {
351
- "Policies": [
352
- {
353
- "PolicyName": "batch-job-custom-policies",
354
- "PolicyDocument": {
355
- "Version": "2012-10-17",
356
- "Statement": functionObject.iamRoleStatements
357
- }
358
- }
359
- ]
360
- },
361
- (objValue, srcValue) => {
362
- if (_.isArray(objValue)) {
363
- return objValue.concat(srcValue);
364
- }
365
- }
366
- )
367
- }
368
-
369
- // Setup the JobQueue to push tasks to
370
- return {
371
- [this.provider.naming.getBatchJobExecutionRoleLogicalId(functionName)]: batchJobExecutionRole
372
- };
373
- }
374
-
375
- /**
376
- * Iterates through all of our functions, starting the compile to JobDefinition if needed
377
- */
378
- function compileBatchTasks() {
379
- const allFunctions = this.serverless.service.getAllFunctions();
380
- return BbPromise.each(
381
- allFunctions,
382
- functionName => compileBatchTask.bind(this)(functionName)
383
- );
384
- }
385
-
386
- module.exports = {
387
- compileBatchTasks,
388
- getJobDefinitionLogicalId,
389
- getBatchJobExecutionRoleLogicalId
390
- };
1
+
2
+ const BbPromise = require('bluebird');
3
+ const _ = require('lodash');
4
+ const util = require('util');
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+ const JSZip = require("jszip");
8
+
9
+ /**
10
+ * @param functionName
11
+ * @returns {string} The logical ID of the Job Definition resource created for the function
12
+ */
13
+ function getJobDefinitionLogicalId(functionName) {
14
+ let logicalId = `JobDefinition${functionName.charAt(0).toUpperCase()}${functionName.slice(1)}`;
15
+ return logicalId.length > 64
16
+ ? logicalId.substring(0, 64)
17
+ : logicalId;
18
+ }
19
+
20
+ /**
21
+ * @returns {string} "IamRoleBatchJobExecution"
22
+ */
23
+ function getBatchJobExecutionRoleLogicalId(functionName) {
24
+ let logicalId = `IamRoleBatchJobExecution${functionName.charAt(0).toUpperCase()}${functionName.slice(1)}`;
25
+ return logicalId.length > 64
26
+ ? logicalId.substring(0, 64)
27
+ : logicalId;
28
+ }
29
+
30
+ let dependsOnFunction = undefined;
31
+
32
+ /**
33
+ * Transforms a function object into a "JobDefinition" object that can be used to run this function inside a Batch task
34
+ */
35
+ function compileBatchTask(functionName) {
36
+ const functionObject = this.serverless.service.getFunction(functionName);
37
+
38
+ // If this isn't a batch function, just skip it
39
+ if (! functionObject.hasOwnProperty("batch")) {
40
+ return BbPromise.resolve();
41
+ }
42
+
43
+ functionObject.getJobDefinitionName = function() {
44
+ return `${this.provider.serverless.service.service}-${this.provider.getStage()}-${functionName}`
45
+ }.bind(this);
46
+
47
+ // Setup our new function
48
+ const newFunction = {
49
+ "Type": "AWS::Batch::JobDefinition",
50
+ "Properties": {
51
+ "JobDefinitionName": functionObject.getJobDefinitionName(),
52
+ "Type": "container"
53
+ }
54
+ }
55
+ updateContainerProperties.bind(this)(newFunction, functionObject, functionName);
56
+ updateRetryStrategy.bind(this)(newFunction, functionObject);
57
+ updateTimeout.bind(this)(newFunction, functionObject);
58
+
59
+ const logicalId = getJobDefinitionLogicalId(functionName);
60
+
61
+ // generate role for a batchjob execution based on custom iam statements
62
+ const newBatchJobExecutionRoleObject = generateBatchJobExecutionRole.bind(this)(functionName, functionObject)
63
+
64
+ // FIX: CloudFormation is blowing up trying to create all the JobDefinitions in parallel,
65
+ // so to fix we make each JobDefinition depend on the prior one being created to solve this issue.
66
+ if ( dependsOnFunction !== undefined ) {
67
+ for(let k in newBatchJobExecutionRoleObject) {
68
+ newBatchJobExecutionRoleObject[k]["DependsOn"] = dependsOnFunction;
69
+ }
70
+ newFunction["DependsOn"] = dependsOnFunction;
71
+ }
72
+ dependsOnFunction = logicalId;
73
+
74
+ // Add it to our compiled cloud formation templates
75
+ _.merge(
76
+ this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
77
+ {
78
+ [logicalId]: newFunction
79
+ },
80
+ newBatchJobExecutionRoleObject
81
+ );
82
+
83
+ // Now replace the original lambda function with code to invoke this batch task
84
+ generateLambdaScheduleArtifact.bind(this)(functionName);
85
+
86
+ _.merge(
87
+ this.serverless.service.functions,
88
+ {
89
+ [functionName]: {
90
+ handler: 'handler.schedule',
91
+ name: functionObject.name,
92
+ memorySize: 128,
93
+ timeout: 6,
94
+ runtime: _.get(this.serverless.service,"custom.awsBatch.runtime", "nodejs18.x" ),
95
+ package: {
96
+ artifact: `${path.join(this.serverless.config.servicePath, '.serverless', functionName)}.zip`
97
+ },
98
+ environment: {
99
+ EVENT_LOGGING_ENABLED: functionObject.batch.hasOwnProperty('scheduleLoggingEnabled') && functionObject.batch.scheduleLoggingEnabled,
100
+ FUNCTION_NAME: functionName,
101
+ JOB_DEFINITION_ARN: {
102
+ 'Ref': this.provider.naming.getJobDefinitionLogicalId(functionName)
103
+ },
104
+ JOB_QUEUE_ARN: {
105
+ 'Ref': this.provider.naming.getBatchJobQueueLogicalId()
106
+ }
107
+ },
108
+ role: {
109
+ "Fn::GetAtt": [
110
+ this.provider.naming.getLambdaScheduleExecutionRoleLogicalId(),
111
+ "Arn"
112
+ ]
113
+ }
114
+ }
115
+ }
116
+ );
117
+ _.unset(this.serverless.service.functions, `${functionName}.iamRoleStatements`)
118
+ return BbPromise.resolve();
119
+ }
120
+
121
+ /**
122
+ * Generates a container properties configuration and adds it to the newFunction object
123
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-containerproperties.html
124
+ */
125
+ function updateContainerProperties(newFunction, functionObject, functionName) {
126
+ newFunction.Properties.ContainerProperties = _.merge(
127
+ {},
128
+ functionObject.batch.ContainerProperties
129
+ );
130
+
131
+ if (! newFunction.Properties.ContainerProperties.hasOwnProperty('Memory')) {
132
+ const memorySize = getMemorySize(functionObject);
133
+ newFunction.Properties.ContainerProperties.Memory = memorySize;
134
+ }
135
+
136
+ if (! newFunction.Properties.ContainerProperties.hasOwnProperty('Command')) {
137
+ if (! functionObject.handler) {
138
+ throw new this.serverless.classes.Error(`Could not find handler for batch task ${functionObject.name}`);
139
+ }
140
+ newFunction.Properties.ContainerProperties.Command = [
141
+ `${functionObject.handler}`,
142
+ "Ref::event"
143
+ ]
144
+ }
145
+
146
+ if (! newFunction.Properties.ContainerProperties.hasOwnProperty('Image')) {
147
+ newFunction.Properties.ContainerProperties.Image = this.provider.naming.getDockerImageName();
148
+ }
149
+
150
+ if (! newFunction.Properties.ContainerProperties.hasOwnProperty('Vcpus')) {
151
+ newFunction.Properties.ContainerProperties.Vcpus = 1;
152
+ }
153
+
154
+ if (! newFunction.Properties.ContainerProperties.hasOwnProperty('JobRoleArn')) {
155
+ newFunction.Properties.ContainerProperties.JobRoleArn = {
156
+ Ref: this.provider.naming.getBatchJobExecutionRoleLogicalId(functionName)
157
+ };
158
+ }
159
+
160
+ // Setup the required environment variables and any included in the serverless configuration
161
+ const environment = _.merge(
162
+ {
163
+ 'AWS_LAMBDA_FUNCTION_TIMEOUT': getTimeout(functionObject),
164
+ 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE': getMemorySize(functionObject),
165
+ 'AWS_REGION': this.serverless.service.provider.region || 'us-east-1',
166
+ 'AWS_SECRET_ACCESS_KEY': process.env.AWS_SECRET_ACCESS_KEY,
167
+ 'AWS_ACCESS_KEY_ID': process.env.AWS_ACCESS_KEY_ID
168
+ },
169
+ this.serverless.service.provider.environment,
170
+ functionObject.environment,
171
+ functionObject.batch.ContainerProperties.Environment
172
+ );
173
+
174
+ let invalidEnvVar = null;
175
+ _.forEach(
176
+ _.keys(environment),
177
+ key => { // eslint-disable-line consistent-return
178
+ // taken from the bash man pages
179
+ if (!key.match(/^[A-Za-z_][a-zA-Z0-9_]*$/)) {
180
+ invalidEnvVar = `Invalid characters in environment variable ${key}`;
181
+ return false; // break loop with lodash
182
+ }
183
+ const value = environment[key];
184
+ if (_.isObject(value)) {
185
+ const isCFRef = _.isObject(value) &&
186
+ !_.some(value, (v, k) => k !== 'Ref' && !_.startsWith(k, 'Fn::'));
187
+ if (!isCFRef) {
188
+ invalidEnvVar = `Environment variable ${key} must contain string`;
189
+ return false;
190
+ }
191
+ }
192
+ }
193
+ );
194
+
195
+ if (invalidEnvVar) {
196
+ throw new Error(invalidEnvVar);
197
+ }
198
+
199
+ newFunction.Properties.ContainerProperties.Environment = [];
200
+ _.forEach(
201
+ _.keys(environment),
202
+ key => {
203
+ newFunction.Properties.ContainerProperties.Environment.push({
204
+ "Name": key,
205
+ "Value": environment[key]
206
+ });
207
+ }
208
+ );
209
+
210
+ const logGroupName = _.get(this.serverless.service,"custom.awsBatch.logGroupName");
211
+ if ( logGroupName ) {
212
+ newFunction.Properties.ContainerProperties.LogConfiguration = {
213
+ LogDriver: 'awslogs',
214
+ Options: {
215
+ "awslogs-group": logGroupName,
216
+ "awslogs-stream-prefix": "batch"
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Generates a retry strategy configuration and adds it to the newFunction object
224
+ */
225
+ function updateRetryStrategy(newFunction, functionObject) {
226
+ newFunction.Properties.RetryStrategy = _.merge(
227
+ {},
228
+ functionObject.batch.RetryStrategy
229
+ );
230
+
231
+ if (! newFunction.Properties.RetryStrategy.hasOwnProperty('Attempts')) {
232
+ newFunction.Properties.RetryStrategy.Attempts = 1;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Generates a timeout configuration and adds it to the newFunction object
238
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-timeout.html
239
+ */
240
+ function updateTimeout(newFunction, functionObject) {
241
+ newFunction.Properties.Timeout = _.merge(
242
+ {},
243
+ functionObject.batch.Timeout
244
+ );
245
+
246
+ if (! newFunction.Properties.Timeout.hasOwnProperty('AttemptDurationSeconds')) {
247
+ const timeout = getTimeout(functionObject);
248
+ newFunction.Properties.Timeout.AttemptDurationSeconds = timeout;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Retrieves the memory to use for this function if it's set on the functionObject.
254
+ * Otherwise defaults to 2gb.
255
+ */
256
+ function getMemorySize(functionObject) {
257
+ return Number(functionObject.batch.memory)
258
+ || Number(functionObject.memory)
259
+ || 2048;
260
+ }
261
+
262
+ /**
263
+ * Retrieves the timeout to use for this function if it's set on the functionObject.
264
+ * Otherwise defaults to 300s
265
+ */
266
+ function getTimeout(functionObject) {
267
+ return Number(functionObject.batch.Timeout ? functionObject.batch.Timeout.AttemptDurationSeconds: undefined)
268
+ || Number(functionObject.timeout)
269
+ || 300;
270
+ }
271
+
272
+
273
+ /**
274
+ * Handles copying the "schedule.js" file into a zip deployment artifact for the specific batch function
275
+ */
276
+ async function generateLambdaScheduleArtifact(functionName) {
277
+ this.serverless.cli.log(`Building lambda schedule artifact for: "${functionName}"...`);
278
+
279
+ const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(functionName);
280
+ const artifactFilePath = path.join(this.serverless.config.servicePath, '.serverless', functionArtifactFileName);
281
+
282
+ const zip = new JSZip();
283
+
284
+ // Add a top-level, arbitrary text file with contents
285
+ const contents = fs.readFileSync(path.join(__dirname, 'schedule.js'));
286
+ zip.file("handler.js", contents);
287
+
288
+ // JSZip can generate Buffers so you can do the following
289
+ const response = new Promise((resolve, reject) => {
290
+ zip.generateNodeStream({type:'nodebuffer',streamFiles:true})
291
+ .pipe(fs.createWriteStream(artifactFilePath))
292
+ .on('finish', function () {
293
+ resolve()
294
+ })
295
+ });
296
+
297
+ return await response;
298
+ }
299
+ /**
300
+ * Generate Batch job execution role per batch job
301
+ */
302
+ function generateBatchJobExecutionRole(functionName, functionObject) {
303
+ const batchJobExecutionRoleName = `BatchJobRole-${functionName}-${this.provider.getRegion()}-${this.provider.getStage()}-${this.provider.serverless.service.service}`.substring(0, 64);
304
+ const batchJobExecutionRoleTemplate = `
305
+ {
306
+ "Type": "AWS::IAM::Role",
307
+ "Properties": {
308
+ "RoleName": "${batchJobExecutionRoleName}",
309
+ "Path": "/",
310
+ "AssumeRolePolicyDocument": {
311
+ "Version": "2012-10-17",
312
+ "Statement": [
313
+ {
314
+ "Sid": "",
315
+ "Effect": "Allow",
316
+ "Principal": {
317
+ "Service": "ecs-tasks.amazonaws.com"
318
+ },
319
+ "Action": "sts:AssumeRole"
320
+ }
321
+ ]
322
+ }
323
+ }
324
+ }
325
+ `;
326
+
327
+ // Merge our iamRoleStatements into this role
328
+ const batchJobExecutionRole = JSON.parse(batchJobExecutionRoleTemplate);
329
+ if (this.serverless.service.provider.hasOwnProperty('iamRoleStatements')) {
330
+ _.merge(
331
+ batchJobExecutionRole.Properties,
332
+ {
333
+ "Policies": [
334
+ {
335
+ "PolicyName": "batch-job-execution-policies",
336
+ "PolicyDocument": {
337
+ "Version": "2012-10-17",
338
+ "Statement": this.serverless.service.provider.iamRoleStatements
339
+ }
340
+ }
341
+ ]
342
+ }
343
+ )
344
+ }
345
+
346
+ // Merge in custom iamRoleStatements from lambda function definition
347
+ if (functionObject.hasOwnProperty('iamRoleStatements')) {
348
+ _.mergeWith(
349
+ batchJobExecutionRole.Properties,
350
+ {
351
+ "Policies": [
352
+ {
353
+ "PolicyName": "batch-job-custom-policies",
354
+ "PolicyDocument": {
355
+ "Version": "2012-10-17",
356
+ "Statement": functionObject.iamRoleStatements
357
+ }
358
+ }
359
+ ]
360
+ },
361
+ (objValue, srcValue) => {
362
+ if (_.isArray(objValue)) {
363
+ return objValue.concat(srcValue);
364
+ }
365
+ }
366
+ )
367
+ }
368
+
369
+ // Setup the JobQueue to push tasks to
370
+ return {
371
+ [this.provider.naming.getBatchJobExecutionRoleLogicalId(functionName)]: batchJobExecutionRole
372
+ };
373
+ }
374
+
375
+ /**
376
+ * Iterates through all of our functions, starting the compile to JobDefinition if needed
377
+ */
378
+ function compileBatchTasks() {
379
+ const allFunctions = this.serverless.service.getAllFunctions();
380
+ return BbPromise.each(
381
+ allFunctions,
382
+ functionName => compileBatchTask.bind(this)(functionName)
383
+ );
384
+ }
385
+
386
+ module.exports = {
387
+ compileBatchTasks,
388
+ getJobDefinitionLogicalId,
389
+ getBatchJobExecutionRoleLogicalId
390
+ };