Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Start Botproject runtime in bot folder #1672

Merged
merged 54 commits into from
Jan 9, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
69ae226
start runtime from samplpebot csharp runtime
VanyLaw Nov 28, 2019
1f3b631
fix
VanyLaw Nov 28, 2019
b8670c9
fix
VanyLaw Nov 28, 2019
6b04384
fix
VanyLaw Nov 28, 2019
1e0ca12
use logger instead of console log
VanyLaw Nov 28, 2019
fbf2ed7
Merge branch 'master' into wenyluo/csConnector
luhan2017 Nov 29, 2019
1b17984
Merge branch 'master' into wenyluo/csConnector
luhan2017 Dec 2, 2019
3964fd7
fix comment
VanyLaw Dec 2, 2019
c0f1411
fix comment
VanyLaw Dec 2, 2019
0334bb8
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 3, 2019
306a0d2
add port usable validation and use log instead console log
VanyLaw Dec 3, 2019
99a2882
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 3, 2019
14c4a21
fix comments
VanyLaw Dec 4, 2019
4d989e6
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 4, 2019
543785b
move build script from template to server
VanyLaw Dec 4, 2019
396e7c7
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 5, 2019
1cc7e70
polish output error message
VanyLaw Dec 5, 2019
4336354
remove environment
VanyLaw Dec 6, 2019
a287f64
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 6, 2019
ffb617e
fix comments and add back the build script into template runtime
VanyLaw Dec 6, 2019
9167b0f
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 6, 2019
3d67936
add migrate old bot feature
VanyLaw Dec 9, 2019
6a28ec5
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 9, 2019
4394dc1
Merge branch 'master' into wenyluo/csConnector
luhan2017 Dec 9, 2019
3a76056
Merge branch 'wenyluo/csConnector' of https://github.com/microsoft/Bo…
VanyLaw Dec 9, 2019
95da7fc
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 9, 2019
07b0120
Merge branch 'master' into wenyluo/csConnector
luhan2017 Dec 9, 2019
a02ee26
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 10, 2019
5aebd48
change docker file
VanyLaw Dec 10, 2019
af79c2c
remove annotation
VanyLaw Dec 10, 2019
2985023
fix docker
VanyLaw Dec 11, 2019
0d98187
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 11, 2019
019f623
simpler dockerfile
VanyLaw Dec 12, 2019
c7ae546
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 12, 2019
32ac161
Merge branch 'master' into wenyluo/csConnector
VanyLaw Dec 13, 2019
c05ec0d
fix dockerfile bug and port mapping
VanyLaw Dec 13, 2019
7a7a2cb
add bash script for unix, and remove pwsh from docker
VanyLaw Dec 17, 2019
e3c2de8
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 17, 2019
0b0bca9
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 18, 2019
4e1bfb5
fix typo and some comments
VanyLaw Dec 19, 2019
1090b93
split dockerfile into two
VanyLaw Dec 19, 2019
0bf8040
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 20, 2019
861d57e
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 20, 2019
a791621
use nultistage instead of two dockerfile
VanyLaw Dec 20, 2019
6a09761
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 27, 2019
3f7d035
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Dec 30, 2019
9b9e139
Merge branch 'master' into wenyluo/csConnector
cwhitten Jan 6, 2020
243f9c8
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Jan 7, 2020
803fe7a
update yarn lock
VanyLaw Jan 7, 2020
20036eb
Merge remote-tracking branch 'origin/master' into wenyluo/csConnector
VanyLaw Jan 7, 2020
d4be796
Merge branch 'master' into wenyluo/csConnector
cwhitten Jan 8, 2020
551c4a2
Merge branch 'master' into wenyluo/csConnector
cwhitten Jan 8, 2020
6ce7143
add more debug logs
a-b-r-o-w-n Jan 8, 2020
429886e
fix settings file mis-created in server folder
VanyLaw Jan 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BotProject/Templates/CSharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using System.IO;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
Expand All @@ -24,8 +26,20 @@ public static IWebHost BuildWebHost(string[] args) =>
config
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddJsonFile($"ComposerDialogs/settings/appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"luis.settings.{env.EnvironmentName}.{luisAuthoringRegion}.json", optional: true, reloadOnChange: true)
.AddJsonFile($"luis.settings.{Environment.UserName}.{luisAuthoringRegion}.json", optional: true, reloadOnChange: true);
try
{
foreach (string filePath in Directory.GetFiles($"ComposerDialogs", "generated/luis.settings.*.json"))
{
config.AddJsonFile(filePath, optional: true, reloadOnChange: true);
}
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}

if (env.IsDevelopment())
{
Expand Down
25 changes: 25 additions & 0 deletions BotProject/Templates/CSharp/Scripts/build_runtime.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Param(
[object] $config,
[string] $customSettingFolder,
[string] $luisAuthroingKey,
[SecureString] $appPassword,
[string] $projFolder = $(Join-Path $(Get-Location) BotProject CSharp)
)

if ($PSVersionTable.PSVersion.Major -lt 6){
Write-Host "! Powershell 6 is required, current version is $($PSVersionTable.PSVersion.Major), please refer following documents for help."
Write-Host "For Windows - https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-6"
Write-Host "For Mac - https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos?view=powershell-6"
Break
}

if ((dotnet --version) -lt 3) {
Write-Host "! dotnet core 3.0 is required, please refer following documents for help."
Write-Host "https://dotnet.microsoft.com/download/dotnet-core/3.0"
Break
}

# Init user secret id
dotnet user-secrets init
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved

dotnet build
5 changes: 4 additions & 1 deletion Composer/packages/client/src/store/action/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

import get from 'lodash/get';
import { SensitiveProperties } from '@bfc/shared';
import { SensitiveProperties as defaultSensitiveProperties } from '@bfc/shared';

import { ActionCreator, DialogSetting } from '../types';
import settingsStorage from '../../utils/dialogSettingStorage';
Expand All @@ -26,6 +26,9 @@ export const setSettings: ActionCreator = async (
},
});
// set value in local storage
const SensitiveProperties = settings.SensitiveProperties
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
? settings.SensitiveProperties
: defaultSensitiveProperties;
for (const property of SensitiveProperties) {
const propertyValue = get(settings, property);
settingsStorage.setField(botName, property, propertyValue ? propertyValue : '');
Expand Down
5 changes: 4 additions & 1 deletion Composer/packages/client/src/store/reducer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import get from 'lodash/get';
import set from 'lodash/set';
import { dialogIndexer } from '@bfc/indexers/lib/dialogIndexer';
import { SensitiveProperties } from '@bfc/shared';
import { SensitiveProperties as defaultSensitiveProperties } from '@bfc/shared';

import { ActionTypes, FileTypes } from '../../constants';
import { DialogSetting, ReducerFunc } from '../types';
Expand All @@ -18,6 +18,8 @@ const projectFiles = ['bot', 'botproj'];

// if user set value in terminal or appsetting.json, it should update the value in localStorage
const refreshLocalStorage = (botName: string, settings: DialogSetting) => {
const SensitiveProperties = settings.SensitiveProperties ? settings.SensitiveProperties : defaultSensitiveProperties;
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
console.log(SensitiveProperties);
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
for (const property of SensitiveProperties) {
const value = get(settings, property);
if (value) {
Expand All @@ -29,6 +31,7 @@ const refreshLocalStorage = (botName: string, settings: DialogSetting) => {
// merge sensitive values in localStorage
const mergeLocalStorage = (botName: string, settings: DialogSetting) => {
const localSetting = settingStorage.get(botName);
const SensitiveProperties = settings.SensitiveProperties ? settings.SensitiveProperties : defaultSensitiveProperties;
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
if (localSetting) {
for (const property of SensitiveProperties) {
const value = get(localSetting, property);
Expand Down
4 changes: 3 additions & 1 deletion Composer/packages/server/src/controllers/connector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import merge from 'lodash/merge';

import { BotEnvironments } from '../models/connector';
import { EnvironmentProvider } from '../models/environment';
Expand Down Expand Up @@ -33,7 +34,8 @@ async function getPublishHistory(req: any, res: any) {
async function sync(req: any, res: any) {
try {
const environment = EnvironmentProvider.getCurrent();
await environment.getBotConnector().sync({ ...req.body, user: req.user });
const settingsInDisk = await environment.getSettingsManager().get('', false);
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
await environment.getBotConnector().sync(merge(settingsInDisk, req.body, { user: req.user }));
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
res.send('OK');
} catch (error) {
res.status(400).json({
Expand Down
173 changes: 132 additions & 41 deletions Composer/packages/server/src/models/connector/csharpBotConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,142 @@
// Licensed under the MIT License.

import fs from 'fs';
import Path from 'path';
import { ChildProcess, spawn } from 'child_process';

import axios from 'axios';
import archiver from 'archiver';
import FormData from 'form-data';

import { BotProjectService } from '../../services/project';
import { DialogSetting } from '../bot/interface';
import { Path } from '../../utility/path';

import { BotConfig, BotEnvironments, BotStatus, IBotConnector, IPublishHistory } from './interface';

let runtime: ChildProcess | null = null;
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
export class CSharpBotConnector implements IBotConnector {
private adminEndpoint: string;
public status: BotStatus = BotStatus.NotConnected;
private endpoint: string;
constructor(adminEndpoint: string, endpoint: string) {
this.adminEndpoint = adminEndpoint;
constructor(endpoint: string) {
this.endpoint = endpoint;
}

public status: BotStatus = BotStatus.NotConnected;
private stop = () => {
shutdown();
this.status = BotStatus.NotConnected;
};

connect = async (_: BotEnvironments, __: string) => {
// confirm bot runtime is listening here
try {
await axios.get(this.adminEndpoint + '/api/admin');
} catch (err) {
throw new Error(err);
private buildProcess = async (dir: string): Promise<number | null> => {
return new Promise((resolve, reject) => {
const build = spawn(`pwsh ./Scripts/build_runtime.ps1`, {
cwd: dir,
detached: true,
shell: true,
stdio: ['ignore', 'ignore', 'inherit'],
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
});
console.log(`build pid : ${build.pid}`);
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved

build.stderr &&
build.stderr.on('data', function(err) {
reject(err.toString());
});

build.on('exit', function(code) {
resolve(code);
});
});
};

private getConnectorConfig = (config: DialogSetting) => {
const configList: string[] = [];
if (config.MicrosoftAppPassword) {
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
configList.push('--MicrosoftAppPassword');
configList.push(config.MicrosoftAppPassword);
}
if (config.luis) {
if (config.luis.authoringKey) {
configList.push('--luis:endpointKey');
configList.push(config.luis.authoringKey);
}
if (config.luis.authoringRegion) {
configList.push('--luis:endpoint');
configList.push(`https://${config.luis.authoringRegion}.api.cognitive.microsoft.com`);
}
}

this.status = BotStatus.NotConnected;
return configList;
};
private addListeners = (child: ChildProcess, handler: Function) => {
if (child.stdout !== null) {
child.stdout.on('data', (data: any) => {
console.log(`stdout: ${data}`);
});
}

if (child.stderr !== null) {
child.stderr.on('data', (data: any) => {
console.log(`stderr: ${data}`);
});
}

child.on('close', code => {
console.log(`close ${code}`);
handler();
});

child.on('error', (err: any) => {
console.log(`stderr: ${err}`);
});

child.on('exit', code => {
console.log(`exit: ${code}`);
handler();
});

return `${this.endpoint}/api/messages`;
child.on('message', msg => {
console.log(msg);
});

child.on('disconnect', code => {
console.log(`disconnect: ${code}`);
handler();
});
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
};

sync = async (config: DialogSetting) => {
// archive the project
// send to bot runtime service
private getBotPath = () => {
const currentProject = BotProjectService.getCurrentBotProject();
if (currentProject === undefined) {
throw new Error('no project is opened, nothing to sync');
}
const dir = Path.join(currentProject.dataDir);
const luisConfig = currentProject.luPublisher.getLuisConfig();
await this.archiveDirectory(dir, './tmp.zip');
const content = fs.readFileSync('./tmp.zip');

const form = new FormData();
form.append('file', content, 'bot.zip');
return Path.join(currentProject.dir);
};

if (luisConfig && luisConfig.authoringKey !== null && !currentProject.checkLuisPublished()) {
throw new Error('Please publish your Luis models');
}
private start = async (dir: string, config: DialogSetting) => {
runtime = spawn(
'dotnet',
['bin/Debug/netcoreapp2.1/BotProject.dll', `--urls`, this.endpoint, ...this.getConnectorConfig(config)],
{
detached: true,
cwd: dir,
stdio: ['ignore', 'ignore', 'inherit'],
}
);
console.log(`start runtime at ${runtime.pid}`);
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
this.addListeners(runtime, this.stop);
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
this.status = BotStatus.Connected;
};

if (luisConfig) {
form.append('endpointKey', luisConfig.endpointKey || luisConfig.authoringKey || '');
}
connect = async (_: BotEnvironments, __: string) => {
// confirm bot runtime can listening here
return Promise.resolve(`${this.endpoint}/api/messages`);
};

config = {
...(await currentProject.settingManager.get(currentProject.environment.getDefaultSlot(), false)),
...config,
};
if (config.MicrosoftAppPassword) {
form.append('microsoftAppPassword', config.MicrosoftAppPassword);
}
sync = async (config: DialogSetting) => {
try {
await axios.post(this.adminEndpoint + '/api/admin', form, { headers: form.getHeaders() });
this.stop();
const dir = this.getBotPath();
await this.buildProcess(dir);
await this.start(dir, config);
} catch (err) {
throw new Error('Unable to sync content to bot runtime');
this.stop();
throw new Error(err);
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
}
};

Expand Down Expand Up @@ -109,3 +177,26 @@ export class CSharpBotConnector implements IBotConnector {
});
};
}

process.on('SIGINT', () => {
console.log('[SIGINT] start graceful shutdown');
shutdown();
process.exit(1);
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
});
process.on('SIGTERM', () => {
console.log('[SIGTERM] start graceful shutdown');
shutdown();
process.exit(1);
});
process.on('SIGQUIT', () => {
console.log('[SIGQUIT] start graceful shutdown');
shutdown();
process.exit(1);
});
function shutdown() {
if (runtime) {
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
console.log(`kill runtime before exit at ${runtime.pid}`);
runtime.kill('SIGKILL');
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
runtime = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class DefaultEnvironment implements IEnvironment {
public constructor(config: IEnvironmentConfig) {
this.config = config;
this.settingManager = new DefaultSettingManager(this.config.basePath);
this.botConnector = new CSharpBotConnector(this.config.adminEndpoint, this.config.endpoint);
this.botConnector = new CSharpBotConnector(this.config.endpoint);
}

public getEnvironmentName(_: string): string | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

import omit from 'lodash/omit';
import { SensitiveProperties } from '@bfc/shared';
import { SensitiveProperties as defaultSensitiveProperties } from '@bfc/shared';

import { Path } from '../../utility/path';

Expand Down Expand Up @@ -34,6 +34,7 @@ export class DefaultSettingManager extends FileSettingManager {
};

private filterOutSensitiveValue = (obj: any) => {
const SensitiveProperties = obj.SensitiveProperties ? obj.SensitiveProperties : defaultSensitiveProperties;
VanyLaw marked this conversation as resolved.
Show resolved Hide resolved
if (obj && typeof obj === 'object') {
return omit(obj, SensitiveProperties);
}
Expand Down