UNPKG

5.51 kBJavaScriptView Raw
1'use strict';
2
3const fs = require('fs-extra');
4const path = require('path');
5const util = require('util');
6const utils = require('kes').utils;
7const yauzl = require('yauzl');
8
9const { Lambda } = require('kes');
10
11/**
12 * A sub-class of the Kes Lambda class that changes
13 * how kes handles Lambda function compression and
14 * upload to S3.
15 *
16 * This sub-class adds cumulus-message-adapter to
17 * lambdas defined in a Kes configuration file.
18 */
19class UpdatedLambda extends Lambda {
20 /**
21 * Override the main constructor to allow
22 * passing the config object to the instance
23 * of the class
24 *
25 * @param {Object} config - Kes config object
26 */
27 constructor(config) {
28 super(config);
29 this.config = config;
30 }
31
32 /**
33 * Executes buildS3Path for all lambdas in a lambda configuration object
34 *
35 * Utilizes buildS3Path to populate bucket/hash values
36 * in the config object for a template that runs following a nested template
37 * that has already run the superclass 'process' method.
38 *
39 * @param {string} configKey - the configuration key with a lambda
40 * configuration object to be modified
41 * @returns {void} returns nothing
42 */
43 buildAllLambdaConfiguration(configKey) {
44 if (this.config[configKey]) {
45 let lambdas = this.config[configKey];
46 // if the lambdas is not an array but a object, convert it to a list
47 if (!Array.isArray(this.config[configKey])) {
48 lambdas = Object.keys(this.config[configKey]).map((name) => {
49 const lambda = this.config[configKey][name];
50 lambda.name = name;
51 return lambda;
52 });
53 }
54 lambdas.forEach((lambda) => this.buildS3Path(lambda));
55 }
56 }
57
58
59 /**
60 * Method adds hash value from each config.lambda to each
61 * defined workflow lambda in config.workflowLambdas
62 *
63 * @returns {void} returns nothing
64 */
65 addWorkflowLambdaHashes() {
66 Object.keys(this.config.lambdas).forEach((key) => {
67 if ((key in this.config.workflowLambdas) && ('hash' in this.config.lambdas[key])) {
68 this.config.workflowLambdas[key].hash = this.config.lambdas[key].hash;
69 }
70 });
71 }
72
73 /**
74 * Copies the source code of a given lambda function, zips it, calculates
75 * the hash of the source code and updates the lambda object with
76 * the hash, local and remote locations of the code.
77 *
78 * @param {Object} lambda - the lambda object
79 * @returns {Promise} returns the updated lambda object
80 */
81 async zipLambda(lambda) {
82 // skip if the file with the same hash is zipped
83 // and is a valid zip file
84 if (await fs.pathExists(lambda.local)) {
85 try {
86 await (util.promisify(yauzl.open))(lambda.local); // Verify yauzl can open the .zip file
87 return Promise.resolve(lambda);
88 } catch (e) {
89 console.log(`${lambda.local} is invalid and will be rebuilt`);
90 }
91 }
92
93 let msg = `Zipping ${lambda.local}`;
94 const fileList = [lambda.source];
95 if (lambda.useMessageAdapter) {
96 const kesFolder = path.join(this.config.kesFolder, 'build', 'adapter');
97 fileList.push(kesFolder);
98 msg += ' and injecting message adapter';
99 }
100
101 console.log(`${msg} for ${lambda.name}`);
102
103 try {
104 await utils.zip(lambda.local, fileList);
105 } catch (e) {
106 console.log(`Error zipping ${e}`);
107 throw e;
108 }
109
110 return lambda;
111 }
112
113 getLambdaVersionFromPackageFile(sourceDir) {
114 let packageJson = '{}';
115 const JsonFilePath = `${sourceDir}/../package.json`;
116
117 try {
118 if (fs.existsSync(JsonFilePath)) {
119 packageJson = fs.readFileSync(`${JsonFilePath}`);
120 }
121 } catch (e) {
122 console.log(`Error reading package.json from ${JsonFilePath}`);
123 throw (e);
124 }
125 const packageData = JSON.parse(packageJson);
126
127 if (!packageData || !packageData.version) {
128 return null;
129 }
130 return packageData.version;
131 }
132
133 /**
134 * Overrides the default method to allow returning
135 * the lambda function after s3 paths were built
136 *
137 * If a s3Source is used, only add remote and bucket values
138 *
139 * If a s3Source is used and a uniqueIdentifier is specified
140 * add that value in place of a calculated hash
141 *
142 * @param {Object} lambdaArg - the Lambda object
143 * @returns {Object} the updated lambda object
144 */
145 buildS3Path(lambdaArg) {
146 const lambda = super.buildS3Path(lambdaArg);
147
148 if (lambda.s3Source && lambda.s3Source.uniqueIdentifier) {
149 const uniqueIdentifier = lambda.s3Source.uniqueIdentifier;
150 if (!uniqueIdentifier.match(/^[a-z0-9]+$/)) {
151 throw new Error(`Invalid uniqueIdentifier ${uniqueIdentifier} provided for lambda`);
152 }
153 lambda.hash = uniqueIdentifier;
154 lambda.humanReadableIdentifier = uniqueIdentifier;
155 } else {
156 const lambdaVersion = this.getLambdaVersionFromPackageFile(lambda.source);
157 lambda.humanReadableIdentifier = lambdaVersion || lambda.hash;
158 }
159
160 // adding the hash of the message adapter zip file as part of lambda zip file
161 if (lambda.useMessageAdapter && UpdatedLambda.messageAdapterZipFileHash) {
162 lambda.local = path.join(
163 path.dirname(lambda.local),
164 `${UpdatedLambda.messageAdapterZipFileHash}-${path.basename(lambda.local)}`
165 );
166 lambda.remote = path.join(
167 path.dirname(lambda.remote),
168 `${UpdatedLambda.messageAdapterZipFileHash}-${path.basename(lambda.remote)}`
169 );
170 }
171
172 return lambda;
173 }
174}
175
176module.exports = UpdatedLambda;
177
178UpdatedLambda.messageAdapterZipFileHash = undefined;