1
+ import { Writable } from 'stream' ;
2
+ import * as archiver from 'archiver' ;
1
3
import { flatMap } from '../../util' ;
2
4
import { ISDK } from '../aws-auth' ;
3
- import { EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template' ;
5
+ import { CfnEvaluationException , EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template' ;
4
6
import { ChangeHotswapImpact , ChangeHotswapResult , HotswapOperation , HotswappableChangeCandidate } from './common' ;
5
7
6
8
/**
@@ -108,7 +110,7 @@ async function isLambdaFunctionCodeOnlyChange(
108
110
switch ( updatedPropName ) {
109
111
case 'Code' :
110
112
let foundCodeDifference = false ;
111
- let s3Bucket , s3Key , imageUri ;
113
+ let s3Bucket , s3Key , imageUri , functionCodeZip ;
112
114
113
115
for ( const newPropName in updatedProp . newValue ) {
114
116
switch ( newPropName ) {
@@ -124,6 +126,18 @@ async function isLambdaFunctionCodeOnlyChange(
124
126
foundCodeDifference = true ;
125
127
imageUri = await evaluateCfnTemplate . evaluateCfnExpression ( updatedProp . newValue [ newPropName ] ) ;
126
128
break ;
129
+ case 'ZipFile' :
130
+ foundCodeDifference = true ;
131
+ // We must create a zip package containing a file with the inline code
132
+ const functionCode = await evaluateCfnTemplate . evaluateCfnExpression ( updatedProp . newValue [ newPropName ] ) ;
133
+ const functionRuntime = await evaluateCfnTemplate . evaluateCfnExpression ( change . newValue . Properties ?. Runtime ) ;
134
+ if ( ! functionRuntime ) {
135
+ return ChangeHotswapImpact . REQUIRES_FULL_DEPLOYMENT ;
136
+ }
137
+ // file extension must be chosen depending on the runtime
138
+ const codeFileExt = determineCodeFileExtFromRuntime ( functionRuntime ) ;
139
+ functionCodeZip = await zipString ( `index.${ codeFileExt } ` , functionCode ) ;
140
+ break ;
127
141
default :
128
142
return ChangeHotswapImpact . REQUIRES_FULL_DEPLOYMENT ;
129
143
}
@@ -133,6 +147,7 @@ async function isLambdaFunctionCodeOnlyChange(
133
147
s3Bucket,
134
148
s3Key,
135
149
imageUri,
150
+ functionCodeZip,
136
151
} ;
137
152
}
138
153
break ;
@@ -173,6 +188,7 @@ interface LambdaFunctionCode {
173
188
readonly s3Bucket ?: string ;
174
189
readonly s3Key ?: string ;
175
190
readonly imageUri ?: string ;
191
+ readonly functionCodeZip ?: Buffer ;
176
192
}
177
193
178
194
enum TagDeletion {
@@ -221,6 +237,7 @@ class LambdaFunctionHotswapOperation implements HotswapOperation {
221
237
S3Bucket : resource . code . s3Bucket ,
222
238
S3Key : resource . code . s3Key ,
223
239
ImageUri : resource . code . imageUri ,
240
+ ZipFile : resource . code . functionCodeZip ,
224
241
} ) . promise ( ) ;
225
242
226
243
// only if the code changed is there any point in publishing a new Version
@@ -288,3 +305,55 @@ class LambdaFunctionHotswapOperation implements HotswapOperation {
288
305
return Promise . all ( operations ) ;
289
306
}
290
307
}
308
+
309
+ /**
310
+ * Compress a string as a file, returning a promise for the zip buffer
311
+ * https://github.com/archiverjs/node-archiver/issues/342
312
+ */
313
+ function zipString ( fileName : string , rawString : string ) : Promise < Buffer > {
314
+ return new Promise ( ( resolve , reject ) => {
315
+ const buffers : Buffer [ ] = [ ] ;
316
+
317
+ const converter = new Writable ( ) ;
318
+
319
+ converter . _write = ( chunk : Buffer , _ : string , callback : ( ) => void ) => {
320
+ buffers . push ( chunk ) ;
321
+ process . nextTick ( callback ) ;
322
+ } ;
323
+
324
+ converter . on ( 'finish' , ( ) => {
325
+ resolve ( Buffer . concat ( buffers ) ) ;
326
+ } ) ;
327
+
328
+ const archive = archiver ( 'zip' ) ;
329
+
330
+ archive . on ( 'error' , ( err ) => {
331
+ reject ( err ) ;
332
+ } ) ;
333
+
334
+ archive . pipe ( converter ) ;
335
+
336
+ archive . append ( rawString , {
337
+ name : fileName ,
338
+ date : new Date ( '1980-01-01T00:00:00.000Z' ) , // Add date to make resulting zip file deterministic
339
+ } ) ;
340
+
341
+ void archive . finalize ( ) ;
342
+ } ) ;
343
+ }
344
+
345
+ /**
346
+ * Get file extension from Lambda runtime string.
347
+ * We use this extension to create a deployment package from Lambda inline code.
348
+ */
349
+ function determineCodeFileExtFromRuntime ( runtime : string ) : string {
350
+ if ( runtime . startsWith ( 'node' ) ) {
351
+ return 'js' ;
352
+ }
353
+ if ( runtime . startsWith ( 'python' ) ) {
354
+ return 'py' ;
355
+ }
356
+ // Currently inline code only supports Node.js and Python, ignoring other runtimes.
357
+ // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#aws-properties-lambda-function-code-properties
358
+ throw new CfnEvaluationException ( `runtime ${ runtime } is unsupported, only node.js and python runtimes are currently supported.` ) ;
359
+ }
0 commit comments