From 7ff6d760769e3cdb150e12eb89143d49178a15f6 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 16 Dec 2020 17:55:02 +0900 Subject: [PATCH] feat(cli): improve deployer selection fixes #18 --- src/action_builder.js | 111 +++++++++++++++++++------------- src/cli.js | 11 +++- src/deploy/AWSDeployer.js | 15 ++++- src/deploy/AzureDeployer.js | 7 ++ src/deploy/BaseDeployer.js | 6 ++ src/deploy/OpenWhiskDeployer.js | 7 ++ test/build.test.js | 6 +- test/cli.test.js | 17 +++-- test/deploy.test.js | 30 ++++++--- 9 files changed, 143 insertions(+), 67 deletions(-) diff --git a/src/action_builder.js b/src/action_builder.js index cb79739e..f0253209 100755 --- a/src/action_builder.js +++ b/src/action_builder.js @@ -198,6 +198,7 @@ module.exports = class ActionBuilder { _gitRef: '', _updatedAt: null, _updatedBy: null, + _target: [], _deployers: { wsk: new OpenWhiskDeployer(this), aws: new AWSDeployer(this), @@ -231,6 +232,16 @@ module.exports = class ActionBuilder { return this; } + withTarget(value) { + this._target = []; + value.forEach((v) => { + v.split(',').forEach((t) => { + this._target.push(t.trim()); + }); + }); + return this; + } + withDeploy(enable) { this._deploy = enable; return this; @@ -597,7 +608,7 @@ module.exports = class ActionBuilder { // create dist dir await fse.ensureDir(this._distDir); - // init openwhisk props + // init deployers await Promise.all(Object.values(this._deployers) .filter((deployer) => !deployer.ready()) .filter((deployer) => typeof deployer.init === 'function') @@ -621,6 +632,33 @@ module.exports = class ActionBuilder { this._showHints = false; this._links = []; } + + // init deployers + const targets = { }; + this._target.forEach((t) => { + if (t === 'auto') { + // get all deployers that are ready(); + Object.entries(this._deployers).forEach(([name, deployer]) => { + if (deployer.ready()) { + targets[name] = deployer; + } + }); + } else { + // deployer must be ready + const deployer = this._deployers[t]; + if (!deployer) { + throw Error(`'No such target: ${t}`); + } + deployer.validate(); + targets[t] = deployer; + } + }); + this._deployers = targets; + if (Object.keys(targets).length === 0) { + if (this._deploy || this._test || this._delete || this._updatePackage) { + throw new Error('No applicable deployers found'); + } + } } async createArchive() { @@ -831,67 +869,46 @@ module.exports = class ActionBuilder { this.log.info(chalk`{green ok:} bundle can be loaded and has a {gray main()} function.`); } - async deploy() { - const results = await Promise.all(Object.entries(this._deployers) - .filter(([name]) => this._deploy.length === 0 - || this._deploy.indexOf('all') >= 0 - || this._deploy.indexOf(name) >= 0) - .filter(([, deployer]) => deployer.ready()) - .map(async ([, deployer]) => { - try { - return deployer.deploy(); - } catch (e) { - return e; - } - })); - - const errors = results.filter((result) => result instanceof Error); - if (errors.length) { - throw errors[0]; - } - if (!results.length) { - throw Error('No applicable deployers found'); + async execute(fnName, msg) { + const deps = Object.values(this._deployers) + .filter((deployer) => typeof deployer[fnName] === 'function'); + // eslint-disable-next-line no-restricted-syntax + for (const dep of deps) { + this.log.info(chalk`--: ${msg}{yellow ${dep.name}} ...`); + // eslint-disable-next-line no-await-in-loop + await dep[fnName](); } } + async deploy() { + return this.execute('deploy', 'deploying action to '); + } + async updatePackage() { - console.log('updating all packages'); - await Promise.all(await Object.values(this._deployers) - .filter((deployer) => deployer.ready()) - .filter((deployer) => typeof deployer.updatePackage === 'function') - .map(async (deployer) => deployer.updatePackage())); + return this.execute('updatePackage', 'updating package on '); } async showDeployHints() { - await Promise.all(await Object.values(this._deployers) - .filter((deployer) => typeof deployer.showDeployHints === 'function') - .map(async (deployer) => deployer.showDeployHints())); + return this.execute('showDeployHints', 'hints for '); } async delete() { - await Promise.all(await Object.values(this._deployers) - .filter((deployer) => deployer.ready()) - .filter((deployer) => typeof deployer.delete === 'function') - .map(async (deployer) => deployer.delete())); + return this.execute('delete', 'deleting action on '); } async test() { - await Promise.all(Object.values(this._deployers) - .filter((deployer) => deployer.ready()) - .filter((deployer) => typeof deployer.test === 'function') - .map(async (deployer) => deployer.test())); + return this.execute('test', 'testing action on '); } async updateLinks() { - await Promise.all(await Object.values(this._deployers) - .filter((deployer) => deployer.ready()) - .filter((deployer) => typeof deployer.updateLinks === 'function') - .map(async (deployer) => deployer.updateLinks())); + return this.execute('updateLinks', 'updating links on '); } async run() { this.log.info(chalk`{grey openwhisk-action-builder v${version}}`); await this.validate(); + this.log.info(chalk`selected targets: {yellow ${Object.values(this._deployers).map((d) => d.name).join(', ')}}`); + if (this._build) { await this.createPackage(); await this.createArchive(); @@ -919,9 +936,13 @@ module.exports = class ActionBuilder { await this.updateLinks(); - return { - name: `openwhisk;host=${this._deployers.wsk.host}`, - url: this._deployers.wsk.fullFunctionName, - }; + return Object.entries(this._deployers).reduce((p, [name, dep]) => { + // eslint-disable-next-line no-param-reassign + p[name] = { + name: `${dep.name.toLowerCase()};host=${dep.host}`, + url: dep.fullFunctionName, + }; + return p; + }, {}); } }; diff --git a/src/cli.js b/src/cli.js index e2264a8d..c7f57b07 100755 --- a/src/cli.js +++ b/src/cli.js @@ -39,11 +39,17 @@ class CLI { type: 'string', default: '.', }) - .option('deploy', { - description: 'Automatically deploy to specified targets (wsk,aws,azure,google,all)', + .option('target', { + description: 'Select target(s) for test, deploy, update-package actions (wsk,aws,azure,google,auto)', type: 'string', + default: ['auto'], array: true, }) + .option('deploy', { + description: 'Automatically deploy to specified targets', + type: 'boolean', + default: false, + }) .option('test', { description: 'Invoke action after deployment. Can be relative url.', type: 'string', @@ -233,6 +239,7 @@ class CLI { return this.createBuilder() .verbose(argv.verbose) .withDirectory(argv.directory) + .withTarget(argv.target) .withBuild(argv.build) .withDelete(argv.delete) .withDeploy(argv.deploy) diff --git a/src/deploy/AWSDeployer.js b/src/deploy/AWSDeployer.js index c53cc93c..99a74259 100644 --- a/src/deploy/AWSDeployer.js +++ b/src/deploy/AWSDeployer.js @@ -54,6 +54,7 @@ class AWSDeployer extends BaseDeployer { super(builder); Object.assign(this, { + name: 'AWS', _region: '', _role: '', _functionARN: '', @@ -85,6 +86,12 @@ class AWSDeployer extends BaseDeployer { return res; } + validate() { + if (!this._role || !this._region) { + throw Error('AWS target needs --aws-region and --aws-role'); + } + } + async init() { this._bucket = `poly-func-maker-temp-${crypto.randomBytes(16).toString('hex')}`; if (this._region) { @@ -260,7 +267,7 @@ class AWSDeployer extends BaseDeployer { const { ApiId, ApiEndpoint } = res; this._apiId = ApiId; - this._functionURL = ApiEndpoint; + this._functionURL = `${ApiEndpoint}/${this._builder.actionName.replace('@', '_')}`; // check for stage res = await this._api.send(new GetStagesCommand({ @@ -315,8 +322,6 @@ class AWSDeployer extends BaseDeployer { StatementId: crypto.randomBytes(16).toString('hex'), })); - this._functionURL += `/${this._builder.actionName.replace('@', '_')}`; - if (this._builder.showHints) { const opts = ''; this.log.info('\nYou can verify the action with:'); @@ -334,6 +339,10 @@ class AWSDeployer extends BaseDeployer { }); } + get fullFunctionName() { + return this._functionURL; + } + async updatePackage() { this.log.info('--: updating app (package) parameters ...'); const commands = Object diff --git a/src/deploy/AzureDeployer.js b/src/deploy/AzureDeployer.js index 01a8f3f9..5649a2a1 100644 --- a/src/deploy/AzureDeployer.js +++ b/src/deploy/AzureDeployer.js @@ -22,6 +22,7 @@ class AzureDeployer extends BaseDeployer { super(builder); Object.assign(this, { + name: 'Azure', _appName: '', _auth: null, _pubcreds: null, @@ -32,6 +33,12 @@ class AzureDeployer extends BaseDeployer { return !!this._appName && !!this._auth; } + validate() { + if (!this.ready()) { + throw Error('Azure target needs --azure-app'); + } + } + withAzureApp(value) { this._appName = value; return this; diff --git a/src/deploy/BaseDeployer.js b/src/deploy/BaseDeployer.js index fbfa2641..ab080102 100644 --- a/src/deploy/BaseDeployer.js +++ b/src/deploy/BaseDeployer.js @@ -33,6 +33,12 @@ class BaseDeployer { return this._builder && false; } + validate() { + if (!this.ready()) { + throw Error(`${this.name} target not valid.`); + } + } + get relZip() { return path.relative(process.cwd(), this._builder.zipFile); } diff --git a/src/deploy/OpenWhiskDeployer.js b/src/deploy/OpenWhiskDeployer.js index 1b5e991d..c897723f 100644 --- a/src/deploy/OpenWhiskDeployer.js +++ b/src/deploy/OpenWhiskDeployer.js @@ -24,6 +24,7 @@ class OpenWhiskDeployer extends BaseDeployer { super(builder); Object.assign(this, { + name: 'Openwhisk', _packageName: '', _namespace: '', _packageShared: false, @@ -68,6 +69,12 @@ class OpenWhiskDeployer extends BaseDeployer { return !!this._wskApiHost && !!this._wskAuth && !!this._wskNamespace; } + validate() { + if (!this.ready()) { + throw Error('Openwhisk target needs --wsk-host, --wsk-auth and --wsk-namespace'); + } + } + getOpenwhiskClient() { if (!this._wskApiHost || !this._wskAuth || !this._wskNamespace) { throw Error(chalk`\nMissing OpenWhisk credentials. Make sure you have a {grey .wskprops} in your home directory.\nYou can also set {grey WSK_NAMESPACE}, {gray WSK_AUTH} and {gray WSK_API_HOST} environment variables.`); diff --git a/test/build.test.js b/test/build.test.js index d9726258..6ea11fab 100644 --- a/test/build.test.js +++ b/test/build.test.js @@ -87,8 +87,10 @@ describe('Build Test', () => { const res = await builder.run(); assert.deepEqual(res, { - name: 'openwhisk;host=https://example.com', - url: '/foobar/simple-package/simple-name@1.45', + wsk: { + name: 'openwhisk;host=https://example.com', + url: '/foobar/simple-package/simple-name@1.45', + }, }); await assertZipEntries(path.resolve(testRoot, 'dist', 'simple-package', 'simple-name@1.45.zip'), [ diff --git a/test/cli.test.js b/test/cli.test.js index 93dd048b..ef9b6703 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -22,7 +22,8 @@ describe('CLI Test', () => { it('has correct defaults with no arguments', () => { const builder = new CLI().prepare(); assert.equal(builder._verbose, false); - assert.equal(builder._deploy, undefined); + assert.equal(builder._deploy, false); + assert.deepEqual(builder._target, ['auto']); assert.equal(builder._build, true); assert.equal(builder._test, undefined); assert.equal(builder._showHints, true); @@ -50,13 +51,19 @@ describe('CLI Test', () => { it('sets deploy flag', () => { const builder = new CLI() .prepare(['--deploy']); - assert.deepEqual(builder._deploy, []); + assert.deepEqual(builder._deploy, true); }); - it('sets deploy targets', () => { + it('sets targets', () => { const builder = new CLI() - .prepare(['--deploy=aws', '--deploy=wsk']); - assert.deepEqual(builder._deploy, ['aws', 'wsk']); + .prepare(['--target=aws', '--target=wsk']); + assert.deepEqual(builder._target, ['aws', 'wsk']); + }); + + it('sets targets with csv', () => { + const builder = new CLI() + .prepare(['--target=aws,wsk']); + assert.deepEqual(builder._target, ['aws', 'wsk']); }); it('clears build flag', () => { diff --git a/test/deploy.test.js b/test/deploy.test.js index 087e884b..f0fea01e 100644 --- a/test/deploy.test.js +++ b/test/deploy.test.js @@ -98,8 +98,10 @@ describe('Deploy Test', () => { const res = await builder.run(); assert.deepEqual(res, { - name: 'openwhisk;host=https://example.com', - url: '/foobar/default/simple-project', + wsk: { + name: 'openwhisk;host=https://example.com', + url: '/foobar/default/simple-project', + }, }); const out = builder._logger.output; @@ -126,8 +128,10 @@ describe('Deploy Test', () => { const res = await builder.run(); assert.deepEqual(res, { - name: 'openwhisk;host=https://example.com', - url: '/foobar/default/simple-project', + wsk: { + name: 'openwhisk;host=https://example.com', + url: '/foobar/default/simple-project', + }, }); const out = builder._logger.output; @@ -156,8 +160,10 @@ describe('Deploy Test', () => { const res = await builder.run(); assert.deepEqual(res, { - name: 'openwhisk;host=https://example.com', - url: '/foobar/test-package/simple-project', + wsk: { + name: 'openwhisk;host=https://example.com', + url: '/foobar/test-package/simple-project', + }, }); const out = builder._logger.output; @@ -193,8 +199,10 @@ describe('Deploy Test', () => { const res = await builder.run(); assert.deepEqual(res, { - name: 'openwhisk;host=https://example.com', - url: '/foobar/default/simple-project', + wsk: { + name: 'openwhisk;host=https://example.com', + url: '/foobar/default/simple-project', + }, }); const out = builder._logger.output; @@ -222,8 +230,10 @@ describe('Deploy Test', () => { const res = await builder.run(); assert.deepEqual(res, { - name: 'openwhisk;host=https://example.com', - url: '/foobar/test-package/simple-project', + wsk: { + name: 'openwhisk;host=https://example.com', + url: '/foobar/default/simple-project', + }, }); const out = builder._logger.output;