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: Enable external plugins to provide storage, publishing and auth mechanisms #1887

Closed
wants to merge 11 commits into from
1,948 changes: 1,948 additions & 0 deletions Composer/packages/server/package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Composer/packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"dotenv": "^8.1.0",
"ejs": "^2.7.1",
"express": "^4.16.4",
"express-session": "^1.17.0",
"form-data": "^2.3.3",
"format-message": "^6.2.1",
"globby": "^9.1.0",
Expand All @@ -78,6 +79,8 @@
"luis-apis": "2.5.1",
"minimatch": "^3.0.4",
"morgan": "^1.9.1",
"passport": "^0.4.1",
"path-to-regexp": "^6.1.0",
"vscode-languageserver": "^5.3.0-next",
"vscode-ws-jsonrpc": "^0.1.1",
"ws": "^5.0.0"
Expand Down
15 changes: 15 additions & 0 deletions Composer/packages/server/src/controllers/publisher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import pluginLoader from '../services/pluginLoader';

export const PublishController = {
Copy link
Contributor

@boydc2014 boydc2014 Feb 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we should consider make this a REST api managing "publishers" which means:

  1. Get /publishers, return a bunches of publisher plugins with ID, having this will enable the UI to list out all the publishers and their recent activities.
  2. POST /publishers/:pid/publish, publishing using one publisher
  3. POST /publishers/:pid/(rollback/history/status)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agree, this is part of the work I've done already in another branch!

publish: (req, res) => {
const method = req.params.method;
if (pluginLoader.extensions.publish[method]) {
res.send(`Got valid request to publish`);
} else {
res.send(`Got invalid request to publish`);
}
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// Licensed under the MIT License.

import fs from 'fs';
import Path from 'path';

import axios from 'axios';
import archiver from 'archiver';
import FormData from 'form-data';
import { FileInfo } from '@bfc/indexers';

import { BotProjectService } from '../../services/project';
import { DialogSetting } from '../bot/interface';
Expand Down Expand Up @@ -43,9 +43,10 @@ export class CSharpBotConnector implements IBotConnector {
if (currentProject === undefined) {
throw new Error('no project is opened, nothing to sync');
}
const dir = Path.join(currentProject.dataDir);
// call .index() in order to load all the files from storage into memory
await currentProject.index();
const luisConfig = currentProject.luPublisher.getLuisConfig();
await this.archiveDirectory(dir, './tmp.zip');
await this.createZipFromFiles(currentProject.files, './tmp.zip');
const content = fs.readFileSync('./tmp.zip');

const form = new FormData();
Expand Down Expand Up @@ -73,20 +74,44 @@ export class CSharpBotConnector implements IBotConnector {
}
};

archiveDirectory = (src: string, dest: string) => {
/**
* given an array of FileInfo objects (resulting from indexing a project),
* create a zip file of the contents. The original files do not have to live on the local filesystem.
*/
createZipFromFiles = (files: FileInfo[], dest: string) => {
return new Promise((resolve, reject) => {
const archive = archiver('zip');
const output = fs.createWriteStream(dest);

archive.pipe(output);
archive.directory(src, false);
for (const f in files) {
const file = files[f];
archive.append(file.content, { name: file.relativePath });
}
archive.finalize();

output.on('close', () => resolve(archive));
archive.on('error', err => reject(err));
});
};

/**
* given a local folder of files, create a zipfile.
* only works with local files (not those managed by a storage provider)
* This method is deprecated in favor of createZipFromFiles.
*/
// archiveDirectory = (src: string, dest: string) => {
// return new Promise((resolve, reject) => {
// const archive = archiver('zip');
// const output = fs.createWriteStream(dest);
// archive.pipe(output);
// archive.directory(src, false);
// archive.finalize();
// output.on('close', () => resolve(archive));
// archive.on('error', err => reject(err));
// });
// };

getEditingStatus = (): Promise<boolean> => {
return new Promise(resolve => {
resolve(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Licensed under the MIT License.

import { Path } from '../../utility/path';
import { LocalDiskStorage } from '../storage/localDiskStorage';
import { IFileStorage } from '../storage/interface';
import StorageService from '../../services/storage';

import { ISettingManager, OBFUSCATED_VALUE } from '.';

Expand All @@ -11,11 +12,12 @@ const subPath = 'ComposerDialogs/settings/appsettings.json';

export class FileSettingManager implements ISettingManager {
private basePath: string;
protected storage: LocalDiskStorage;
protected storage: IFileStorage;

constructor(basePath: string) {
this.basePath = basePath;
this.storage = new LocalDiskStorage();
// todo: do we need to pass in a storage client id? there can only be one at a time.
this.storage = StorageService.getStorageClient('default');
}

public get = async (slot: string, obfuscate: boolean): Promise<any> => {
Expand Down
21 changes: 12 additions & 9 deletions Composer/packages/server/src/models/storage/storageFactory.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { StorageConnection, IFileStorage } from './interface';
import pluginLoader from '../../services/pluginLoader';

import { LocalDiskStorage } from './localDiskStorage';
import { AzureBlobStorage } from './azureBlobStorage';
import { StorageConnection, IFileStorage } from './interface';

export class StorageFactory {
public static createStorageClient(conn: StorageConnection): IFileStorage {
switch (conn.type) {
case 'LocalDisk':
return new LocalDiskStorage();
case 'AzureBlobStorage':
return new AzureBlobStorage(conn);
default:
throw new Error(`unknow storage type ${conn.type}`);
if (pluginLoader.extensions.storage && pluginLoader.extensions.storage.customStorageClass) {
const customStorageClass = pluginLoader.extensions.storage.customStorageClass;
if (customStorageClass) {
return new customStorageClass(conn) as IFileStorage;
}
}

// otherwise...
return new LocalDiskStorage();
}
}
3 changes: 3 additions & 0 deletions Composer/packages/server/src/router/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import express, { Router, Request, Response, NextFunction, RequestHandler } from
import { ProjectController } from '../controllers/project';
import { StorageController } from '../controllers/storage';
import { BotConnectorController } from '../controllers/connector';
import { PublishController } from '../controllers/publisher';
import { AssetController } from '../controllers/asset';

const router: Router = express.Router({});
Expand Down Expand Up @@ -45,6 +46,8 @@ router.get('/launcher/publishHistory', BotConnectorController.getPublishHistory)
router.post('/launcher/publish', BotConnectorController.publish);
router.post('/launcher/publish/:label', BotConnectorController.publish);

router.get('/publish/:method', PublishController.publish);

//assets
router.get('/assets/projectTemplates', AssetController.getProjTemplates);

Expand Down
Loading