fix(startup): Startup improved and more checks added.

This commit is contained in:
Rodrigo Rodriguez 2018-12-18 13:50:35 -02:00
parent 8490f5ed06
commit 5d6c60ed6d
6 changed files with 452 additions and 426 deletions

View file

@ -63,6 +63,10 @@ export class AdminDialog extends IGBDialog {
public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) {
const packageName = text.split(' ')[1];
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
if (!additionalPath)
{
throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.');
}
await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName));
}

View file

@ -247,7 +247,7 @@ export class AzureDeployerService extends GBService {
instance.storageServer = storageServer;
logger.info(`Deploying Search...`);
const searchName = `${name}-search`;
const searchName = `${name}-search`.toLowerCase();
await this.createSearch(name, searchName, instance.cloudLocation);
const searchKeys = await this.searchClient.adminKeys.get(
name,
@ -473,7 +473,7 @@ export class AzureDeployerService extends GBService {
const retrieveBotId = () => {
if (!botId) {
process.stdout.write(
`${GBAdminService.GB_PROMPT}Bot Id must only contain lowercase letters, digits or dashes, cannot start or end with or contain consecutive dashes and is limited from 4 to 42 characters long.\n`
`${GBAdminService.GB_PROMPT}Choose a unique bot Id containing lowercase letters, digits or dashes (cannot use dash as the first two or last one characters), cannot start or end with or contain consecutive dashes and having 4 to 42 characters long.\n`
);
process.stdout.write(`${GBAdminService.GB_PROMPT}BOT_ID:`);
botId = scanf('%s').replace(/(\n|\r)+$/, ''); // TODO: Update this regexp to match description of it.

View file

@ -218,21 +218,22 @@ export class GBCoreService implements IGBCoreService {
public async writeEnv(instance: IGBInstance) {
const env = `ADDITIONAL_DEPLOY_PATH=
ADMIN_PASS=${instance.adminPass}
CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId}
CLOUD_LOCATION=${instance.cloudLocation}
CLOUD_GROUP=${instance.botId}
CLOUD_USERNAME=${instance.cloudUsername}
CLOUD_PASSWORD=${instance.cloudPassword}
MARKETPLACE_ID=${instance.marketplaceId}
MARKETPLACE_SECRET=${instance.marketplacePassword}
NLP_AUTHORING_KEY=${instance.nlpAuthoringKey}
STORAGE_DIALECT=${instance.storageDialect}
STORAGE_SERVER=${instance.storageServer}.database.windows.net
STORAGE_NAME=${instance.storageName}
STORAGE_USERNAME=${instance.storageUsername}
STORAGE_PASSWORD=${instance.storagePassword}
STORAGE_SYNC=true`;
ADMIN_PASS=${instance.adminPass}
CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId}
CLOUD_LOCATION=${instance.cloudLocation}
CLOUD_GROUP=${instance.botId}
CLOUD_USERNAME=${instance.cloudUsername}
CLOUD_PASSWORD=${instance.cloudPassword}
MARKETPLACE_ID=${instance.marketplaceId}
MARKETPLACE_SECRET=${instance.marketplacePassword}
NLP_AUTHORING_KEY=${instance.nlpAuthoringKey}
STORAGE_DIALECT=${instance.storageDialect}
STORAGE_SERVER=${instance.storageServer}.database.windows.net
STORAGE_NAME=${instance.storageName}
STORAGE_USERNAME=${instance.storageUsername}
STORAGE_PASSWORD=${instance.storagePassword}
STORAGE_SYNC=true
`;
fs.writeFileSync('.env', env);
}

View file

@ -45,13 +45,13 @@ const express = require('express');
const child_process = require('child_process');
import { GBMinInstance, IGBCoreService, IGBInstance } from 'botlib';
import { GBError } from 'botlib';
import { IGBPackage } from 'botlib';
import { GBError,IGBPackage } from 'botlib';
import { AzureSearch } from 'pragmatismo-io-framework';
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GuaribasInstance, GuaribasPackage } from '../models/GBModel';
import { KBService } from './../../kb.gbapp/services/KBService';
import { GBConfigService } from './GBConfigService';
import { GBCoreService } from './GBCoreService';
import { GBImporter } from './GBImporterService';
import { GBVMService } from './GBVMService';
@ -77,9 +77,10 @@ export class GBDeployer {
*
* Performs package deployment in all .gbai or default.
*
* */
public deployPackages(core: IGBCoreService, server: any, appPackages: IGBPackage[]) {
*/
public async deployPackages(core: IGBCoreService, server: any, appPackages: IGBPackage[]) {
const _this = this;
return new Promise(
(resolve: any, reject: any): any => {
let totalPackages = 0;
@ -121,106 +122,30 @@ export class GBDeployer {
doIt(e);
});
/** Deploys all .gbapp files first. */
// Deploys all .gbapp files first.
let appPackagesProcessed = 0;
gbappPackages.forEach(e => {
// Skips .gbapp inside deploy folder.
if (!e.startsWith('packages')) {
logger.info(`Deploying app: ${e}...`);
import(e)
.then(m => {
const p = new m.Package();
p.loadPackage(core, core.sequelize);
appPackages.push(p);
logger.info(`App (.gbapp) deployed: ${e}.`);
appPackagesProcessed++;
})
.catch(err => {
logger.error(`Error deploying App (.gbapp): ${e}: ${err}`);
appPackagesProcessed++;
});
} else {
appPackagesProcessed++;
}
});
const appPackagesProcessed = this.deployAppPackages(gbappPackages, core, appPackages);
WaitUntil()
.interval(1000)
.times(10)
.condition(function(cb) {
.condition(cb => {
logger.info(`Waiting for app package deployment...`);
cb(appPackagesProcessed == gbappPackages.length);
cb(appPackagesProcessed === gbappPackages.length);
})
.done(async result => {
logger.info(`App Package deployment done.`);
try {
await core.syncDatabaseStructure();
} catch (e) {
throw e;
}
/** Deploys all .gbot files first. */
botPackages.forEach(e => {
if (e !== 'packages\\boot.gbot') {
logger.info(`Deploying bot: ${e}...`);
_this.deployBot(e);
logger.info(`Bot: ${e} deployed...`);
}
});
/** Then all remaining generalPackages are loaded. */
generalPackages = generalPackages.filter(p => !p.endsWith('.git'));
generalPackages.forEach(filename => {
const filenameOnly = Path.basename(filename);
logger.info(`Deploying package: ${filename}...`);
/** Handles apps for general bots - .gbapp must stay out of deploy folder. */
if (Path.extname(filename) === '.gbapp' || Path.extname(filename) === '.gblib') {
/** Themes for bots. */
} else if (Path.extname(filename) === '.gbtheme') {
server.use('/themes/' + filenameOnly, express.static(filename));
logger.info(`Theme (.gbtheme) assets accessible at: ${'/themes/' + filenameOnly}.`);
/** Knowledge base for bots. */
} else if (Path.extname(filename) === '.gbkb') {
server.use('/kb/' + filenameOnly + '/subjects', express.static(UrlJoin(filename, 'subjects')));
logger.info(`KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`);
} else if (Path.extname(filename) === '.gbui') {
// Already Handled
} else if (Path.extname(filename) === '.gbdialog') {
// Already Handled
} else {
/** Unknown package format. */
const err = new Error(`Package type not handled: ${filename}.`);
reject(err);
}
totalPackages++;
});
WaitUntil()
.interval(100)
.times(5)
.condition(function(cb) {
logger.info(`Waiting for package deployment...`);
cb(totalPackages == generalPackages.length);
})
.done(function(result) {
if (botPackages.length === 0) {
logger.info(
'No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder.'
);
} else {
logger.info(`Package deployment done.`);
}
resolve();
});
({ generalPackages, totalPackages } = await this.deployDataPackages(
core,
botPackages,
_this,
generalPackages,
server,
reject,
totalPackages,
resolve
));
});
}
);
@ -363,9 +288,109 @@ export class GBDeployer {
public installDefaultGBUI() {
const root = 'packages/default.gbui';
if (!Fs.existsSync(`${root}/build`)) {
logger.info(`Preparing default.gbui (it may take some additional time for the first time)...`);
Fs.writeFileSync(`${root}/.env`, 'SKIP_PREFLIGHT_CHECK=true');
child_process.execSync('npm install', { cwd: root });
child_process.execSync('npm run build', { cwd: root });
}
}
private async deployDataPackages(
core: GBCoreService,
botPackages: string[],
_this: this,
generalPackages: string[],
server: any,
reject: any,
totalPackages: number,
resolve: any
) {
try {
await core.syncDatabaseStructure();
} catch (e) {
throw e;
}
// Deploys all .gbot files first.
botPackages.forEach(e => {
if (e !== 'packages\\boot.gbot') {
logger.info(`Deploying bot: ${e}...`);
_this.deployBot(e);
logger.info(`Bot: ${e} deployed...`);
}
});
// Then all remaining generalPackages are loaded.
generalPackages = generalPackages.filter(p => !p.endsWith('.git'));
generalPackages.forEach(filename => {
const filenameOnly = Path.basename(filename);
logger.info(`Deploying package: ${filename}...`);
// Handles apps for general bots - .gbapp must stay out of deploy folder.
if (Path.extname(filename) === '.gbapp' || Path.extname(filename) === '.gblib') {
// Themes for bots.
} else if (Path.extname(filename) === '.gbtheme') {
server.use('/themes/' + filenameOnly, express.static(filename));
logger.info(`Theme (.gbtheme) assets accessible at: ${'/themes/' + filenameOnly}.`);
} else if (Path.extname(filename) === '.gbkb') {
server.use('/kb/' + filenameOnly + '/subjects', express.static(UrlJoin(filename, 'subjects')));
logger.info(`KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`);
} else if (Path.extname(filename) === '.gbui') {
// Already Handled
} else if (Path.extname(filename) === '.gbdialog') {
// Already Handled
} else {
// Unknown package format.
const err = new Error(`Package type not handled: ${filename}.`);
reject(err);
}
totalPackages++;
});
WaitUntil()
.interval(100)
.times(5)
.condition(cb => {
logger.info(`Waiting for package deployment...`);
cb(totalPackages === generalPackages.length);
})
.done(result => {
if (botPackages.length === 0) {
logger.info('Use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder (no external packages).');
} else {
logger.info(`Package deployment done.`);
}
resolve();
});
return { generalPackages, totalPackages };
}
private deployAppPackages(gbappPackages: string[], core: any, appPackages: any[]) {
let appPackagesProcessed = 0;
gbappPackages.forEach(e => {
// Skips .gbapp inside deploy folder.
if (!e.startsWith('packages')) {
logger.info(`Deploying app: ${e}...`);
import(e)
.then(m => {
const p = new m.Package();
p.loadPackage(core, core.sequelize);
appPackages.push(p);
logger.info(`App (.gbapp) deployed: ${e}.`);
appPackagesProcessed++;
})
.catch(err => {
logger.error(`Error deploying App (.gbapp): ${e}: ${err}`);
appPackagesProcessed++;
});
} else {
appPackagesProcessed++;
}
});
return appPackagesProcessed;
}
}

View file

@ -30,19 +30,19 @@
| |
\*****************************************************************************/
import React from "react";
import GBMarkdownPlayer from "./players/GBMarkdownPlayer.js";
import GBImagePlayer from "./players/GBImagePlayer.js";
import GBVideoPlayer from "./players/GBVideoPlayer.js";
import GBLoginPlayer from "./players/GBLoginPlayer.js";
import GBBulletPlayer from "./players/GBBulletPlayer.js";
import SidebarMenu from "./components/SidebarMenu.js";
import GBCss from "./components/GBCss.js";
import { DirectLine } from "botframework-directlinejs";
import { ConnectionStatus } from "botframework-directlinejs";
import { Chat } from "botframework-webchat";
import GBPowerBIPlayer from "./players/GBPowerBIPlayer.js";
import { UserAgentApplication } from "msal";
import React from 'react';
import GBMarkdownPlayer from './players/GBMarkdownPlayer.js';
import GBImagePlayer from './players/GBImagePlayer.js';
import GBVideoPlayer from './players/GBVideoPlayer.js';
import GBLoginPlayer from './players/GBLoginPlayer.js';
import GBBulletPlayer from './players/GBBulletPlayer.js';
import SidebarMenu from './components/SidebarMenu.js';
import GBCss from './components/GBCss.js';
import { DirectLine } from 'botframework-directlinejs';
import { ConnectionStatus } from 'botframework-directlinejs';
import ReactWebChat from 'botframework-webchat';
import GBPowerBIPlayer from './players/GBPowerBIPlayer.js';
import { UserAgentApplication } from 'msal';
class GBUIApp extends React.Component {
constructor() {
@ -54,25 +54,24 @@ class GBUIApp extends React.Component {
token: null,
instanceClient: null
};
window.user = this.getUser()
window.user = this.getUser();
}
sendToken(token) {
setTimeout(() => {
window.line
.postActivity({
type: "event",
name: "updateToken",
type: 'event',
name: 'updateToken',
data: token,
locale: "en-us",
textFormat: "plain",
locale: 'en-us',
textFormat: 'plain',
timestamp: new Date().toISOString(),
from: this.getUser()
})
.subscribe(() => {
window.userAgentApplication.logout();
console.log("updateToken done")
console.log('updateToken done');
});
}, 400);
}
@ -80,23 +79,23 @@ class GBUIApp extends React.Component {
send(command) {
window.line
.postActivity({
type: "event",
type: 'event',
name: command,
locale: "en-us",
textFormat: "plain",
locale: 'en-us',
textFormat: 'plain',
timestamp: new Date().toISOString(),
from: this.getUser()
})
.subscribe(console.log("EVENT SENT TO Guaribas."));
.subscribe(console.log('EVENT SENT TO Guaribas.'));
}
getUser() {
return { id: "webUser@gb", name: "You" };
return { id: 'webUser@gb', name: 'You' };
}
postEvent(name, value) {
window.line.postActivity({
type: "event",
type: 'event',
value: value,
from: this.getUser(),
name: name
@ -105,23 +104,23 @@ class GBUIApp extends React.Component {
postMessage(value) {
window.line.postActivity({
type: "message",
type: 'message',
text: value,
from: this.getUser()
});
}
configureChat() {
var botId = window.location.href.split("/")[3];
var botId = window.location.href.split('/')[3];
if (botId.indexOf('#') !== -1) {
botId = botId.split("#")[0];
botId = botId.split('#')[0];
}
if (!botId || botId === "") {
botId = "[default]";
if (!botId || botId === '') {
botId = '[default]';
}
fetch("/instances/" + botId)
fetch('/instances/' + botId)
.then(res => res.json())
.then(
result => {
@ -139,11 +138,9 @@ class GBUIApp extends React.Component {
authenticate() {
let _this_ = this;
let authority =
"https://login.microsoftonline.com/" +
this.state.instanceClient.authenticatorTenant;
let authority = 'https://login.microsoftonline.com/' + this.state.instanceClient.authenticatorTenant;
let graphScopes = ["Directory.AccessAsUser.All"];
let graphScopes = ['Directory.AccessAsUser.All'];
let userAgentApplication = new UserAgentApplication(
this.state.instanceClient.authenticatorClientId,
@ -159,18 +156,21 @@ class GBUIApp extends React.Component {
if (!userAgentApplication.isCallback(window.location.hash) && window.parent === window && !window.opener) {
var user = userAgentApplication.getUser();
if (user) {
userAgentApplication.acquireTokenSilent(graphScopes).then(function(accessToken) {
userAgentApplication.acquireTokenSilent(graphScopes).then(
function(accessToken) {
_this_.sendToken(accessToken);
}, function(error) {
},
function(error) {
console.log(error);
})
}
);
}
}
}
setupBotConnection() {
let _this_ = this;
window["botchatDebug"] = true;
window['botchatDebug'] = true;
const line = new DirectLine({
secret: this.state.instanceClient.secret
@ -179,30 +179,28 @@ class GBUIApp extends React.Component {
line.connectionStatus$.subscribe(connectionStatus => {
if (connectionStatus === ConnectionStatus.Online) {
_this_.setState({ line: line });
window['botConnection'] = line;
line.postActivity({
type: "event",
value: "startGB",
type: 'event',
value: 'startGB',
from: this.getUser(),
name: "startGB"
name: 'startGB'
});
}
});
window.line = line;
this.postEvent("startGB", true);
this.postEvent('startGB', true);
line.activity$
.filter(
activity =>
activity.type === "event" && activity.name === "loadInstance"
)
.filter(activity => activity.type === 'event' && activity.name === 'loadInstance')
.subscribe(activity => {
_this_.setState({ instance: activity.value });
_this_.authenticate()
_this_.authenticate();
});
line.activity$
.filter(activity => activity.type === "event" && activity.name === "stop")
.filter(activity => activity.type === 'event' && activity.name === 'stop')
.subscribe(activity => {
if (_this_.player) {
_this_.player.stop();
@ -210,7 +208,7 @@ class GBUIApp extends React.Component {
});
line.activity$
.filter(activity => activity.type === "event" && activity.name === "play")
.filter(activity => activity.type === 'event' && activity.name === 'play')
.subscribe(activity => {
_this_.setState({ playerType: activity.value.playerType });
_this_.player.play(activity.value.data);
@ -222,13 +220,11 @@ class GBUIApp extends React.Component {
}
render() {
let playerComponent = "";
let playerComponent = '';
if (this.state.playerType) {
switch (this.state.playerType) {
case "markdown":
case 'markdown':
playerComponent = (
<GBMarkdownPlayer
app={this}
@ -238,7 +234,7 @@ class GBUIApp extends React.Component {
/>
);
break;
case "bullet":
case 'bullet':
playerComponent = (
<GBBulletPlayer
app={this}
@ -248,7 +244,7 @@ class GBUIApp extends React.Component {
/>
);
break;
case "video":
case 'video':
playerComponent = (
<GBVideoPlayer
app={this}
@ -258,7 +254,7 @@ class GBUIApp extends React.Component {
/>
);
break;
case "image":
case 'image':
playerComponent = (
<GBImagePlayer
app={this}
@ -268,7 +264,7 @@ class GBUIApp extends React.Component {
/>
);
break;
case "pbi":
case 'pbi':
playerComponent = (
<GBPowerBIPlayer
app={this}
@ -278,7 +274,7 @@ class GBUIApp extends React.Component {
/>
);
break;
case "login":
case 'login':
playerComponent = (
<GBLoginPlayer
app={this}
@ -289,20 +285,14 @@ class GBUIApp extends React.Component {
);
break;
default:
console.log(
"GBERROR: Unknow player type specified on message from server."
);
console.log('GBERROR: Unknow player type specified on message from server.');
break;
}
}
let chat = <div />;
let gbCss = <div />;
let sideBar = (
<div className="sidebar">
<SidebarMenu chat={this.chat} instance={this.state.instance} />
@ -330,22 +320,21 @@ class GBUIApp extends React.Component {
// };
chat = (
<Chat
<ReactWebChat
ref={chat => {
this.chat = chat;
}}
locale={'pt-br'}
botConnection={this.state.line}
user={this.getUser()}
bot={{ id: "bot@gb", name: "Bot" }}
bot={{ id: 'bot@gb', name: 'Bot' }}
// speechOptions={speechOptions}
/>
);
}
if (!this.state.instance) {
sideBar = "";
sideBar = '';
}
return (

View file

@ -90,20 +90,20 @@ export class GBServer {
GBConfigService.init();
const core = new GBCoreService();
core.ensureAdminIsSecured();
// Ensures cloud / on-premises infrastructure is setup.
logger.info(`Establishing a development local proxy (ngrok)...`);
const proxyAddress: string = await core.ensureProxy(port);
logger.info(`Deploying packages...`);
const importer: GBImporter = new GBImporter(core);
const deployer: GBDeployer = new GBDeployer(core, importer);
const azureDeployer: AzureDeployerService = new AzureDeployerService(deployer);
const adminService: GBAdminService = new GBAdminService(core);
const conversationalService: GBConversationalService = new GBConversationalService(core);
core.ensureAdminIsSecured();
// Ensure that local development proxy is setup.
logger.info(`Establishing a development local proxy (ngrok)...`);
const proxyAddress: string = await core.ensureProxy(port);
// Creates a boot instance or load it frmo storage.
let bootInstance: IGBInstance = null;
try {
@ -113,10 +113,16 @@ export class GBServer {
await core.initStorage();
}
// Deploys system and user packages.
logger.info(`Deploying packages...`);
await core.loadSysPackages(core);
await core.checkStorage(azureDeployer);
await deployer.deployPackages(core, server, appPackages);
// Loads all bot instances.
logger.info(`Publishing instances...`);
const packageInstance = await importer.importIfNotExistsBotPackage(
GBConfigService.get('CLOUD_GROUP'),
@ -127,20 +133,27 @@ export class GBServer {
await core.saveInstance(fullInstance);
let instances: GuaribasInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress);
instances = await core.ensureInstances(instances, bootInstance, core);
if(!bootInstance) {
if (!bootInstance) {
bootInstance = instances[0];
}
// Builds minimal service infrastructure.
const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer);
await minService.buildMin(bootInstance, server, appPackages, instances, deployer);
logger.info(`Preparing default.gbui (it may take some additional time for the first time)...`);
// Deployment of local applications for the first time.
deployer.installDefaultGBUI();
logger.info(`The Bot Server is in RUNNING mode...`);
// Opens Navigator.
core.openBrowserInDevelopment();
return core;
} catch (err) {
logger.error(`STOP: ${err} ${err.stack ? err.stack : ''}`);
process.exit(1);
@ -152,10 +165,4 @@ export class GBServer {
// First line to run.
// const path = 'packages/default.gbdialog';
// const file = 'bot.vbs';
// const source =(path + '/' + file);
// let s = new GBVMService();
// s.run(source, path, null, null, null)
GBServer.run();