-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cumulus 748 #409
Cumulus 748 #409
Changes from 5 commits
d72fa12
b45a71c
fb25317
5000f71
aa3fa57
7fe3b38
56b83fc
97f2c7d
dd789e0
c9fb6f1
d6cd99a
49a48ea
c77310d
bb3973b
3467547
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
670 | ||
467 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ const path = require('path'); | |
const utils = require('kes').utils; | ||
const { Lambda } = require('kes'); | ||
|
||
const yauzl = require('yauzl'); | ||
|
||
/** | ||
* A sub-class of the Kes Lambda class that changes | ||
* how kes handles Lambda function compression and | ||
|
@@ -27,6 +29,28 @@ class UpdatedLambda extends Lambda { | |
super(config); | ||
this.config = config; | ||
} | ||
|
||
/** | ||
* Makes use of yauzl.open's verification | ||
* to validate the file referenced in lambda.local | ||
* is a valid zip archive | ||
* | ||
* @param {Object} lambda - the lambda object | ||
* | ||
* @returns {Promise} promise resolution indicates success, will reject if | ||
* zipfile cannot be opened | ||
**/ | ||
validateZipFile(lambda) { | ||
return new Promise((resolve, reject) => { | ||
yauzl.open(lambda.local, (err, _zipfile) => { | ||
if (err) { | ||
reject(err); | ||
} | ||
resolve(); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Copies the source code of a given lambda function, zips it, calculates | ||
* the hash of the source code and updates the lambda object with | ||
|
@@ -35,23 +59,28 @@ class UpdatedLambda extends Lambda { | |
* @param {Object} lambda - the lambda object | ||
* @returns {Promise} returns the updated lambda object | ||
*/ | ||
zipLambda(lambda) { | ||
async zipLambda(lambda) { | ||
let msg = `Zipping ${lambda.local}`; | ||
// skip if the file with the same hash is zipped | ||
// and is a valid zip file | ||
if (fs.existsSync(lambda.local)) { | ||
return Promise.resolve(lambda); | ||
try { | ||
await this.validateZipFile(lambda); | ||
return Promise.resolve(lambda); | ||
} | ||
catch (e) { | ||
console.log(`${lambda.local} appears to be an invalid zip file, and will be re-built`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we actually retry it if it's invalid? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @laurenfrederick yes, the original code was skipping regeneration on L69, if the validator throws an error it logs and continues instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
} | ||
} | ||
const fileList = [lambda.source]; | ||
|
||
if (lambda.useMessageAdapter) { | ||
const kesFolder = path.join(this.config.kesFolder, 'build', 'adapter'); | ||
fileList.push(kesFolder); | ||
msg += ' and injecting message adapter'; | ||
} | ||
|
||
console.log(`${msg} for ${lambda.name}`); | ||
|
||
return utils.zip(lambda.local, fileList).then(() => lambda); | ||
return utils.zip(lambda.local, fileList).then(() => lambda).catch((e) => console.log(`Error zipping ${e}`)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed in chat, please put the |
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,7 @@ test.beforeEach(async (t) => { | |
bucket: 'testbucket', | ||
stack: 'teststack' | ||
}; | ||
|
||
t.context.fixturedir = 'test/fixtures'; | ||
t.context.lambda = { | ||
handler: 'index.handler', | ||
name: 'lambda-example', | ||
|
@@ -62,7 +62,7 @@ test.serial('zipLambda: works for lambda not using message adapter', async (t) = | |
t.context.lambda.useMessageAdapter = false; | ||
const lambdaLocalOrigin = t.context.lambda.local; | ||
const lambdaRemoteOrigin = t.context.lambda.remote; | ||
const l = new Lambda(t.context.config) | ||
const l = new Lambda(t.context.config); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be given a more descriptive variable name? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I don't think it's possible to have a less descriptive variable name. How about testLambda. |
||
await l.zipLambda(l.buildS3Path(t.context.lambda)); | ||
t.truthy(fs.statSync(t.context.lambda.local)); | ||
t.is(t.context.lambda.local, lambdaLocalOrigin); | ||
|
@@ -73,7 +73,7 @@ test.serial('zipLambda: works for lambda using message adapter', async (t) => { | |
t.context.lambda.useMessageAdapter = true; | ||
const lambdaLocalOrigin = t.context.lambda.local; | ||
const lambdaRemoteOrigin = t.context.lambda.remote; | ||
const l = new Lambda(t.context.config) | ||
const l = new Lambda(t.context.config); | ||
await l.zipLambda(l.buildS3Path(t.context.lambda)); | ||
t.truthy(fs.statSync(t.context.lambda.local)); | ||
t.is( | ||
|
@@ -87,7 +87,42 @@ test.serial('zipLambda: works for lambda using message adapter', async (t) => { | |
}); | ||
|
||
test.serial( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason that these tests can't be run in parallel? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm. Probably not, the setup function tempdir should be unique, it doesn't appear anything here should collide, assuming I'm not missing anything in the underlying dependencies.... why was it implemented that way initially 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Running them in parallel results in several failures. I can dig into that further and put up another PR if appropriate, or address it here if it feels like it should be sorted in this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like it's a defensive thing due to localstack constraints as much as anything - see #416 |
||
`zipLambda: for lambda using message adapter, no new file is generated | ||
`zipLambda: given an invalid zip file generated from a previous run, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If string interpolation is not being performed, use single quotes instead of backticks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no interpolation, however it's a multi-line string due to line length. Do we have a project-standard (aside eslint) when it comes to that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the case of long, single-line strings, the project convention is to use single quotes. If eslint complains about the length of the line, add |
||
a new valid lambda file is generated`, | ||
async (t) => { | ||
t.context.lambda.useMessageAdapter = true; | ||
const lambdaLocalOrigin = t.context.lambda.local; | ||
const lambdaRemoteOrigin = t.context.lambda.remote; | ||
|
||
// put an empty lambda zip file there as the result of the previous run | ||
const existingLambdaLocal = path.join( | ||
path.dirname(t.context.lambda.local), | ||
`${Lambda.messageAdapterZipFileHash}-${path.basename(t.context.lambda.local)}` | ||
); | ||
|
||
fs.writeFileSync(existingLambdaLocal, 'hello'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yjpa7145 out of curiosity, what is the benefit of doing an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, I think I see. If the next line is also async, then everything makes sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ifestus If you use the Sync call, the entire process stops until that writeFile operation finishes. That means that any other background operations (any asynchronous code running in the process) will hang until this write operation finishes. If the async version is used then those other asynchronous operations can continue while this write is being performed. |
||
t.is(fs.statSync(existingLambdaLocal).size, 5); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above, consider using the Promise-returning version from fs-extra. |
||
|
||
const l = new Lambda(t.context.config); | ||
await l.zipLambda(l.buildS3Path(t.context.lambda)); | ||
t.truthy(fs.statSync(t.context.lambda.local)); | ||
t.true(fs.statSync(t.context.lambda.local).size > 5); | ||
t.is(t.context.lambda.local, existingLambdaLocal); | ||
|
||
t.is( | ||
path.basename(t.context.lambda.local), | ||
`${Lambda.messageAdapterZipFileHash}-${path.basename(lambdaLocalOrigin)}` | ||
); | ||
t.is( | ||
path.basename(t.context.lambda.remote), | ||
`${Lambda.messageAdapterZipFileHash}-${path.basename(lambdaRemoteOrigin)}` | ||
); | ||
} | ||
); | ||
|
||
|
||
test.serial( | ||
`zipLambda: for lambda using message adapter, no new file is generated | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment about using single quotes instead of backticks. |
||
if the task and message adapter are not updated`, | ||
async (t) => { | ||
t.context.lambda.useMessageAdapter = true; | ||
|
@@ -103,20 +138,20 @@ test.serial( | |
`${Lambda.messageAdapterZipFileHash}-${path.basename(t.context.lambda.remote)}` | ||
); | ||
|
||
fs.writeFileSync(existingLambdaLocal, 'hello'); | ||
t.is(fs.statSync(existingLambdaLocal).size, 5); | ||
fs.copySync(`${t.context.fixturedir}/zipfile-fixture.zip`, existingLambdaLocal); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment about using fs-extra and not Sync version. |
||
t.is(fs.statSync(existingLambdaLocal).size, 180); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the 180 is the size of zipfile-fixture.zip, would it make sense to do a
(or something)? While it does add another line, it would make the test a bit more resilient to changes to the zip file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ifestus that's .... probably preferable for a fixture like this, as the contents are largely irrelevant, and it eliminates the magic-numbery feeling. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
const l = new Lambda(t.context.config) | ||
const l = new Lambda(t.context.config); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A more descriptive variable name would be helpful. |
||
await l.zipLambda(l.buildS3Path(t.context.lambda)); | ||
t.truthy(fs.statSync(t.context.lambda.local)); | ||
t.is(fs.statSync(t.context.lambda.local).size, 5); | ||
t.is(fs.statSync(t.context.lambda.local).size, 180); | ||
t.is(t.context.lambda.local, existingLambdaLocal); | ||
t.is(t.context.lambda.remote, existingLambdaRemote); | ||
} | ||
); | ||
|
||
test.serial( | ||
`zipLambda: for lambda using message adapter, a new file is created | ||
`zipLambda: for lambda using message adapter, a new file is created | ||
if the message adapter is updated`, | ||
async (t) => { | ||
t.context.lambda.useMessageAdapter = true; | ||
|
@@ -136,7 +171,7 @@ test.serial( | |
const adapterHashOrigin = Lambda.messageAdapterZipFileHash; | ||
Lambda.messageAdapterZipFileHash = `${adapterHashOrigin}123`; | ||
|
||
const l = new Lambda(t.context.config) | ||
const l = new Lambda(t.context.config); | ||
await l.zipLambda(l.buildS3Path(t.context.lambda)); | ||
t.truthy(fs.statSync(t.context.lambda.local)); | ||
t.true(fs.statSync(t.context.lambda.local).size > 5); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be:
If you don't return the rejection, it will continue on in the function and call
resolve()
as well. Speaking from experience, that's an annoying bug to track down.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, if you add
const util = require('util');
to the top of this file, this entire function can be rewritten as:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yjpa7145 Nice, I like that far better. TIL. Thanks!