diff --git a/index.js b/index.js index e97224c..848de4a 100644 --- a/index.js +++ b/index.js @@ -4,168 +4,329 @@ var gutil = require('gulp-util'); var through = require('through2'); var extend = require('xtend'); - var DEFAULT_OPTS = { - profile: null, - region: 'us-east-1' + profile: null, + region: 'us-east-1' }; var DEFAULT_PARAMS = { - Handler: 'index.handler', - Runtime: 'nodejs4.3' + Handler: 'index.handler', + Runtime: 'nodejs12.x' }; +function makeErr(message) { + return new gutil.PluginError('gulp-awslambda', message); +} -var makeErr = function(message) { - return new gutil.PluginError('gulp-awslambda', message); -}; +/** + * + * @param {Lambda} lambda + * @param {string} name + * @param {{contents: Buffer|Uint8Array|Blob|string }} upload + * @param {{Runtime: string}} params + * @param {{}} opts + * @returns {Promise} + */ +function updateFunctionCode(lambda, name, upload, params, opts) { + delete params.Runtime; + var code = { ZipFile: upload.contents }; + return lambda + .updateFunctionCode( + extend( + { + FunctionName: name + }, + code, + { + Publish: opts.publish || false + } + ) + ) + .promise(); +} -var updateFunctionCode = function(lambda, name, upload, params, opts) { - delete params.Runtime; - var code = params.Code || { ZipFile: upload.contents }; - return lambda.updateFunctionCode(extend({ - FunctionName: name - }, code, { - Publish: opts.publish || false - })); -}; +/** + * + * @param {Lambda} lambda + * @param {{contents: Buffer | Blob| string}} upload + * @param {{Code: *}} params + * @param {{publish: [boolean]}} opts + * @returns {Promise} + */ +function createFunction(lambda, upload, params, opts) { + params.Code = params.Code || { ZipFile: upload.contents }; + return lambda + .createFunction( + extend( + DEFAULT_PARAMS, + { + Publish: opts.publish || false + }, + params + ) + ) + .promise(); +} -var createFunction = function(lambda, upload, params, opts) { - params.Code = params.Code || { ZipFile: upload.contents }; - return lambda.createFunction(extend(DEFAULT_PARAMS, { - Publish: opts.publish || false - }, params)); -}; +/** + * + * @param {string} operation + * @param {Lambda} lambda + * @param {string} functionName + * @param {number} functionVersion + * @param {string} alias + * @param {string} aliasDescription + * @returns {Promise} + */ +async function upsertAlias( + operation, + lambda, + functionName, + functionVersion, + alias, + aliasDescription +) { + var params = { + FunctionName: functionName, + FunctionVersion: functionVersion, + Name: alias, + Description: aliasDescription + }; + gutil.log(`attempting to ${operation} alias ${alias}`); + const aliasFunc = + operation === 'create' + ? lambda.createAlias.bind(lambda) + : lambda.updateAlias.bind(lambda); + try { + await aliasFunc(params).promise(); + gutil.log( + `${operation}d alias ${gutil.colors.magenta( + alias + )} to point to version ${gutil.colors.magenta(functionVersion)}` + ); + } catch (err) { + gutil.log(`Could not ${operation} alias ${alias}: ${err}`); + } +} -var upsertAlias = function(operation, lambda, functionName, functionVersion, alias, aliasDescription) { - var params = { - FunctionName: functionName, - FunctionVersion: functionVersion, - Name: alias, - Description: aliasDescription - }; - lambda[operation + 'Alias'](params, function(err) { - if (err) { - gutil.log('Could not ' + operation + ' alias ' + alias + ':' + err); - } else { - gutil.log(operation + 'd alias ' + gutil.colors.magenta(alias) + ' for version ' + - gutil.colors.magenta(functionVersion)); - } - }); -}; +module.exports = function (params, opts) { + opts = extend(DEFAULT_OPTS, opts); + + AWS.config.update({ region: opts.region }); + var lambda = new AWS.Lambda(); + var toUpload; + var functionName = typeof params === 'string' ? params : params.FunctionName; + + async function updateOrCreateAlias(functionConfiguration) { + let operation; + if (!(opts.publish && opts.alias)) + return Promise.resolve('not updating alias'); + + gutil.log(`Getting alias: ${opts.alias.name}`); + try { + await lambda + .getAlias({ + FunctionName: functionName, + Name: opts.alias.name + }) + .promise(); + operation = 'update'; + } catch (error) { + operation = 'create'; + } + try { + await upsertAlias( + operation, + lambda, + functionName, + (opts.alias.version || functionConfiguration.Version).toString(), + opts.alias.name, + opts.alias.description + ); + } catch (error) { + gutil.log(`Failed to ${operation} alias: ${error}`); + throw error; + } + } + function printVersion(functionConfiguration) { + if (opts.publish) { + gutil.log( + 'Publishing Function Version: ' + + gutil.colors.magenta(functionConfiguration.Version) + ); + } + } + function transform(file, enc, cb) { + if (file.isNull()) { + return cb(); + } + if (file.isStream()) { + return cb(makeErr('Streaming is not supported')); + } + if (!toUpload) { + toUpload = file; + } + cb(); + } + + async function flush(cb) { + if (!toUpload && (typeof params === 'string' || !params.Code)) { + return cb(makeErr('No code provided')); + } + if (toUpload && toUpload.path.slice(-4) !== '.zip') { + return cb(makeErr('Provided file is not a ZIP')); + } + if (opts.alias) { + if (!opts.alias.name) { + return cb( + makeErr(`Alias requires a ${gutil.colors.red('name')} parameter`) + ); + } else if (!(typeof opts.alias.name === 'string')) { + return cb( + makeErr(`Alias ${gutil.colors.red('name')} must be a string`) + ); + } + } + + gutil.log(`Uploading Lambda function "${functionName}"...`); + + if (opts.profile !== null) { + AWS.config.credentials = new AWS.SharedIniFileCredentials({ + profile: opts.profile + }); + } + + var stream = this; + + function done(err) { + if (err) { + return cb(makeErr(err.message)); + } + gutil.log(`Lambda function "${functionName}" successfully uploaded`); + stream.push(toUpload); + cb(); + } + let functionConfiguration; + if (typeof params === 'string') { + // Just updating code + try { + gutil.log(`updating function code for "${functionName}"`); + functionConfiguration = await updateFunctionCode( + lambda, + params, + toUpload, + params, + opts + ); + gutil.log( + `function configuration is ${JSON.stringify( + functionConfiguration + )}. waiting for active state...` + ); + await lambda + .waitFor('functionActive', { FunctionName: functionName }) + .promise(); + printVersion(functionConfiguration); + gutil.log( + `updating alias "${opts.alias.name}" for function "${functionName}"` + ); + await updateOrCreateAlias(functionConfiguration); + done(); + } catch (error) { + done(error); + } + } else { + try { + gutil.log(`getting function configuration for "${functionName}"`); + const existingParams = await lambda + .getFunctionConfiguration({ + FunctionName: functionName + }) + .promise(); + try { + // Updating code + config + gutil.log(`updating function code for "${functionName}"`); + functionConfiguration = await updateFunctionCode( + lambda, + functionName, + toUpload, + params, + opts + ); + gutil.log( + `function configuration is ${JSON.stringify( + functionConfiguration + )}. waiting for active state...` + ); + await lambda + .waitFor('functionActive', { FunctionName: functionName }) + .promise(); + printVersion(functionConfiguration); + gutil.log( + `updating alias "${opts.alias.name}" for function "${functionName}"` + ); + await updateOrCreateAlias(functionConfiguration); + gutil.log(`waiting for update to complete...`); + await lambda + .waitFor('functionUpdated', { FunctionName: functionName }) + .promise(); + const { + Description, + FunctionName, + Handler, + MemorySize, + Role, + Runtime, + Timeout + } = existingParams; + const newParams = { + Description, + FunctionName, + Handler, + MemorySize, + Role, + Runtime, + Timeout, + ...params + }; + delete newParams.Code; + gutil.log('updating function configuration...'); + await lambda.updateFunctionConfiguration(newParams).promise(); + done(); + } catch (error) { + done(error); + } + } catch (error) { + gutil.log( + `failed to get configuration for "${functionName}". ${error}` + ); + // Creating a function + try { + gutil.log(`creating function "${functionName}"`); + functionConfiguration = await createFunction( + lambda, + toUpload, + params, + opts + ); + gutil.log( + `function configuration is ${JSON.stringify( + functionConfiguration + )}. waiting for active state...` + ); + await lambda + .waitFor('functionActive', { FunctionName: functionName }) + .promise(); + printVersion(functionConfiguration); + await updateOrCreateAlias(functionConfiguration); + done(); + } catch (error) { + done(error); + } + } + } + } -module.exports = function(params, opts) { - opts = extend(DEFAULT_OPTS, opts); - - AWS.config.update({ region: opts.region }); - var lambda = new AWS.Lambda(); - var toUpload; - var functionName = typeof params === 'string' ? params : params.FunctionName; - - var updateOrCreateAlias = function(response) { - if (opts.publish && opts.alias) { - lambda.getAlias({ - FunctionName: functionName, - Name: opts.alias.name - }, function(err) { - var operation = err ? 'create' : 'update'; - upsertAlias(operation, lambda, functionName, - (opts.alias.version || response.data.Version).toString(), - opts.alias.name, - opts.alias.description); - }); - } - }; - var printVersion = function(response) { - if (opts.publish) { - gutil.log('Publishing Function Version: ' + gutil.colors.magenta(response.data.Version)); - } - }; - var successfulUpdate = function(response) { - printVersion(response); - updateOrCreateAlias(response); - }; - var successfulCreation = function(response) { - printVersion(response); - updateOrCreateAlias(response); - }; - - var transform = function(file, enc, cb) { - if (file.isNull()) { - return cb(); - } - if (file.isStream()) { - return cb(makeErr('Streaming is not supported')); - } - if (!toUpload) { - toUpload = file; - } - cb(); - }; - - var flush = function(cb) { - if (!toUpload && (typeof params === 'string' || !params.Code)) { - return cb(makeErr('No code provided')); - } - if (toUpload && toUpload.path.slice(-4) !== '.zip') { - return cb(makeErr('Provided file is not a ZIP')); - } - if (opts.alias) { - if (!opts.alias.name) { - return cb(makeErr('Alias requires a ' + gutil.colors.red('name') + ' parameter')); - } else if (!(typeof opts.alias.name === 'string')) { - return cb(makeErr('Alias ' + gutil.colors.red('name') + ' must be a string')); - } - } - - gutil.log('Uploading Lambda function "' + functionName + '"...'); - - if (opts.profile !== null) { - AWS.config.credentials = new AWS.SharedIniFileCredentials({ profile: opts.profile }); - } - - var stream = this; - - var done = function(err) { - if (err) { - return cb(makeErr(err.message)); - } - gutil.log('Lambda function "' + functionName + '" successfully uploaded'); - stream.push(toUpload); - cb(); - }; - - if (typeof params === 'string') { - // Just updating code - updateFunctionCode(lambda, params, toUpload, params, opts) - .on('success', successfulUpdate) - .send(done); - } else { - lambda.getFunctionConfiguration({ - FunctionName: params.FunctionName - }, function(err) { - if (err) { - // Creating a function - createFunction(lambda, toUpload, params, opts) - .on('success', successfulCreation) - .send(done); - } else { - // Updating code + config - var runtime = params.Runtime; - updateFunctionCode(lambda, params.FunctionName, toUpload, params, opts) - .on('success', successfulUpdate) - .send(function() { - delete params.Code; - if (runtime) { - params.Runtime = runtime; - } - lambda.updateFunctionConfiguration(params, done); - }); - } - }); - } - }; - - return through.obj(transform, flush); + return through.obj(transform, flush); }; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6a7bf29 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,102 @@ +{ + "name": "gulp-awslambda", + "version": "0.5.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "aws-sdk": { + "version": "2.1023.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1023.0.tgz", + "integrity": "sha512-RAI8sUfK+00yL9i3xz5kbM3+t/0mjjnKhKyauXAlJN4seDYtIX5+BqMghpkZwvLBdi6idXIuz+FHWETHZccyuA==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } +} diff --git a/package.json b/package.json index 1ec20d8..78d7d49 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "url": "https://github.com/willyg302/gulp-awslambda/issues" }, "dependencies": { - "aws-sdk": "^2.121.0", + "aws-sdk": "^2.1023.0", "gulp-util": "^3.0.7", "through2": "^2.0.1", "xtend": "^4.0.1"