@contrail/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +6 -0
- package/bin/init +4 -0
- package/lib/auth/login.d.ts +1 -0
- package/lib/auth/login.js +79 -0
- package/lib/cli.d.ts +5 -0
- package/lib/cli.js +30 -0
- package/lib/create-app/create.d.ts +1 -0
- package/lib/create-app/create.js +48 -0
- package/lib/create-app/setup-template.d.ts +1 -0
- package/lib/create-app/setup-template.js +48 -0
- package/lib/deploy-app/actions-deployer.d.ts +10 -0
- package/lib/deploy-app/actions-deployer.js +78 -0
- package/lib/deploy-app/actions.d.ts +5 -0
- package/lib/deploy-app/actions.js +17 -0
- package/lib/deploy-app/app-declaration.d.ts +20 -0
- package/lib/deploy-app/app-declaration.js +0 -0
- package/lib/deploy-app/app-deployer.d.ts +9 -0
- package/lib/deploy-app/app-deployer.js +58 -0
- package/lib/deploy-app/app.d.ts +8 -0
- package/lib/deploy-app/app.js +56 -0
- package/lib/deploy-app/deploy.d.ts +2 -0
- package/lib/deploy-app/deploy.js +30 -0
- package/lib/deploy-app/interfaces.d.ts +34 -0
- package/lib/deploy-app/interfaces.js +0 -0
- package/package.json +63 -0
- package/src/auth/login.ts +89 -0
- package/src/cli.ts +33 -0
- package/src/create-app/create.ts +58 -0
- package/src/create-app/setup-template.ts +61 -0
- package/src/deploy-app/actions-deployer.ts +87 -0
- package/src/deploy-app/app-deployer.ts +63 -0
- package/src/deploy-app/deploy.ts +34 -0
- package/src/deploy-app/interfaces.ts +35 -0
- package/templates/javascript/actions/example/action.js +38 -0
- package/templates/javascript/app.yml +42 -0
- package/templates/javascript/package.json +23 -0
- package/tsconfig.json +23 -0
- package/tslint.json +57 -0
package/bin/init
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function authUser(): Promise<unknown>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { setConfig } from '@contrail/sdk';
|
|
4
|
+
const CONFIGURATION = {
|
|
5
|
+
userPoolId: 'us-east-1_x8hXZUsL6',
|
|
6
|
+
clientId: process.env.CONTRAIL_COGNITO_CLIENT_ID || '2oubek96sue5rsncovtqhs3scm',
|
|
7
|
+
};
|
|
8
|
+
export async function authUser() {
|
|
9
|
+
const questions = [];
|
|
10
|
+
if (!process.env.CONTRAIL_CLI_EMAIL) {
|
|
11
|
+
questions.push({
|
|
12
|
+
type: 'input',
|
|
13
|
+
name: 'email',
|
|
14
|
+
validate: (val) => !!val,
|
|
15
|
+
message: 'Email',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (!process.env.CONTRAIL_CLI_PASSWORD) {
|
|
19
|
+
questions.push({
|
|
20
|
+
type: 'password',
|
|
21
|
+
name: 'password',
|
|
22
|
+
validate: (val) => !!val,
|
|
23
|
+
message: 'Password',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (!process.env.CONTRAIL_CLI_ORGSLUG) {
|
|
27
|
+
questions.push({
|
|
28
|
+
type: 'input',
|
|
29
|
+
name: 'orgSlug',
|
|
30
|
+
validate: (val) => !!val,
|
|
31
|
+
message: 'Org (slug)',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const answers = await inquirer.prompt(questions);
|
|
35
|
+
const params = {
|
|
36
|
+
email: process.env.CONTRAIL_CLI_EMAIL || answers.email,
|
|
37
|
+
password: process.env.CONTRAIL_CLI_PASSWORD || answers.password,
|
|
38
|
+
orgSlug: process.env.CONTRAIL_CLI_ORGSLUG || answers.orgSlug,
|
|
39
|
+
};
|
|
40
|
+
if (!params.email || !params.password) {
|
|
41
|
+
throw new Error('email and password required');
|
|
42
|
+
}
|
|
43
|
+
const poolData = {
|
|
44
|
+
UserPoolId: CONFIGURATION.userPoolId,
|
|
45
|
+
ClientId: CONFIGURATION.clientId,
|
|
46
|
+
};
|
|
47
|
+
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
|
|
48
|
+
const authData = {
|
|
49
|
+
Username: params.email,
|
|
50
|
+
Password: params.password,
|
|
51
|
+
};
|
|
52
|
+
const authDetails = new AmazonCognitoIdentity.AuthenticationDetails(authData);
|
|
53
|
+
const userData = {
|
|
54
|
+
Username: params.email,
|
|
55
|
+
Pool: userPool,
|
|
56
|
+
};
|
|
57
|
+
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
cognitoUser.authenticateUser(authDetails, {
|
|
60
|
+
onSuccess: async (result) => {
|
|
61
|
+
const user = result;
|
|
62
|
+
await setSDKConfig(user, params.orgSlug);
|
|
63
|
+
resolve({ success: true, data: result });
|
|
64
|
+
},
|
|
65
|
+
onFailure: (err) => {
|
|
66
|
+
console.log('authUser error: ' + err);
|
|
67
|
+
reject(err);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function setSDKConfig(user, orgSlug) {
|
|
73
|
+
// console.log('user:', user);
|
|
74
|
+
setConfig({
|
|
75
|
+
apiUserToken: user.idToken.jwtToken,
|
|
76
|
+
apiGateway: process.env.API_GATEWAY,
|
|
77
|
+
orgSlug,
|
|
78
|
+
});
|
|
79
|
+
}
|
package/lib/cli.d.ts
ADDED
package/lib/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { create } from './create-app/create';
|
|
2
|
+
import { deployApp, removeApp } from './deploy-app/deploy';
|
|
3
|
+
/**
|
|
4
|
+
* Built using documentation here:
|
|
5
|
+
* https://www.twilio.com/blog/how-to-build-a-cli-with-node-js
|
|
6
|
+
*/
|
|
7
|
+
export async function cli(args) {
|
|
8
|
+
const command = args[2];
|
|
9
|
+
switch (command) {
|
|
10
|
+
case 'create-app': {
|
|
11
|
+
create(args);
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
case 'deploy-app': {
|
|
15
|
+
deployApp(args);
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
case 'remove-app': {
|
|
19
|
+
removeApp(args);
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
case 'extract-types': {
|
|
23
|
+
removeApp(args);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
default: {
|
|
27
|
+
console.log('Illegal command.');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function create(args: any): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import arg from 'arg';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { setUpTemplate } from './setup-template';
|
|
5
|
+
function parseArgumentsIntoOptions(rawArgs) {
|
|
6
|
+
const args = arg({
|
|
7
|
+
'--create-app': Boolean,
|
|
8
|
+
'--app-name': String,
|
|
9
|
+
'-c': '--create-app',
|
|
10
|
+
'-n': '--app-name',
|
|
11
|
+
}, {
|
|
12
|
+
argv: rawArgs.slice(2),
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
createApp: args['--create-app'] || false,
|
|
16
|
+
appName: args['--app-name']
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async function promptForMissingOptions(options) {
|
|
20
|
+
const defaultTemplate = 'JavaScript';
|
|
21
|
+
if (options.skipPrompts) {
|
|
22
|
+
return {
|
|
23
|
+
...options,
|
|
24
|
+
template: options.template || defaultTemplate,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const questions = [];
|
|
28
|
+
if (!options.appName) {
|
|
29
|
+
questions.push({
|
|
30
|
+
type: 'input',
|
|
31
|
+
name: 'appName',
|
|
32
|
+
validate: (val) => { return !!val; },
|
|
33
|
+
message: 'What is the name of your app?',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const answers = await inquirer.prompt(questions);
|
|
37
|
+
return {
|
|
38
|
+
...options,
|
|
39
|
+
appName: options.appName || answers.appName,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function create(args) {
|
|
43
|
+
let options = parseArgumentsIntoOptions(args);
|
|
44
|
+
options = await promptForMissingOptions(options);
|
|
45
|
+
console.log("%s Setting up new new contrail application.", chalk.blue.bold('START'));
|
|
46
|
+
await setUpTemplate();
|
|
47
|
+
console.log('%s Application initialized successfully.', chalk.green.bold('COMPLETE'));
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setUpTemplate(): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import ncp from 'ncp';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import execa from 'execa';
|
|
5
|
+
const Listr = require('listr');
|
|
6
|
+
import { projectInstall } from 'pkg-install';
|
|
7
|
+
const copy = promisify(ncp);
|
|
8
|
+
const TARGET_DIR = process.cwd();
|
|
9
|
+
const currentFileUrl = import.meta.url;
|
|
10
|
+
const TEMPLATE_DIR = path.resolve(new URL(currentFileUrl).pathname, '../../../templates/javascript').replace(/\\/g, '\\').replace(/C:\\C:/g, '');
|
|
11
|
+
// console.log("TEMPLATE_DIR: ", TEMPLATE_DIR)
|
|
12
|
+
// console.log("TARGET_DIR: ", TARGET_DIR)
|
|
13
|
+
function copyTemplate() {
|
|
14
|
+
return copy(TEMPLATE_DIR, TARGET_DIR, {
|
|
15
|
+
clobber: false,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function npmInstall() {
|
|
19
|
+
async function initGit(options) {
|
|
20
|
+
const result = await execa('git', ['init'], {
|
|
21
|
+
cwd: options.targetDirectory,
|
|
22
|
+
});
|
|
23
|
+
if (result.failed) {
|
|
24
|
+
return Promise.reject(new Error('Failed to initialize git'));
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function setUpTemplate() {
|
|
30
|
+
try {
|
|
31
|
+
const tasks = new Listr([
|
|
32
|
+
{
|
|
33
|
+
title: 'Copy template files',
|
|
34
|
+
task: () => copyTemplate(),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
title: 'Install dependencies',
|
|
38
|
+
task: () => projectInstall({
|
|
39
|
+
cwd: TARGET_DIR,
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
await tasks.run();
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
console.log('Error copying template: ', e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { App, AppDeclartion, AppActionDeclaration } from './interfaces';
|
|
2
|
+
export declare class ActionsDeployer {
|
|
3
|
+
deployActions(appDeclaration: AppDeclartion, app: App): Promise<void>;
|
|
4
|
+
getActionsForApp(app: App): Promise<any>;
|
|
5
|
+
deployAction(actionKey: string, actionDeclaration: AppActionDeclaration, app: App, existingActions: Array<any>): Promise<void>;
|
|
6
|
+
private createNewActionDefinition;
|
|
7
|
+
private updateActionDefinition;
|
|
8
|
+
getActionDeclarationIdentifier(actionKey: any, app: App): string;
|
|
9
|
+
getFunction(actionDeclaration: AppActionDeclaration): Promise<string>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Entities } from '@contrail/sdk';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
export class ActionsDeployer {
|
|
5
|
+
async deployActions(appDeclaration, app) {
|
|
6
|
+
// FETCH ALL ACTIONS FROM THE API.
|
|
7
|
+
const existingActions = await this.getActionsForApp(app);
|
|
8
|
+
console.log('existingActions: ', existingActions);
|
|
9
|
+
// UPSERT / DELETE BASED ON ACTION IDENTIFIER
|
|
10
|
+
console.log('%s \tDeploying actions.', chalk.yellow.bold('ACTION'));
|
|
11
|
+
for (const actionKey of Object.keys(appDeclaration.actions)) {
|
|
12
|
+
await this.deployAction(actionKey, appDeclaration.actions[actionKey], app, existingActions);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async getActionsForApp(app) {
|
|
16
|
+
return await new Entities().get({ entityName: 'event-workflow-action-definition',
|
|
17
|
+
criteria: { ownerKey: `app:${app.id}` } });
|
|
18
|
+
}
|
|
19
|
+
async deployAction(actionKey, actionDeclaration, app, existingActions) {
|
|
20
|
+
console.log(`%s \tDeploying action: ${actionKey}`, chalk.yellow.bold('ACTION'));
|
|
21
|
+
const identifier = this.getActionDeclarationIdentifier(actionKey, app);
|
|
22
|
+
const existingDefinition = existingActions.find(ad => ad.identifier === identifier);
|
|
23
|
+
if (!existingDefinition) {
|
|
24
|
+
await this.createNewActionDefinition(actionKey, actionDeclaration, app);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
await this.updateActionDefinition(existingDefinition, actionDeclaration);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async createNewActionDefinition(actionKey, actionDeclaration, app) {
|
|
31
|
+
const newObject = {
|
|
32
|
+
identifier: this.getActionDeclarationIdentifier(actionKey, app),
|
|
33
|
+
name: actionDeclaration.name,
|
|
34
|
+
ownerKey: `app:${app.id}`,
|
|
35
|
+
public: !app.private,
|
|
36
|
+
};
|
|
37
|
+
const functionString = await this.getFunction(actionDeclaration);
|
|
38
|
+
if (functionString) {
|
|
39
|
+
newObject.executeFunctionString = functionString;
|
|
40
|
+
}
|
|
41
|
+
const actionDefinition = await new Entities().create({
|
|
42
|
+
entityName: 'event-workflow-action-definition',
|
|
43
|
+
object: newObject
|
|
44
|
+
});
|
|
45
|
+
console.log(`%s Created new action defintion: ${actionKey}`, chalk.yellow.bold('APP'));
|
|
46
|
+
return actionDefinition;
|
|
47
|
+
}
|
|
48
|
+
async updateActionDefinition(existingActionDefinition, actionDeclaration) {
|
|
49
|
+
const updates = {
|
|
50
|
+
name: actionDeclaration.name,
|
|
51
|
+
};
|
|
52
|
+
const functionString = await this.getFunction(actionDeclaration);
|
|
53
|
+
if (functionString) {
|
|
54
|
+
updates.executeFunctionString = functionString;
|
|
55
|
+
}
|
|
56
|
+
const actionDefinition = await new Entities().update({
|
|
57
|
+
entityName: 'event-workflow-action-definition',
|
|
58
|
+
id: existingActionDefinition.id,
|
|
59
|
+
object: updates
|
|
60
|
+
});
|
|
61
|
+
console.log(`%s Updated existingaction defintion: ${actionDefinition.identifier}`, chalk.yellow.bold('APP'));
|
|
62
|
+
return actionDefinition;
|
|
63
|
+
}
|
|
64
|
+
getActionDeclarationIdentifier(actionKey, app) {
|
|
65
|
+
return app.identifier + ':' + actionKey;
|
|
66
|
+
}
|
|
67
|
+
async getFunction(actionDeclaration) {
|
|
68
|
+
if (!actionDeclaration.handler) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const CURRENT_DIR = process.cwd();
|
|
72
|
+
const functionLocaiton = CURRENT_DIR + '\\' + actionDeclaration.handler;
|
|
73
|
+
console.log('getFunction: ', functionLocaiton);
|
|
74
|
+
const content = fs.readFileSync(functionLocaiton);
|
|
75
|
+
console.log('content: ', content.toString());
|
|
76
|
+
return content.toString();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Entities } from '@contrail/sdk';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
export async function deployActions(appDeclaration) {
|
|
4
|
+
// FETCH ALL ACTIONS FROM THE API.
|
|
5
|
+
// UPSERT / DELETE BASED ON ACTION IDENTIFIER
|
|
6
|
+
console.log('%s \tDeploying actions.', chalk.yellow.bold('ACTION'));
|
|
7
|
+
for (const action of Object.keys(appDeclaration.actions)) {
|
|
8
|
+
console.log(`%s \tDeploying action: ${action}`, chalk.yellow.bold('ACTION'));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
async function getActionsForApp() {
|
|
15
|
+
const workspaces = await new Entities().get({ entityName: 'event-workflow-actions' });
|
|
16
|
+
console.log('workspaces: ', workspaces);
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Describes the derivative JSON / object represented
|
|
2
|
+
* in app.yml file.
|
|
3
|
+
*/
|
|
4
|
+
export interface AppDeclartion {
|
|
5
|
+
appIdentifier: string;
|
|
6
|
+
appName: string;
|
|
7
|
+
appDescription?: string;
|
|
8
|
+
visibility: 'private' | 'public';
|
|
9
|
+
publisher: string;
|
|
10
|
+
version: string;
|
|
11
|
+
thumbnail?: string;
|
|
12
|
+
actions?: {
|
|
13
|
+
[key: string]: AppActionDeclaration;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface AppActionDeclaration {
|
|
17
|
+
handler: string;
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AppDeclartion } from './interfaces';
|
|
2
|
+
export declare class AppDeployer {
|
|
3
|
+
private app;
|
|
4
|
+
deploy(appDeclaration: AppDeclartion): Promise<void>;
|
|
5
|
+
remove(appDeclaration: AppDeclartion): Promise<void>;
|
|
6
|
+
getExistingApp(appDeclaration: AppDeclartion): Promise<any>;
|
|
7
|
+
updateExistingApp(appDeclaration: AppDeclartion, existingApp: any): Promise<any>;
|
|
8
|
+
private createNewApp;
|
|
9
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Entities } from '@contrail/sdk';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { ActionsDeployer } from './actions-deployer';
|
|
4
|
+
export class AppDeployer {
|
|
5
|
+
async deploy(appDeclaration) {
|
|
6
|
+
let app = await this.getExistingApp(appDeclaration);
|
|
7
|
+
if (!app) {
|
|
8
|
+
app = await this.createNewApp(appDeclaration);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
app = await this.updateExistingApp(appDeclaration, app);
|
|
12
|
+
}
|
|
13
|
+
this.app = app;
|
|
14
|
+
await new ActionsDeployer().deployActions(appDeclaration, this.app);
|
|
15
|
+
}
|
|
16
|
+
async remove(appDeclaration) {
|
|
17
|
+
let app = await this.getExistingApp(appDeclaration);
|
|
18
|
+
if (app) {
|
|
19
|
+
app = await new Entities().delete({ entityName: 'app', id: app.id });
|
|
20
|
+
console.log(`%s Deleted app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(`%s App not found. Can not remove`, chalk.red.bold('APP'));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async getExistingApp(appDeclaration) {
|
|
27
|
+
const apps = await new Entities().get({ entityName: 'app', criteria: { orgId: '-VMrLJrt3clDKPi0', private: (appDeclaration.visibility === 'private') } });
|
|
28
|
+
// console.log('Existing: ', apps);
|
|
29
|
+
return apps.find(app => app.identifier === appDeclaration.appIdentifier);
|
|
30
|
+
}
|
|
31
|
+
async updateExistingApp(appDeclaration, existingApp) {
|
|
32
|
+
const updates = {
|
|
33
|
+
name: appDeclaration.appName,
|
|
34
|
+
public: appDeclaration.visibility === 'public',
|
|
35
|
+
description: appDeclaration.appDescription,
|
|
36
|
+
publisher: appDeclaration.publisher,
|
|
37
|
+
thumbnail: appDeclaration.thumbnail,
|
|
38
|
+
eventWorkflowTriggerKeyMapping: 'NONE',
|
|
39
|
+
};
|
|
40
|
+
const app = await new Entities().update({ entityName: 'app', id: existingApp.id, object: updates });
|
|
41
|
+
console.log(`%s Updated app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
42
|
+
return app;
|
|
43
|
+
}
|
|
44
|
+
async createNewApp(appDeclaration) {
|
|
45
|
+
const newApp = {
|
|
46
|
+
identifier: appDeclaration.appIdentifier,
|
|
47
|
+
name: appDeclaration.appName,
|
|
48
|
+
public: appDeclaration.visibility === 'public',
|
|
49
|
+
description: appDeclaration.appDescription,
|
|
50
|
+
publisher: appDeclaration.publisher,
|
|
51
|
+
thumbnail: appDeclaration.thumbnail,
|
|
52
|
+
eventWorkflowTriggerKeyMapping: 'NONE',
|
|
53
|
+
};
|
|
54
|
+
const app = await new Entities().create({ entityName: 'app', object: newApp });
|
|
55
|
+
console.log(`%s Created new app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
56
|
+
return app;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AppDeclartion } from './app-declaration';
|
|
2
|
+
export declare class AppDeployer {
|
|
3
|
+
deploy(appDeclaration: AppDeclartion): Promise<void>;
|
|
4
|
+
remove(appDeclaration: AppDeclartion): Promise<void>;
|
|
5
|
+
getExistingApp(appDeclaration: AppDeclartion): Promise<any>;
|
|
6
|
+
updateExistingApp(appDeclaration: AppDeclartion, existingApp: any): Promise<any>;
|
|
7
|
+
private createNewApp;
|
|
8
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Entities } from '@contrail/sdk';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
export class AppDeployer {
|
|
4
|
+
async deploy(appDeclaration) {
|
|
5
|
+
let app = await this.getExistingApp(appDeclaration);
|
|
6
|
+
if (!app) {
|
|
7
|
+
app = await this.createNewApp(appDeclaration);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
app = await this.updateExistingApp(appDeclaration, app);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async remove(appDeclaration) {
|
|
14
|
+
const app = await this.getExistingApp(appDeclaration);
|
|
15
|
+
if (app) {
|
|
16
|
+
await new Entities().delete({ entityName: 'app', id: app.id });
|
|
17
|
+
console.log(`%s Deleted app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(`%s App not found. Can not remove`, chalk.red.bold('APP'));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async getExistingApp(appDeclaration) {
|
|
24
|
+
const apps = await new Entities().get({ entityName: 'app', criteria: { orgId: '-VMrLJrt3clDKPi0', private: (appDeclaration.visibility === 'private') } });
|
|
25
|
+
console.log('Existing: ', apps);
|
|
26
|
+
const existing = apps.find(app => app.identifier === appDeclaration.appIdentifier);
|
|
27
|
+
return existing;
|
|
28
|
+
}
|
|
29
|
+
async updateExistingApp(appDeclaration, existingApp) {
|
|
30
|
+
const updates = {
|
|
31
|
+
name: appDeclaration.appName,
|
|
32
|
+
public: appDeclaration.visibility === 'public',
|
|
33
|
+
description: appDeclaration.appDescription,
|
|
34
|
+
publisher: appDeclaration.publisher,
|
|
35
|
+
thumbnail: appDeclaration.thumbnail,
|
|
36
|
+
eventWorkflowTriggerKeyMapping: 'NONE',
|
|
37
|
+
};
|
|
38
|
+
const app = await new Entities().update({ entityName: 'app', id: existingApp.id, object: updates });
|
|
39
|
+
console.log(`%s Updated app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
40
|
+
return app;
|
|
41
|
+
}
|
|
42
|
+
async createNewApp(appDeclaration) {
|
|
43
|
+
const newApp = {
|
|
44
|
+
identifier: appDeclaration.appIdentifier,
|
|
45
|
+
name: appDeclaration.appName,
|
|
46
|
+
public: appDeclaration.visibility === 'public',
|
|
47
|
+
description: appDeclaration.appDescription,
|
|
48
|
+
publisher: appDeclaration.publisher,
|
|
49
|
+
thumbnail: appDeclaration.thumbnail,
|
|
50
|
+
eventWorkflowTriggerKeyMapping: 'NONE',
|
|
51
|
+
};
|
|
52
|
+
const app = await new Entities().create({ entityName: 'app', object: newApp });
|
|
53
|
+
console.log(`%s Created new app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
54
|
+
return app;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { authUser } from '../auth/login';
|
|
4
|
+
import { AppDeployer } from './app-deployer';
|
|
5
|
+
const yaml = require('js-yaml');
|
|
6
|
+
function loadAppConfig() {
|
|
7
|
+
const TARGET_DIR = process.cwd();
|
|
8
|
+
try {
|
|
9
|
+
const doc = yaml.load(fs.readFileSync(TARGET_DIR + '/app.yml', 'utf8'));
|
|
10
|
+
console.log(doc);
|
|
11
|
+
return doc;
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
console.log('ERROR PARSING YAML: ', e);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function deployApp(args) {
|
|
18
|
+
const appDeclaration = loadAppConfig();
|
|
19
|
+
console.log(`%s Deploying application. ${appDeclaration.appIdentifier}`, chalk.blue.bold('START'));
|
|
20
|
+
const user = await authUser();
|
|
21
|
+
await new AppDeployer().deploy(appDeclaration);
|
|
22
|
+
console.log('%s Deployment complete.', chalk.green.bold('COMPLETE'));
|
|
23
|
+
}
|
|
24
|
+
export async function removeApp(args) {
|
|
25
|
+
const appDeclaration = loadAppConfig();
|
|
26
|
+
console.log(`%s Removing application. ${appDeclaration.appIdentifier}`, chalk.blue.bold('START'));
|
|
27
|
+
await authUser();
|
|
28
|
+
await new AppDeployer().remove(appDeclaration);
|
|
29
|
+
console.log('%s Removing complete.', chalk.green.bold('COMPLETE'));
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Describes the derivative JSON / object represented
|
|
2
|
+
* in app.yml file.
|
|
3
|
+
*/
|
|
4
|
+
export interface AppDeclartion {
|
|
5
|
+
appIdentifier: string;
|
|
6
|
+
appName: string;
|
|
7
|
+
appDescription?: string;
|
|
8
|
+
visibility: 'private' | 'public';
|
|
9
|
+
publisher: string;
|
|
10
|
+
version: string;
|
|
11
|
+
thumbnail?: string;
|
|
12
|
+
actions?: {
|
|
13
|
+
[key: string]: AppActionDeclaration;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface AppActionDeclaration {
|
|
17
|
+
handler: string;
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface App {
|
|
22
|
+
id: string;
|
|
23
|
+
identifier: string;
|
|
24
|
+
name: string;
|
|
25
|
+
private: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface EventWorkflowActionDefinition {
|
|
28
|
+
id?: string;
|
|
29
|
+
identifier?: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
executeFunctionString?: string;
|
|
32
|
+
ownerKey?: string;
|
|
33
|
+
public?: boolean;
|
|
34
|
+
}
|
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@contrail/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "VibeIQ's Contrail Platform CLI.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"@contrail/contrail-cli": "bin/init",
|
|
8
|
+
"contrail": "bin/init"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cli",
|
|
15
|
+
"contrail-cli"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
|
|
20
|
+
"lint": "tslint -p tsconfig.json",
|
|
21
|
+
"test": "jest --watch"
|
|
22
|
+
},
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "ISC",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@contrail/sdk": "^1.0.91",
|
|
27
|
+
"amazon-cognito-identity-js": "^5.2.3",
|
|
28
|
+
"arg": "^4.0.1",
|
|
29
|
+
"aws-sdk": "^2.1044.0",
|
|
30
|
+
"chalk": "^4.0.0",
|
|
31
|
+
"esm": "^3.2.25",
|
|
32
|
+
"execa": "^5.0.0",
|
|
33
|
+
"inquirer": "^8.2.0",
|
|
34
|
+
"js-yaml": "^4.1.0",
|
|
35
|
+
"listr": "^0.14.3",
|
|
36
|
+
"ncp": "^2.0.0",
|
|
37
|
+
"pkg-install": "^1.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/jest": "^23.3.14",
|
|
41
|
+
"jest": "^23.6.0",
|
|
42
|
+
"prettier": "^1.19.1",
|
|
43
|
+
"ts-jest": "^23.10.5",
|
|
44
|
+
"tslint": "^5.11.0",
|
|
45
|
+
"tslint-config-prettier": "^1.18.0",
|
|
46
|
+
"typescript": "^3.0.1",
|
|
47
|
+
"@types/node": "^14.6.4"
|
|
48
|
+
},
|
|
49
|
+
"jest": {
|
|
50
|
+
"moduleFileExtensions": [
|
|
51
|
+
"js",
|
|
52
|
+
"json",
|
|
53
|
+
"ts"
|
|
54
|
+
],
|
|
55
|
+
"rootDir": "src",
|
|
56
|
+
"testRegex": ".spec.ts$",
|
|
57
|
+
"transform": {
|
|
58
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
59
|
+
},
|
|
60
|
+
"coverageDirectory": "../coverage",
|
|
61
|
+
"testEnvironment": "node"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { Entities, setConfig } from '@contrail/sdk';
|
|
4
|
+
|
|
5
|
+
const CONFIGURATION = {
|
|
6
|
+
userPoolId: 'us-east-1_x8hXZUsL6',
|
|
7
|
+
clientId: process.env.CONTRAIL_COGNITO_CLIENT_ID || '2oubek96sue5rsncovtqhs3scm',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function authUser(){
|
|
11
|
+
const questions = [];
|
|
12
|
+
if (!process.env.CONTRAIL_CLI_EMAIL) {
|
|
13
|
+
questions.push({
|
|
14
|
+
type: 'input',
|
|
15
|
+
name: 'email',
|
|
16
|
+
validate: (val) => !!val,
|
|
17
|
+
message: 'Email',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (!process.env.CONTRAIL_CLI_PASSWORD) {
|
|
21
|
+
questions.push({
|
|
22
|
+
type: 'password',
|
|
23
|
+
name: 'password',
|
|
24
|
+
validate: (val) => !!val,
|
|
25
|
+
message: 'Password',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (!process.env.CONTRAIL_CLI_ORGSLUG) {
|
|
29
|
+
questions.push({
|
|
30
|
+
type: 'input',
|
|
31
|
+
name: 'orgSlug',
|
|
32
|
+
validate: (val) => !!val,
|
|
33
|
+
message: 'Org (slug)',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const answers = await inquirer.prompt(questions);
|
|
38
|
+
const params = {
|
|
39
|
+
email: process.env.CONTRAIL_CLI_EMAIL || answers.email,
|
|
40
|
+
password: process.env.CONTRAIL_CLI_PASSWORD || answers.password,
|
|
41
|
+
orgSlug: process.env.CONTRAIL_CLI_ORGSLUG || answers.orgSlug,
|
|
42
|
+
};
|
|
43
|
+
if (!params.email || !params.password){
|
|
44
|
+
throw new Error('email and password required');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const poolData = {
|
|
48
|
+
UserPoolId: CONFIGURATION.userPoolId,
|
|
49
|
+
ClientId: CONFIGURATION.clientId,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
|
|
53
|
+
const authData = {
|
|
54
|
+
Username: params.email,
|
|
55
|
+
Password: params.password,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const authDetails = new AmazonCognitoIdentity.AuthenticationDetails(authData);
|
|
59
|
+
const userData = {
|
|
60
|
+
Username: params.email,
|
|
61
|
+
Pool: userPool,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
|
|
65
|
+
|
|
66
|
+
return new Promise( (resolve, reject) => {
|
|
67
|
+
cognitoUser.authenticateUser(authDetails, {
|
|
68
|
+
onSuccess: async (result) => {
|
|
69
|
+
const user = result;
|
|
70
|
+
await setSDKConfig(user, params.orgSlug);
|
|
71
|
+
resolve({success: true, data: result});
|
|
72
|
+
},
|
|
73
|
+
onFailure: (err) => {
|
|
74
|
+
console.log('authUser error: ' + err);
|
|
75
|
+
reject(err);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function setSDKConfig(user, orgSlug){
|
|
83
|
+
// console.log('user:', user);
|
|
84
|
+
setConfig({
|
|
85
|
+
apiUserToken: user.idToken.jwtToken,
|
|
86
|
+
apiGateway: process.env.API_GATEWAY,
|
|
87
|
+
orgSlug,
|
|
88
|
+
});
|
|
89
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
import { create } from './create-app/create';
|
|
3
|
+
import { deployApp, removeApp } from './deploy-app/deploy';
|
|
4
|
+
/**
|
|
5
|
+
* Built using documentation here:
|
|
6
|
+
* https://www.twilio.com/blog/how-to-build-a-cli-with-node-js
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export async function cli(args) {
|
|
10
|
+
const command = args[2];
|
|
11
|
+
switch (command) {
|
|
12
|
+
case 'create-app': {
|
|
13
|
+
create(args);
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
case 'deploy-app': {
|
|
17
|
+
deployApp(args);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
case 'remove-app': {
|
|
21
|
+
removeApp(args);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
case 'extract-types': {
|
|
25
|
+
removeApp(args);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
default: {
|
|
29
|
+
console.log('Illegal command.');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import arg from 'arg';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ncp from 'ncp';
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import { setUpTemplate } from './setup-template';
|
|
6
|
+
|
|
7
|
+
function parseArgumentsIntoOptions(rawArgs) {
|
|
8
|
+
const args = arg(
|
|
9
|
+
{
|
|
10
|
+
'--create-app': Boolean,
|
|
11
|
+
'--app-name': String,
|
|
12
|
+
'-c': '--create-app',
|
|
13
|
+
'-n': '--app-name',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
argv: rawArgs.slice(2),
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
return {
|
|
20
|
+
createApp: args['--create-app'] || false,
|
|
21
|
+
appName: args['--app-name']
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function promptForMissingOptions(options) {
|
|
25
|
+
const defaultTemplate = 'JavaScript';
|
|
26
|
+
if (options.skipPrompts) {
|
|
27
|
+
return {
|
|
28
|
+
...options,
|
|
29
|
+
template: options.template || defaultTemplate,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const questions = [];
|
|
34
|
+
if (!options.appName) {
|
|
35
|
+
questions.push({
|
|
36
|
+
type: 'input',
|
|
37
|
+
name: 'appName',
|
|
38
|
+
validate: (val) => { return !!val },
|
|
39
|
+
message: 'What is the name of your app?',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const answers = await inquirer.prompt(questions);
|
|
44
|
+
return {
|
|
45
|
+
...options,
|
|
46
|
+
appName: options.appName || answers.appName,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
export async function create(args) {
|
|
52
|
+
|
|
53
|
+
let options = parseArgumentsIntoOptions(args);
|
|
54
|
+
options = await promptForMissingOptions(options);
|
|
55
|
+
console.log("%s Setting up new new contrail application.", chalk.blue.bold('START'));
|
|
56
|
+
await setUpTemplate();
|
|
57
|
+
console.log('%s Application initialized successfully.', chalk.green.bold('COMPLETE'));
|
|
58
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
import ncp from 'ncp';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
import execa from 'execa';
|
|
6
|
+
const Listr = require('listr');
|
|
7
|
+
import { projectInstall } from 'pkg-install';
|
|
8
|
+
const copy = promisify(ncp);
|
|
9
|
+
const TARGET_DIR = process.cwd();
|
|
10
|
+
|
|
11
|
+
const currentFileUrl = import .meta.url;
|
|
12
|
+
const TEMPLATE_DIR = path.resolve(
|
|
13
|
+
new URL(currentFileUrl).pathname,
|
|
14
|
+
'../../../templates/javascript',
|
|
15
|
+
).replace(/\\/g, '\\').replace(/C:\\C:/g, '');
|
|
16
|
+
|
|
17
|
+
// console.log("TEMPLATE_DIR: ", TEMPLATE_DIR)
|
|
18
|
+
// console.log("TARGET_DIR: ", TARGET_DIR)
|
|
19
|
+
|
|
20
|
+
function copyTemplate(){
|
|
21
|
+
return copy(TEMPLATE_DIR, TARGET_DIR, {
|
|
22
|
+
clobber: false,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function npmInstall(){
|
|
27
|
+
async function initGit(options) {
|
|
28
|
+
const result = await execa('git', ['init'], {
|
|
29
|
+
cwd: options.targetDirectory,
|
|
30
|
+
});
|
|
31
|
+
if (result.failed) {
|
|
32
|
+
return Promise.reject(new Error('Failed to initialize git'));
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function setUpTemplate() {
|
|
39
|
+
try {
|
|
40
|
+
const tasks = new Listr([
|
|
41
|
+
{
|
|
42
|
+
title: 'Copy template files',
|
|
43
|
+
task: () => copyTemplate(),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: 'Install dependencies',
|
|
47
|
+
task: () =>
|
|
48
|
+
projectInstall({
|
|
49
|
+
cwd: TARGET_DIR,
|
|
50
|
+
}),
|
|
51
|
+
// skip: () =>
|
|
52
|
+
// !options.runInstall
|
|
53
|
+
// ? 'Pass --install to automatically install dependencies'
|
|
54
|
+
// : undefined,
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
await tasks.run();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.log('Error copying template: ', e);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Entities } from '@contrail/sdk';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { App, AppDeclartion, AppActionDeclaration, EventWorkflowActionDefinition } from './interfaces';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
export class ActionsDeployer {
|
|
7
|
+
async deployActions(appDeclaration: AppDeclartion, app: App) {
|
|
8
|
+
|
|
9
|
+
// FETCH ALL ACTIONS FROM THE API.
|
|
10
|
+
const existingActions = await this.getActionsForApp(app);
|
|
11
|
+
console.log('existingActions: ', existingActions);
|
|
12
|
+
// UPSERT / DELETE BASED ON ACTION IDENTIFIER
|
|
13
|
+
console.log('%s \tDeploying actions.', chalk.yellow.bold('ACTION'));
|
|
14
|
+
for (const actionKey of Object.keys(appDeclaration.actions)) {
|
|
15
|
+
await this.deployAction(actionKey, appDeclaration.actions[actionKey], app, existingActions);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getActionsForApp(app: App){
|
|
20
|
+
return await new Entities().get({ entityName: 'event-workflow-action-definition',
|
|
21
|
+
criteria: { ownerKey: `app:${app.id}`}});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async deployAction(actionKey: string,
|
|
25
|
+
actionDeclaration: AppActionDeclaration,
|
|
26
|
+
app: App,
|
|
27
|
+
existingActions: Array<any>){
|
|
28
|
+
console.log(`%s \tDeploying action: ${actionKey}`, chalk.yellow.bold('ACTION'));
|
|
29
|
+
const identifier = this.getActionDeclarationIdentifier(actionKey, app);
|
|
30
|
+
const existingDefinition = existingActions.find(ad => ad.identifier === identifier);
|
|
31
|
+
if (!existingDefinition) {
|
|
32
|
+
await this.createNewActionDefinition(actionKey, actionDeclaration, app);
|
|
33
|
+
} else {
|
|
34
|
+
await this.updateActionDefinition(existingDefinition, actionDeclaration);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
private async createNewActionDefinition(actionKey: string,
|
|
38
|
+
actionDeclaration: AppActionDeclaration,
|
|
39
|
+
app: App) {
|
|
40
|
+
const newObject: EventWorkflowActionDefinition = {
|
|
41
|
+
identifier: this.getActionDeclarationIdentifier(actionKey, app),
|
|
42
|
+
name: actionDeclaration.name,
|
|
43
|
+
ownerKey: `app:${app.id}`,
|
|
44
|
+
public: !app.private,
|
|
45
|
+
};
|
|
46
|
+
const functionString = await this.getFunction(actionDeclaration);
|
|
47
|
+
if (functionString) {
|
|
48
|
+
newObject.executeFunctionString = functionString;
|
|
49
|
+
}
|
|
50
|
+
const actionDefinition = await new Entities().create({
|
|
51
|
+
entityName: 'event-workflow-action-definition',
|
|
52
|
+
object: newObject });
|
|
53
|
+
console.log(`%s Created new action defintion: ${actionKey}`, chalk.yellow.bold('APP'));
|
|
54
|
+
return actionDefinition;
|
|
55
|
+
}
|
|
56
|
+
private async updateActionDefinition(existingActionDefinition: EventWorkflowActionDefinition,
|
|
57
|
+
actionDeclaration: AppActionDeclaration) {
|
|
58
|
+
const updates: EventWorkflowActionDefinition = {
|
|
59
|
+
name: actionDeclaration.name,
|
|
60
|
+
};
|
|
61
|
+
const functionString = await this.getFunction(actionDeclaration);
|
|
62
|
+
if (functionString) {
|
|
63
|
+
updates.executeFunctionString = functionString;
|
|
64
|
+
}
|
|
65
|
+
const actionDefinition = await new Entities().update({
|
|
66
|
+
entityName: 'event-workflow-action-definition',
|
|
67
|
+
id: existingActionDefinition.id,
|
|
68
|
+
object: updates });
|
|
69
|
+
console.log(`%s Updated existingaction defintion: ${actionDefinition.identifier}`, chalk.yellow.bold('APP'));
|
|
70
|
+
return actionDefinition;
|
|
71
|
+
}
|
|
72
|
+
getActionDeclarationIdentifier(actionKey, app: App) {
|
|
73
|
+
return app.identifier + ':' + actionKey;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getFunction(actionDeclaration: AppActionDeclaration): Promise<string> {
|
|
77
|
+
if (!actionDeclaration.handler) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const CURRENT_DIR = process.cwd();
|
|
81
|
+
const functionLocaiton = CURRENT_DIR + '\\' + actionDeclaration.handler;
|
|
82
|
+
console.log('getFunction: ', functionLocaiton);
|
|
83
|
+
const content = fs.readFileSync(functionLocaiton);
|
|
84
|
+
console.log('content: ', content.toString());
|
|
85
|
+
return content.toString();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Entities } from '@contrail/sdk';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { ActionsDeployer } from './actions-deployer';
|
|
4
|
+
import { App, AppDeclartion } from './interfaces';
|
|
5
|
+
|
|
6
|
+
export class AppDeployer {
|
|
7
|
+
private app: App;
|
|
8
|
+
public async deploy(appDeclaration: AppDeclartion) {
|
|
9
|
+
let app = await this.getExistingApp(appDeclaration);
|
|
10
|
+
if (!app) {
|
|
11
|
+
app = await this.createNewApp(appDeclaration);
|
|
12
|
+
} else {
|
|
13
|
+
app = await this.updateExistingApp(appDeclaration, app);
|
|
14
|
+
}
|
|
15
|
+
this.app = app;
|
|
16
|
+
await new ActionsDeployer().deployActions(appDeclaration, this.app);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async remove(appDeclaration: AppDeclartion) {
|
|
20
|
+
let app = await this.getExistingApp(appDeclaration);
|
|
21
|
+
if (app) {
|
|
22
|
+
app = await new Entities().delete({ entityName: 'app', id: app.id});
|
|
23
|
+
console.log(`%s Deleted app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(`%s App not found. Can not remove`, chalk.red.bold('APP'));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public async getExistingApp(appDeclaration: AppDeclartion) {
|
|
30
|
+
const apps = await new Entities().get({ entityName: 'app', criteria: { orgId: '-VMrLJrt3clDKPi0', private: (appDeclaration.visibility === 'private')}});
|
|
31
|
+
// console.log('Existing: ', apps);
|
|
32
|
+
return apps.find(app => app.identifier === appDeclaration.appIdentifier);
|
|
33
|
+
}
|
|
34
|
+
public async updateExistingApp(appDeclaration: AppDeclartion, existingApp) {
|
|
35
|
+
const updates = {
|
|
36
|
+
name: appDeclaration.appName,
|
|
37
|
+
public: appDeclaration.visibility === 'public',
|
|
38
|
+
description: appDeclaration.appDescription,
|
|
39
|
+
publisher: appDeclaration.publisher,
|
|
40
|
+
thumbnail: appDeclaration.thumbnail,
|
|
41
|
+
eventWorkflowTriggerKeyMapping: 'NONE',
|
|
42
|
+
};
|
|
43
|
+
const app = await new Entities().update({ entityName: 'app', id: existingApp.id, object: updates });
|
|
44
|
+
console.log(`%s Updated app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
45
|
+
return app;
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
private async createNewApp(appDeclaration: AppDeclartion) {
|
|
49
|
+
const newApp = {
|
|
50
|
+
identifier: appDeclaration.appIdentifier,
|
|
51
|
+
name: appDeclaration.appName,
|
|
52
|
+
public: appDeclaration.visibility === 'public',
|
|
53
|
+
description: appDeclaration.appDescription,
|
|
54
|
+
publisher: appDeclaration.publisher,
|
|
55
|
+
thumbnail: appDeclaration.thumbnail,
|
|
56
|
+
eventWorkflowTriggerKeyMapping: 'NONE',
|
|
57
|
+
|
|
58
|
+
};
|
|
59
|
+
const app = await new Entities().create({ entityName: 'app', object: newApp });
|
|
60
|
+
console.log(`%s Created new app: ${app.name}`, chalk.yellow.bold('APP'));
|
|
61
|
+
return app;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { authUser } from '../auth/login';
|
|
4
|
+
import { AppDeployer } from './app-deployer';
|
|
5
|
+
import { AppDeclartion } from './interfaces';
|
|
6
|
+
const yaml = require('js-yaml');
|
|
7
|
+
|
|
8
|
+
function loadAppConfig(){
|
|
9
|
+
const TARGET_DIR = process.cwd();
|
|
10
|
+
try {
|
|
11
|
+
const doc = yaml.load(fs.readFileSync(TARGET_DIR + '/app.yml', 'utf8'));
|
|
12
|
+
console.log(doc);
|
|
13
|
+
return doc;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.log('ERROR PARSING YAML: ', e);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function deployApp(args) {
|
|
20
|
+
const appDeclaration: AppDeclartion = loadAppConfig();
|
|
21
|
+
|
|
22
|
+
console.log(`%s Deploying application. ${appDeclaration.appIdentifier}`, chalk.blue.bold('START'));
|
|
23
|
+
const user = await authUser();
|
|
24
|
+
await new AppDeployer().deploy(appDeclaration);
|
|
25
|
+
console.log('%s Deployment complete.', chalk.green.bold('COMPLETE'));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function removeApp(args) {
|
|
29
|
+
const appDeclaration: AppDeclartion = loadAppConfig();
|
|
30
|
+
console.log(`%s Removing application. ${appDeclaration.appIdentifier}`, chalk.blue.bold('START'));
|
|
31
|
+
await authUser();
|
|
32
|
+
await new AppDeployer().remove(appDeclaration);
|
|
33
|
+
console.log('%s Removing complete.', chalk.green.bold('COMPLETE'));
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Describes the derivative JSON / object represented
|
|
2
|
+
* in app.yml file.
|
|
3
|
+
*/
|
|
4
|
+
export interface AppDeclartion {
|
|
5
|
+
appIdentifier: string;
|
|
6
|
+
appName: string;
|
|
7
|
+
appDescription?: string;
|
|
8
|
+
visibility: 'private' | 'public';
|
|
9
|
+
publisher: string;
|
|
10
|
+
version: string;
|
|
11
|
+
thumbnail?: string;
|
|
12
|
+
actions?: { [key: string]: AppActionDeclaration };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AppActionDeclaration {
|
|
16
|
+
handler: string;
|
|
17
|
+
name: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface App {
|
|
22
|
+
id: string;
|
|
23
|
+
identifier: string;
|
|
24
|
+
name: string;
|
|
25
|
+
private: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EventWorkflowActionDefinition {
|
|
29
|
+
id?: string;
|
|
30
|
+
identifier?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
executeFunctionString?: string;
|
|
33
|
+
ownerKey?: string;
|
|
34
|
+
public?: boolean;
|
|
35
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
async function execute(config, event, Functions) {
|
|
2
|
+
const getBodyFromEvent = (config, event) => {
|
|
3
|
+
let body = Functions.StringUtil.parseVariables(config.message, event);
|
|
4
|
+
console.log('body: ', body);
|
|
5
|
+
if (config.sendDetails) {
|
|
6
|
+
const data = event;
|
|
7
|
+
const properties = {};
|
|
8
|
+
let addedProperty = false;
|
|
9
|
+
for (const property of config.fieldsToForward) {
|
|
10
|
+
const variableLevels = property.split('.');
|
|
11
|
+
let replacementValue = data;
|
|
12
|
+
for (let i = 0; i < variableLevels.length; i++) {
|
|
13
|
+
replacementValue = replacementValue[variableLevels[i]];
|
|
14
|
+
}
|
|
15
|
+
properties[property] = replacementValue;
|
|
16
|
+
addedProperty = true;
|
|
17
|
+
}
|
|
18
|
+
if (addedProperty) {
|
|
19
|
+
body = body + ' Properties: ' + JSON.stringify(properties) + '.';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return JSON.stringify({ text: body });
|
|
23
|
+
};
|
|
24
|
+
const body = getBodyFromEvent(config, event);
|
|
25
|
+
console.log('Sending message: ', body);
|
|
26
|
+
const response = await Functions.fetch(config.url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
body: body,
|
|
29
|
+
headers: { 'Content-Type': 'application/json' }
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
console.log('Webhook event failed to publish');
|
|
33
|
+
console.log('Response: ', JSON.stringify(response));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.log('Published!');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
appIdentifier: "@your-co/app-name"
|
|
2
|
+
appName: "Your App Name"
|
|
3
|
+
appDescription: "Your app description."
|
|
4
|
+
visibility: public
|
|
5
|
+
publisher: "Your company name"
|
|
6
|
+
version: 1.0
|
|
7
|
+
|
|
8
|
+
appConfiguration:
|
|
9
|
+
apiHost:
|
|
10
|
+
label: "API Host"
|
|
11
|
+
propertyType: "string"
|
|
12
|
+
userName:
|
|
13
|
+
label: "User Name"
|
|
14
|
+
propertyType: "string"
|
|
15
|
+
password:
|
|
16
|
+
label: "Password"
|
|
17
|
+
propertyType: "string"
|
|
18
|
+
|
|
19
|
+
actions:
|
|
20
|
+
updateProduct:
|
|
21
|
+
handler: actions/example/action.js
|
|
22
|
+
name: "Update Product"
|
|
23
|
+
description: "Updates a product in the destination system."
|
|
24
|
+
|
|
25
|
+
triggers:
|
|
26
|
+
trigger1:
|
|
27
|
+
key: product|create
|
|
28
|
+
name: "Produt Created"
|
|
29
|
+
|
|
30
|
+
workflows:
|
|
31
|
+
workflow1:
|
|
32
|
+
name: "Update item on product creation"
|
|
33
|
+
triggerKey: product|create
|
|
34
|
+
paths:
|
|
35
|
+
path1:
|
|
36
|
+
conditional: "{retailPrice} > 10"
|
|
37
|
+
actions:
|
|
38
|
+
updateItem:
|
|
39
|
+
actionId: updateItem
|
|
40
|
+
configuration:
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"@contrail/contrail-cli": "bin/init",
|
|
8
|
+
"contrail": "bin/init"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"chalk": "^4.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"outDir": "./lib",
|
|
8
|
+
"strict": false,
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"strictPropertyInitialization": false,
|
|
11
|
+
"noImplicitAny": false,
|
|
12
|
+
"emitDecoratorMetadata": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"moduleResolution": "node"
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"exclude": [
|
|
20
|
+
"node_modules",
|
|
21
|
+
"**/__tests__/*"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/tslint.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"defaultSeverity": "error",
|
|
3
|
+
"extends": [
|
|
4
|
+
"tslint:recommended"
|
|
5
|
+
],
|
|
6
|
+
"jsRules": {
|
|
7
|
+
"no-unused-expression": true
|
|
8
|
+
},
|
|
9
|
+
"rules": {
|
|
10
|
+
"eofline": false,
|
|
11
|
+
"quotemark": [
|
|
12
|
+
true,
|
|
13
|
+
"single"
|
|
14
|
+
],
|
|
15
|
+
"no-shadowed-variable": false,
|
|
16
|
+
"indent": false,
|
|
17
|
+
"member-access": [
|
|
18
|
+
false
|
|
19
|
+
],
|
|
20
|
+
"no-string-literal": false,
|
|
21
|
+
"ordered-imports": [
|
|
22
|
+
false
|
|
23
|
+
],
|
|
24
|
+
"max-line-length": [
|
|
25
|
+
false,
|
|
26
|
+
150
|
|
27
|
+
],
|
|
28
|
+
"member-ordering": [
|
|
29
|
+
false
|
|
30
|
+
],
|
|
31
|
+
"curly": false,
|
|
32
|
+
"interface-name": [
|
|
33
|
+
false
|
|
34
|
+
],
|
|
35
|
+
"array-type": [
|
|
36
|
+
false
|
|
37
|
+
],
|
|
38
|
+
"no-empty-interface": false,
|
|
39
|
+
"no-empty": false,
|
|
40
|
+
"arrow-parens": false,
|
|
41
|
+
"object-literal-sort-keys": false,
|
|
42
|
+
"no-unused-expression": false,
|
|
43
|
+
"max-classes-per-file": [
|
|
44
|
+
false
|
|
45
|
+
],
|
|
46
|
+
"variable-name": [
|
|
47
|
+
false
|
|
48
|
+
],
|
|
49
|
+
"one-line": [
|
|
50
|
+
false
|
|
51
|
+
],
|
|
52
|
+
"one-variable-per-declaration": [
|
|
53
|
+
false
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"rulesDirectory": []
|
|
57
|
+
}
|