[Code] Remove download installation type and the flaky test not in use (#35626) (#35628)

This commit is contained in:
Fuyao Zhao 2019-04-25 15:56:14 -07:00 committed by GitHub
parent 761c8053d4
commit 5d59eb453c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 23 additions and 296 deletions

View file

@ -42,9 +42,6 @@ export function createJestConfig({
transformIgnorePatterns: [
'[/\\\\]node_modules[/\\\\].+\\.js$'
],
testPathIgnorePatterns: [
`.*[/\\\\]plugins[/\\\\]code[/\\\\].*`
],
snapshotSerializers: [
`${kibanaDirectory}/node_modules/enzyme-to-json/serializer`
],

View file

@ -6,21 +6,5 @@
export enum InstallationType {
Embed,
Download,
Plugin,
}
export enum InstallEventType {
DOWNLOADING,
UNPACKING,
DONE,
FAIL,
}
export interface InstallEvent {
langServerName: string;
eventType: InstallEventType;
progress?: number;
message?: string;
params?: any;
}

View file

@ -249,7 +249,7 @@ async function initCodeNode(server: Server, serverOptions: ServerOptions, log: L
fileRoute(codeServerRouter, serverOptions);
workspaceRoute(codeServerRouter, serverOptions);
symbolByQnameRoute(codeServerRouter, log);
installRoute(codeServerRouter, lspService, installManager);
installRoute(codeServerRouter, lspService);
lspRoute(codeServerRouter, lspService, serverOptions);
setupRoute(codeServerRouter);
}

View file

@ -87,6 +87,7 @@ beforeEach(async () => {
// @ts-ignore
installManager,
new ConsoleLoggerFactory(),
// @ts-ignore
repoConfigController
);
});

View file

@ -6,12 +6,8 @@
/* eslint-disable no-console */
import fs from 'fs';
import nock from 'nock';
import os from 'os';
import path from 'path';
import tar from 'tar-fs';
import URL from 'url';
import zlib from 'zlib';
import { LanguageServers } from './language_servers';
import { InstallManager } from './install_manager';
import { ServerOptions } from '../server_options';
@ -23,14 +19,9 @@ import { InstallationType } from '../../common/installation';
const LANG_SERVER_NAME = 'Java';
const langSrvDef = LanguageServers.find(l => l.name === LANG_SERVER_NAME)!;
const fakeTestDir = fs.mkdtempSync(path.join(os.tmpdir(), 'foo-'));
const fakePackageFile: string = path.join(fakeTestDir, 'fakePackage.tar.gz');
const fakeTarDir = path.join(fakeTestDir, 'fakePackage');
const fakeFile = 'fake.file';
const fakeContent = 'fake content';
const options: ServerOptions = {
langServerPath: fakeTestDir,
} as ServerOptions;
const fakeTestDir = path.join(os.tmpdir(), 'foo-');
const options: ServerOptions = {} as ServerOptions;
const server = new Server();
server.config = () => {
@ -48,68 +39,14 @@ server.config = () => {
const manager = new InstallManager(server, options);
beforeAll(async () => {
console.log('test folder is: ' + fakeTestDir);
beforeAll(() => {
fs.mkdirSync(fakeTestDir);
});
fs.mkdirSync(fakeTarDir);
// create a fake tar.gz package for testing
fs.writeFileSync(path.join(fakeTarDir, fakeFile), fakeContent);
return await new Promise(resolve => {
tar
.pack(fakeTarDir)
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream(fakePackageFile))
.on('finish', resolve);
});
});
beforeEach(() => {
const downloadUrl = URL.parse(
langSrvDef.downloadUrl!(langSrvDef, server.config().get('pkg.version'))
);
nock.cleanAll();
// mimic github's behavior, redirect to a s3 address
nock(`${downloadUrl.protocol}//${downloadUrl.host!}`)
.get(downloadUrl.path!)
.reply(302, '', {
Location: 'https://s3.amazonaws.com/file.tar.gz',
});
nock('https://s3.amazonaws.com')
.get('/file.tar.gz')
.replyWithFile(200, fakePackageFile, {
'Content-Length': fs.statSync(fakePackageFile).size.toString(),
'Content-Disposition': `attachment ;filename=${path.basename(fakePackageFile)}`,
});
});
afterEach(() => {
const p = manager.installationPath(langSrvDef);
if (p) rimraf.sync(p);
});
afterAll(() => {
nock.cleanAll();
rimraf.sync(fakeTestDir);
});
test('it can download a package', async () => {
langSrvDef.installationType = InstallationType.Download;
const p = await manager.downloadFile(langSrvDef);
console.log('package downloaded at ' + p);
expect(fs.existsSync(p)).toBeTruthy();
expect(fs.statSync(p).size).toBe(fs.statSync(fakePackageFile).size);
});
test('it can install language server', async () => {
expect(manager.status(langSrvDef)).toBe(LanguageServerStatus.NOT_INSTALLED);
langSrvDef.installationType = InstallationType.Download;
const installPromise = manager.install(langSrvDef);
expect(manager.status(langSrvDef)).toBe(LanguageServerStatus.INSTALLING);
await installPromise;
expect(manager.status(langSrvDef)).toBe(LanguageServerStatus.READY);
const installPath = manager.installationPath(langSrvDef)!;
const fakeFilePath = path.join(installPath, fakeFile);
expect(fs.existsSync(fakeFilePath)).toBeTruthy();
expect(fs.readFileSync(fakeFilePath, 'utf8')).toBe(fakeContent);
});
test('install language server by plugin', async () => {
langSrvDef.installationType = InstallationType.Plugin;
expect(manager.status(langSrvDef)).toBe(LanguageServerStatus.NOT_INSTALLED);

View file

@ -4,204 +4,40 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EventEmitter } from 'events';
import fs from 'fs';
import { Server } from 'hapi';
import mkdirp from 'mkdirp';
import fetch from 'node-fetch';
import path from 'path';
import tar from 'tar-fs';
import zlib from 'zlib';
import { InstallationType, InstallEvent, InstallEventType } from '../../common/installation';
import { InstallationType } from '../../common/installation';
import { LanguageServerStatus } from '../../common/language_server';
import { ServerOptions } from '../server_options';
import { LanguageServerDefinition } from './language_servers';
const DOWNLOAD_PROGRESS_WEIGHT = 0.9;
export class InstallManager {
private eventEmitter = new EventEmitter();
private readonly basePath: string;
private installing: Set<LanguageServerDefinition> = new Set();
constructor(readonly server: Server, readonly serverOptions: ServerOptions) {
this.basePath = serverOptions.langServerPath;
}
constructor(readonly server: Server, readonly serverOptions: ServerOptions) {}
public status(def: LanguageServerDefinition): LanguageServerStatus {
if (def.installationType === InstallationType.Embed) {
return LanguageServerStatus.READY;
}
if (def.installationType === InstallationType.Plugin) {
// @ts-ignore
const plugin = this.server.plugins[def.installationPluginName!];
if (plugin) {
const pluginPath = plugin.install.path;
if (fs.existsSync(pluginPath)) {
return LanguageServerStatus.READY;
}
// @ts-ignore
const plugin = this.server.plugins[def.installationPluginName!];
if (plugin) {
const pluginPath = plugin.install.path;
if (fs.existsSync(pluginPath)) {
return LanguageServerStatus.READY;
}
return LanguageServerStatus.NOT_INSTALLED;
}
if (this.installing.has(def)) {
return LanguageServerStatus.INSTALLING;
}
const installationPath = this.installationPath(def);
return fs.existsSync(installationPath!)
? LanguageServerStatus.READY
: LanguageServerStatus.NOT_INSTALLED;
}
public on(fn: (event: InstallEvent) => void) {
this.eventEmitter.on('event', fn);
}
public async install(def: LanguageServerDefinition) {
if (def.installationType === InstallationType.Download) {
if (!this.installing.has(def)) {
try {
this.installing.add(def);
const packageFile = await this.downloadFile(def);
await this.unPack(packageFile, def);
this.sendEvent({
langServerName: def.name,
eventType: InstallEventType.DONE,
progress: 1,
message: `install ${def.name} done.`,
});
} catch (e) {
this.sendEvent({
langServerName: def.name,
eventType: InstallEventType.FAIL,
message: `install ${def.name} failed. error: ${e.message}`,
});
throw e;
} finally {
this.installing.delete(def);
}
}
} else {
throw new Error("can't install this language server by downloading");
}
}
public async downloadFile(def: LanguageServerDefinition): Promise<string> {
const url =
typeof def.downloadUrl === 'function'
? def.downloadUrl(def, this.getKibanaVersion())
: def.downloadUrl;
const res = await fetch(url!);
if (!res.ok) {
throw new Error(`Unable to download language server ${def.name} from url ${url}`);
}
const installationPath = this.installationPath(def)!;
let filename: string;
const header = res.headers.get('Content-Disposition');
const FILE_NAME_HEAD_PREFIX = 'filename=';
if (header && header.includes(FILE_NAME_HEAD_PREFIX)) {
filename = header.substring(
header.indexOf(FILE_NAME_HEAD_PREFIX) + FILE_NAME_HEAD_PREFIX.length
);
} else {
filename = `${def.name}-${def.version}.tar.gz`;
}
const downloadPath = path.resolve(installationPath, '..', filename);
mkdirp.sync(installationPath);
const stream = fs.createWriteStream(downloadPath);
const total = parseInt(res.headers.get('Content-Length') || '0', 10);
let downloaded = 0;
return await new Promise<string>((resolve, reject) => {
res
// @ts-ignore
.body!.pipe(stream)
.on('error', (error: any) => {
reject(error);
})
.on('data', (data: Buffer) => {
downloaded += data.length;
this.sendEvent({
langServerName: def.name,
eventType: InstallEventType.DOWNLOADING,
progress: DOWNLOAD_PROGRESS_WEIGHT * (downloaded / total),
message: `downloading ${filename}(${downloaded}/${total})`,
params: {
downloaded,
total,
},
});
})
.on('finish', () => {
if (res.ok) {
resolve(downloadPath);
} else {
reject(new Error(res.statusText));
}
});
});
return LanguageServerStatus.NOT_INSTALLED;
}
public installationPath(def: LanguageServerDefinition): string | undefined {
if (def.installationType === InstallationType.Embed) {
return def.embedPath!;
} else if (def.installationType === InstallationType.Plugin) {
// @ts-ignore
const plugin: any = this.server.plugins[def.installationPluginName];
if (plugin) {
return plugin.install.path;
}
return undefined;
} else {
let version = def.version;
if (version) {
if (def.build) {
version += '-' + def.build;
}
} else {
version = this.getKibanaVersion();
}
return path.join(this.basePath, def.installationFolderName || def.name, version);
}
}
private getKibanaVersion(): string {
return this.server.config().get('pkg.version');
}
private async unPack(packageFile: string, def: LanguageServerDefinition) {
const dest = this.installationPath(def)!;
this.sendEvent({
langServerName: def.name,
eventType: InstallEventType.UNPACKING,
progress: DOWNLOAD_PROGRESS_WEIGHT,
message: `unpacking ${path.basename(packageFile)}`,
});
const ext = path.extname(packageFile);
switch (ext) {
case '.gz':
await this.unPackTarball(packageFile, dest);
break;
default:
// todo support .zip
throw new Error(`unknown extension "${ext}"`);
// @ts-ignore
const plugin: any = this.server.plugins[def.installationPluginName];
if (plugin) {
return plugin.install.path;
}
// await decompress(packageFile, '/tmp/1/1');
}
private sendEvent(event: InstallEvent) {
this.eventEmitter.emit('event', event);
}
private unPackTarball(packageFile: string, dest: string) {
return new Promise((resolve, reject) => {
fs.createReadStream(packageFile)
.on('error', reject)
.pipe(zlib.createGunzip())
.on('error', reject)
.pipe(tar.extract(dest))
.on('error', reject)
.on('finish', resolve);
});
return undefined;
}
}

View file

@ -6,17 +6,11 @@
import * as Boom from 'boom';
import { Request } from 'hapi';
import { InstallationType } from '../../common/installation';
import { InstallManager } from '../lsp/install_manager';
import { LanguageServerDefinition, LanguageServers } from '../lsp/language_servers';
import { LspService } from '../lsp/lsp_service';
import { CodeServerRouter } from '../security';
export function installRoute(
server: CodeServerRouter,
lspService: LspService,
installManager: InstallManager
) {
export function installRoute(server: CodeServerRouter, lspService: LspService) {
const kibanaVersion = server.server.config().get('pkg.version') as string;
const status = (def: LanguageServerDefinition) => ({
name: def.name,
@ -51,24 +45,4 @@ export function installRoute(
},
method: 'GET',
});
server.route({
path: '/api/code/install/{name}',
requireAdmin: true,
async handler(req: Request) {
const name = req.params.name;
const def = LanguageServers.find(d => d.name === name);
if (def) {
if (def.installationType === InstallationType.Plugin) {
return Boom.methodNotAllowed(
`${name} language server can only be installed by plugin ${def.installationPluginName}`
);
}
await installManager.install(def);
} else {
return Boom.notFound(`language server ${name} not found.`);
}
},
method: 'POST',
});
}

View file

@ -29,8 +29,6 @@ export class ServerOptions {
public readonly credsPath = resolve(this.config.get('path.data'), 'code/credentials');
public readonly langServerPath = resolve(this.config.get('path.data'), 'code/langserver');
public readonly jdtWorkspacePath = resolve(this.config.get('path.data'), 'code/jdt_ws');
public readonly jdtConfigPath = resolve(this.config.get('path.data'), 'code/jdt_config');