From 2686c41c25be2d64e633cdca7c25b8e239bc0a8a Mon Sep 17 00:00:00 2001 From: Lars Trieloff Date: Mon, 7 Dec 2020 16:05:34 +0000 Subject: [PATCH] feat(aws): deploy to lambda and create version tags --- src/action_builder.js | 6 ++ src/cli.js | 6 ++ src/deploy/AWSDeployer.js | 121 +++++++++++++++++++++++++++++++++++++- test/aws.integration.js | 4 +- 4 files changed, 133 insertions(+), 4 deletions(-) diff --git a/src/action_builder.js b/src/action_builder.js index 8c7316d..f74a2bf 100755 --- a/src/action_builder.js +++ b/src/action_builder.js @@ -444,6 +444,12 @@ module.exports = class ActionBuilder { return this; } + withAWSRole(value) { + // propagate AWS region + this._deployers.aws.withAWSRole(value); + return this; + } + get testPath() { return this._test; } diff --git a/src/cli.js b/src/cli.js index 2832b53..e4d15f0 100755 --- a/src/cli.js +++ b/src/cli.js @@ -148,6 +148,11 @@ class CLI { type: 'string', default: '', }) + .option('aws-role', { + description: 'the AWS role ARN to execute lambda functions with', + type: 'string', + default: '', + }) .group([ 'name', 'node-version', 'params', 'params-file', 'web-secure', 'namespace', 'timeout', 'updated-by', 'updated-at'], 'OpenWhisk Action Options') @@ -242,6 +247,7 @@ class CLI { .withLinks(argv.versionLink) .withLinksPackage(argv.linksPackage) .withAWSRegion(argv.awsRegion) + .withAWSRole(argv.awsRole) .withPackageShared(argv.package.shared); } diff --git a/src/deploy/AWSDeployer.js b/src/deploy/AWSDeployer.js index 394209a..854ec69 100644 --- a/src/deploy/AWSDeployer.js +++ b/src/deploy/AWSDeployer.js @@ -10,8 +10,23 @@ * governing permissions and limitations under the License. */ const { - S3Client, CreateBucketCommand, PutObjectCommand, DeleteBucketCommand, DeleteObjectCommand, + S3Client, + CreateBucketCommand, + PutObjectCommand, + DeleteBucketCommand, + DeleteObjectCommand, } = require('@aws-sdk/client-s3'); +const { + LambdaClient, + CreateFunctionCommand, + GetFunctionCommand, + UpdateFunctionConfigurationCommand, + UpdateFunctionCodeCommand, + GetAliasCommand, + PublishVersionCommand, + CreateAliasCommand, + UpdateAliasCommand, +} = require('@aws-sdk/client-lambda'); const path = require('path'); const fse = require('fs-extra'); const crypto = require('crypto'); @@ -23,6 +38,7 @@ class AWSDeployer extends BaseDeployer { Object.assign(this, { _region: '', + _role: '', }); } @@ -31,8 +47,14 @@ class AWSDeployer extends BaseDeployer { return this; } + withAWSRole(value) { + this._role = value; + return this; + } + ready() { - return !!this._region && !!this._s3; + const res = !!this._region && !!this._s3 && !!this._role && !!this._lambda; + return res; } async init() { @@ -40,6 +62,9 @@ class AWSDeployer extends BaseDeployer { this._s3 = new S3Client({ region: this._region, }); + this._lambda = new LambdaClient({ + region: this._region, + }); } async createS3Bucket() { @@ -76,11 +101,101 @@ class AWSDeployer extends BaseDeployer { this.log.info(`Bucket ${this._bucket} emptied and deleted`); } + async createLambda() { + const functionName = `${this._builder.packageName}--${this._builder.name.replace(/@.*/g, '')}`; + const functionVersion = this._builder.name.replace(/.*@/g, '').replace(/\./g, '_'); + + const functionConfig = { + Code: { + S3Bucket: this._bucket, + S3Key: this._key, + }, + // todo: package name + FunctionName: functionName, + Role: this._role, + Runtime: `nodejs${this._builder.nodeVersion}.x`, + // todo: cram annotations into description? + Tags: { + pkgVersion: this._builder.version, + dependencies: this._builder.dependencies.main.map((dep) => `${dep.name}:${dep.version}`).join(','), + repository: encodeURIComponent(this._builder.gitUrl).replace(/%/g, '@'), + git: encodeURIComponent(`${this._builder.gitOrigin}#${this._builder.gitRef}`).replace(/%/g, '@'), + updated: `${this._builder.updatedAt}`, + }, + Description: this._builder.pkgJson.description, + MemorySize: this._builder.memory, + Timeout: Math.floor(this._builder.timeout / 1000), + // todo: what about package params? + Environment: { + Variables: this._builder.params, + }, + Handler: 'index.lambda', + }; + + try { + this.log.info(`Updating existing Lambda function ${functionName}`); + await this._lambda.send(new GetFunctionCommand({ + FunctionName: functionName, + })); + + const updatecode = this._lambda.send(new UpdateFunctionCodeCommand({ + FunctionName: functionName, + ...functionConfig.Code, + })); + const updateconfig = this._lambda.send( + new UpdateFunctionConfigurationCommand(functionConfig), + ); + + await updateconfig; + await updatecode; + } catch (e) { + if (e.name === 'ResourceNotFoundException') { + this.log.info(`Creating new Lambda function ${functionName}`); + await this._lambda.send(new CreateFunctionCommand(functionConfig)); + } else { + this.log.error(`Unable to verify existence of Lambda function ${functionName}`); + throw e; + } + } + + const versiondata = await this._lambda.send(new PublishVersionCommand({ + FunctionName: functionName, + })); + + const versionNum = versiondata.Version; + try { + await this._lambda.send(new GetAliasCommand({ + FunctionName: functionName, + Name: functionVersion, + })); + + this.log.info(`Updating existing alias ${functionName}:${functionVersion} to v${versionNum}`); + await this._lambda.send(new UpdateAliasCommand({ + FunctionName: functionName, + Name: functionVersion, + FunctionVersion: versionNum, + + })); + } catch (e) { + if (e.name === 'ResourceNotFoundException') { + this.log.info(`Creating new alias ${functionName}:${functionVersion} at v${versionNum}`); + await this._lambda.send(new CreateAliasCommand({ + FunctionName: functionName, + Name: functionVersion, + FunctionVersion: versionNum, + })); + } else { + this.log.error(`Unable to verify existence of Lambda alias ${functionName}:${functionVersion}`); + throw e; + } + } + } + async deploy() { try { await this.createS3Bucket(); await this.uploadZIP(); - // await this.createLambda(); + await this.createLambda(); await this.deleteS3Bucket(); } catch (err) { this.log.error(`Unable to deploy Lambda function: ${err.message}`, err); diff --git a/test/aws.integration.js b/test/aws.integration.js index 7558503..41b4488 100644 --- a/test/aws.integration.js +++ b/test/aws.integration.js @@ -42,6 +42,8 @@ describe('AWS Integration Test', () => { '--verbose', '--deploy', '--aws-region', 'us-east-1', + '--aws-role', 'arn:aws:iam::320028119408:role/lambda-role', + '-p', 'FOO=bar', '--test', '/foo', '--directory', testRoot, '--entryFile', 'index.js', @@ -53,5 +55,5 @@ describe('AWS Integration Test', () => { const out = builder._logger.output; assert.ok(out.indexOf(`ok: 200 Hello, world.`) > 0, out); - }).timeout(10000); + }).timeout(50000); });