Skip to content
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

Merged
merged 15 commits into from
Jul 5, 2018
2 changes: 1 addition & 1 deletion .eslint-ratchet-high-water-mark
Original file line number Diff line number Diff line change
@@ -1 +1 @@
670
466
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- update the StreamSpecification DynamoDB tables to have StreamViewType: "NEW_AND_OLD_IMAGES"
- delete granule files in s3
- **CUMULUS-398** - Fix not able to filter executions bu workflow
- **CUMULUS-748** - Fix invalid lambda .zip files being validated/uploaded to AWS
- **CUMULUS-544** - Post to CMR task has UAT URL hard-coded
- Made configurable: PostToCmr now requires CMR_ENVIRONMENT env to be set to 'SIT' or 'OPS' for those CMR environments. Default is UAT.

Expand Down Expand Up @@ -373,4 +374,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
[v1.1.0]: https://github.com/cumulus-nasa/cumulus/compare/v1.0.1...v1.1.0
[v1.0.1]: https://github.com/cumulus-nasa/cumulus/compare/v1.0.0...v1.0.1
[v1.0.0]: https://github.com/cumulus-nasa/cumulus/compare/pre-v1-release...v1.0.0

34 changes: 27 additions & 7 deletions packages/deployment/lib/lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

const fs = require('fs-extra');
const path = require('path');
const util = require('util');
const utils = require('kes').utils;
const yauzl = require('yauzl');

const { Lambda } = require('kes');

/**
Expand All @@ -27,22 +30,31 @@ class UpdatedLambda extends Lambda {
super(config);
this.config = config;
}

/**
* Copies the source code of a given lambda function, zips it, calculates
* the hash of the source code and updates the lambda object with
* the hash, local and remote locations of the code
* the hash, local and remote locations of the code.
*
* @param {Object} lambda - the lambda object
* @returns {Promise} returns the updated lambda object
*/
zipLambda(lambda) {
let msg = `Zipping ${lambda.local}`;

async zipLambda(lambda) {
// skip if the file with the same hash is zipped
if (fs.existsSync(lambda.local)) {
return Promise.resolve(lambda);
// and is a valid zip file
if (await fs.pathExists(lambda.local)) {
try {
await (util.promisify(yauzl.open))(lambda.local); // Verify yauzl can open the .zip file
return Promise.resolve(lambda);
}
catch (e) {
console.log(`${lambda.local} is invalid and will be rebuilt`);
}
}
const fileList = [lambda.source];

let msg = `Zipping ${lambda.local}`;
const fileList = [lambda.source];
if (lambda.useMessageAdapter) {
const kesFolder = path.join(this.config.kesFolder, 'build', 'adapter');
fileList.push(kesFolder);
Expand All @@ -51,7 +63,15 @@ class UpdatedLambda extends Lambda {

console.log(`${msg} for ${lambda.name}`);

return utils.zip(lambda.local, fileList).then(() => lambda);
try {
await utils.zip(lambda.local, fileList);
}
catch (e) {
console.log(`Error zipping ${e}`);
throw e;
}

return lambda;
}

/**
Expand Down
Binary file not shown.
169 changes: 97 additions & 72 deletions packages/deployment/test/test_lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@

'use strict';

const os = require('os');
const fs = require('fs-extra');
const os = require('os');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Alphabetical

const path = require('path');
const test = require('ava');

const Lambda = require('../lib/lambda');
const { fetchMessageAdapter } = require('../lib/adapter');

const gitPath = 'cumulus-nasa/cumulus-message-adapter';
const zipFixturePath = 'test/fixtures/zipfile-fixture.zip';
const zipFixtureSize = fs.statSync(zipFixturePath).size;

test.beforeEach(async (t) => {
t.context.temp = fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
Expand All @@ -25,7 +28,6 @@ test.beforeEach(async (t) => {
bucket: 'testbucket',
stack: 'teststack'
};

t.context.lambda = {
handler: 'index.handler',
name: 'lambda-example',
Expand Down Expand Up @@ -62,8 +64,8 @@ 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)
await l.zipLambda(l.buildS3Path(t.context.lambda));
const testLambda = new Lambda(t.context.config);
await testLambda.zipLambda(testLambda.buildS3Path(t.context.lambda));
t.truthy(fs.statSync(t.context.lambda.local));
t.is(t.context.lambda.local, lambdaLocalOrigin);
t.is(t.context.lambda.remote, lambdaRemoteOrigin);
Expand All @@ -73,8 +75,8 @@ 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)
await l.zipLambda(l.buildS3Path(t.context.lambda));
const testLambda = new Lambda(t.context.config);
await testLambda.zipLambda(testLambda.buildS3Path(t.context.lambda));
t.truthy(fs.statSync(t.context.lambda.local));
t.is(
path.basename(t.context.lambda.local),
Expand All @@ -86,69 +88,92 @@ test.serial('zipLambda: works for lambda using message adapter', async (t) => {
);
});

test.serial(
`zipLambda: for lambda using message adapter, no new file is generated
if the task and message adapter are not updated`,
async (t) => {
t.context.lambda.useMessageAdapter = true;

// put a 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)}`
);

const existingLambdaRemote = path.join(
path.dirname(t.context.lambda.remote),
`${Lambda.messageAdapterZipFileHash}-${path.basename(t.context.lambda.remote)}`
);

fs.writeFileSync(existingLambdaLocal, 'hello');
t.is(fs.statSync(existingLambdaLocal).size, 5);

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(fs.statSync(t.context.lambda.local).size, 5);
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
if the message adapter is updated`,
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');
t.is(fs.statSync(existingLambdaLocal).size, 5);

// message adapter is updated, a new lambda zip file is generated
const adapterHashOrigin = Lambda.messageAdapterZipFileHash;
Lambda.messageAdapterZipFileHash = `${adapterHashOrigin}123`;

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.not(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: given an invalid zip file generated from a previous run, a new valid lambda file is generated', async (t) => { // eslint-disable-line max-len
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)}`
);

await fs.writeFile(existingLambdaLocal, 'hello');
t.is(fs.statSync(existingLambdaLocal).size, 5);

const testLambda = new Lambda(t.context.config);
await testLambda.zipLambda(testLambda.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 if the task and message adapter are not updated', async (t) => { // eslint-disable-line max-len
t.context.lambda.useMessageAdapter = true;

// put a 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)}`
);

const existingLambdaRemote = path.join(
path.dirname(t.context.lambda.remote),
`${Lambda.messageAdapterZipFileHash}-${path.basename(t.context.lambda.remote)}`
);

await fs.copy(zipFixturePath, existingLambdaLocal);
t.is(fs.statSync(existingLambdaLocal).size, zipFixtureSize);

const testLambda = new Lambda(t.context.config);
await testLambda.zipLambda(testLambda.buildS3Path(t.context.lambda));
t.truthy(fs.statSync(t.context.lambda.local));
t.is(fs.statSync(t.context.lambda.local).size, zipFixtureSize);
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 if the message adapter is updated', async (t) => { // eslint-disable-line max-len
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)}`
);

await fs.writeFile(existingLambdaLocal, 'hello');
t.is(fs.statSync(existingLambdaLocal).size, 5);

// message adapter is updated, a new lambda zip file is generated
const adapterHashOrigin = Lambda.messageAdapterZipFileHash;
Lambda.messageAdapterZipFileHash = `${adapterHashOrigin}123`;

const testLambda = new Lambda(t.context.config);
await testLambda.zipLambda(testLambda.buildS3Path(t.context.lambda));
t.truthy(fs.statSync(t.context.lambda.local));
t.true(fs.statSync(t.context.lambda.local).size > 5);
t.not(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)}`
);
});