Continue work on #1895

- Introduce Chefile data with generation and parsing : Allow to customize port number and workspace name for now
- Introduce logger to get same kind of logs than with che-launcher
- Fix documentation
- Improve sh scripts to generate nightly or latest or be started from any directory

Change-Id: I6da278cf1724fbb5a50526a7555e3059477e80fb
Signed-off-by: Florent BENOIT <fbenoit@codenvy.com>
6.19.x
Florent BENOIT 2016-08-03 18:16:07 +02:00
parent 4b37b05a1c
commit fb82bf80b9
15 changed files with 374 additions and 86 deletions

View File

@ -12,7 +12,7 @@
# To use:
# `docker run -v /var/run/docker.sock:/var/run/docker.sock \
# -v "$PWD":"$PWD" --rm codenvy/che-file \
# /bin/che $PWD <init|up>`
# $PWD <init|up>`
#
# where [COMMAND]:
# init -- Initialize configuration files for this directory

View File

@ -26,6 +26,7 @@ Run script
```
docker run -v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD":"$PWD" --rm codenvy/che-file \
/bin/che $PWD <init|up>
$PWD <init|up>
```
note: if Eclipse Che is already started, it does not handle yet this state

View File

@ -5,6 +5,13 @@
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
if [ "latest" = "$1" ]
then
TAG="latest"
else
TAG="nightly"
fi
DIR=$(cd "$(dirname "$0")"; cd ..; pwd)
echo "Building Docker Image from $DIR directory"
cd $DIR && docker build -t codenvy/che-file -f che-file/Dockerfile .
echo "Building Docker Image from $DIR directory with tag $TAG"
cd $DIR && docker build -t codenvy/che-file:$TAG -f che-file/Dockerfile .

View File

@ -5,6 +5,13 @@
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
if [ "latest" = "$1" ]
then
TAG="latest"
else
TAG="nightly"
fi
DIR=$(cd "$(dirname "$0")"; cd ..; pwd)
echo "Building Docker Image from $DIR directory"
cd $DIR && docker build -t codenvy/che-test -f che-test/Dockerfile .
echo "Building Docker Image from $DIR directory with tag $TAG"
cd $DIR && docker build -t codenvy/che-test:$TAG -f che-test/Dockerfile .

View File

@ -5,6 +5,9 @@
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
DIR=$(cd "$(dirname "$0")"; pwd)
echo "Compiling from $DIR directory"
cd $DIR
docker run --rm -v $(pwd):/usr/src/app -w /usr/src/app node:6 /bin/bash -c "/usr/src/app/dependencies/compile/node_modules/typescript/bin/tsc --outDir /usr/src/app/lib /usr/src/app/src/index.ts && groupadd user && useradd -g user user && (chown --silent -R user.user /usr/src/app || true)"
if [ $? -eq 0 ]; then

View File

@ -15,19 +15,23 @@ import {RecipeBuilder} from './recipebuilder';
import {Workspace} from './workspace';
import {WorkspaceDto} from './dto/workspacedto';
import {Websocket} from './websocket';
import {CheFileStructWorkspace} from './chefile-struct/che-file-struct';
import {CheFileStruct} from './chefile-struct/che-file-struct';
import {CheFileServerTypeStruct} from "./chefile-struct/che-file-struct";
import {Log} from "./log";
export class CheFile {
// grab default hostname from the remote ip component
DEFAULT_HOSTNAME: string;
debug: boolean = false;
times: number = 10;
// gloabl var
waitDone = false;
che: { hostname: string, server: any };
chefileStruct: CheFileStruct;
chefileStructWorkspace: CheFileStructWorkspace;
dockerContent;
// requirements
@ -53,40 +57,54 @@ export class CheFile {
constructor(args) {
this.args = args;
this.DEFAULT_HOSTNAME = new RemoteIp().getIp();
this.che = {hostname: this.DEFAULT_HOSTNAME, server: 'tmp'};
this.currentFolder = this.path.resolve(args[0]);
this.folderName = this.path.basename(this.currentFolder);
this.cheFile = this.path.resolve(this.currentFolder, 'chefile');
this.cheFile = this.path.resolve(this.currentFolder, 'Chefile');
this.dotCheFolder = this.path.resolve(this.currentFolder, '.che');
this.confFolder = this.path.resolve(this.dotCheFolder, 'conf');
this.workspacesFolder = this.path.resolve(this.dotCheFolder, 'workspaces');
this.chePropertiesFile = this.path.resolve(this.confFolder, 'che.properties');
this.initDefault();
}
initDefault() {
this.chefileStruct = new CheFileStruct();
this.chefileStruct.server.ip = new RemoteIp().getIp();
this.chefileStruct.server.port = 8080;
this.chefileStruct.server.type = 'local';
this.chefileStructWorkspace = new CheFileStructWorkspace();
this.chefileStructWorkspace.name = 'local';
this.chefileStructWorkspace.commands[0] = {name: 'hello'};
}
startsWith(value:string, searchString: string) : boolean {
return value.substr(0, searchString.length) === searchString;
}
run() {
Log.context = 'ECLIPSE CHE FILE';
if (this.args.length == 0) {
console.log('only init and up commands are supported.');
Log.getLogger().error('only init and up commands are supported.');
return;
} else if ('init' === this.args[1]) {
this.init();
} else if ('up' === this.args[1]) {
this.init();
// call init if not already done
if (!this.isInitialized()) {
this.init();
}
this.up();
} else {
console.log('Invalid arguments ' + this.args +': Only init and up commands are supported.');
Log.getLogger().error('Invalid arguments ' + this.args +': Only init and up commands are supported.');
return;
}
@ -98,35 +116,89 @@ export class CheFile {
this.fs.statSync(this.cheFile);
// we have a file
} catch (e) {
console.log('No chefile defined, use default settings');
Log.getLogger().debug('No chefile defined, use default settings');
return;
}
// load the chefile script if defined
var script_code = this.fs.readFileSync(this.cheFile);
// setup the bindings for the script
this.che.server = {};
this.che.server.ip = this.che.hostname;
// create sandboxed object
var sandbox = { "che": this.che, "console": console};
var sandbox = { "che": this.chefileStruct, "workspace": this.chefileStructWorkspace, "console": console};
var script = this.vm.createScript(script_code);
script.runInNewContext(sandbox);
if (this.debug) {
console.log('Che file parsing object is ', this.che);
Log.getLogger().debug('Che file parsing object is ', this.chefileStruct);
}
/**
* Check if directory has been initialized or not
* @return true if initialization has been done
*/
isInitialized() : boolean {
try {
this.fs.statSync(this.chePropertiesFile);
return true;
} catch (e) {
return false;
}
}
/**
* Write a default chefile
*/
writeDefaultChefile() {
// get json string from object
let stringified = JSON.stringify(this.chefileStruct, null, 4);
let content = '';
let flatChe = this.flatJson('che', this.chefileStruct);
flatChe.forEach((value, key) => {
Log.getLogger().debug( 'the value is ' + value.toString() + ' for key' + key);
content += key + '=' + value.toString() + '\n';
});
let flatWorkspace = this.flatJson('workspace', this.chefileStructWorkspace);
flatWorkspace.forEach((value, key) => {
Log.getLogger().debug( 'the flatWorkspace value is ' + value.toString() + ' for key' + key);
content += key + '=' + value.toString() + '\n';
});
// write content of this.che object
this.fs.writeFileSync(this.cheFile, content);
Log.getLogger().info('File', this.cheFile, 'written.')
}
init() {
// needs to create folders
this.initCheFolders();
this.setupConfigFile();
// Check if we have internal che.properties file. If we have, throw error
if (this.isInitialized()) {
Log.getLogger().warn('Che already initialized');
} else {
// needs to create folders
this.initCheFolders();
this.setupConfigFile();
// write a default chefile if there is none
try {
this.fs.statSync(this.cheFile);
Log.getLogger().debug('Chefile is present at ', this.cheFile);
} catch (e) {
// write default
Log.getLogger().debug('Write a default Chefile at ', this.cheFile);
this.writeDefaultChefile();
}
Log.getLogger().info('ADDING', this.dotCheFolder, 'DIRECTORY');
}
console.log('Che configuration initialized in ' + this.dotCheFolder );
}
up() {
@ -136,11 +208,11 @@ export class CheFile {
try {
var statsPropertiesFile = this.fs.statSync(this.chePropertiesFile);
} catch (e) {
console.log('No che configured. che init has been done ?');
Log.getLogger().error('No che configured. che init has been done ?');
return;
}
console.log('Starting che');
Log.getLogger().info('STARTING ECLIPSE CHE SILENTLY');
// needs to invoke docker run
this.cheBoot();
@ -152,12 +224,14 @@ export class CheFile {
// Create workspace based on the remote hostname and workspacename
// if custom docker content is provided, use it
createWorkspace(remoteHostname, workspaceName, dockerContent) {
/**
* Create workspace based on the remote hostname and workspacename
* if custom docker content is provided, use it
*/
createWorkspace(dockerContent) {
var options = {
hostname: remoteHostname,
port: 8080,
hostname: this.chefileStruct.server.ip,
port: this.chefileStruct.server.port,
path: '/api/workspace?account=',
method: 'POST',
headers: {
@ -173,13 +247,13 @@ export class CheFile {
this.displayUrlWorkspace(JSON.parse(body));
} else {
// error
console.log('Invalid response from the server side. Aborting');
console.log('response was ' + body);
Log.getLogger().error('Invalid response from the server side. Aborting');
Log.getLogger().error('response was ' + body);
}
});
});
req.on('error', (e) => {
console.log('problem with request: ' + e.message);
Log.getLogger().error('problem with request: ' + e.message);
});
var workspace = {
@ -198,7 +272,7 @@ export class CheFile {
"links": []
}], "name": "default"
}],
"name": workspaceName,
"name": this.chefileStructWorkspace.name,
"links": [],
"description": null
};
@ -219,14 +293,14 @@ export class CheFile {
var link = links[i];
if (link.rel === 'ide url') {
found = true;
console.log('Open browser to ' + link.href);
Log.getLogger().info('WORKSPACE AT ' + link.href);
}
i++;
}
if (!found) {
console.log('Workspace successfully started but unable to find workspace link');
Log.getLogger().warn('Workspace successfully started but unable to find workspace link');
}
@ -313,37 +387,33 @@ export class CheFile {
var commandLine: string = 'docker run ' +
' -v /var/run/docker.sock:/var/run/docker.sock' +
' -e CHE_PORT=' + this.chefileStruct.server.port +
' -e CHE_DATA_FOLDER=' + this.workspacesFolder +
' -e CHE_CONF_FOLDER=' + this.confFolder +
' codenvy/che-launcher:nightly start';
if (this.debug) {
console.log('Executing command line', commandLine);
}
Log.getLogger().debug('Executing command line', commandLine);
var child = this.exec(commandLine , function callback(error, stdout, stderr) {
//console.log('error is ' + error, stdout, stderr);
}
);
//if (debug) {
child.stdout.on('data', (data) => {
console.log(data.toString());
Log.getLogger().debug(data.toString());
});
//}
}
// test if can connect on port 8080
// test if can connect on che port
waitCheBoot(self: CheFile) {
if(self.times < 1) {
return;
}
//console.log('wait che on boot', times);
var options = {
hostname: self.che.hostname,
port: 8080,
hostname: self.chefileStruct.server.ip,
port: self.chefileStruct.server.port,
path: '/api/workspace',
method: 'GET',
headers: {
@ -351,26 +421,20 @@ export class CheFile {
'Content-Type': 'application/json;charset=UTF-8'
}
};
if (self.debug) {
console.log('using che ping options', options, 'and docker content', self.dockerContent);
}
Log.getLogger().debug('using che ping options', options, 'and docker content', self.dockerContent);
var req = self.http.request(options, (res) => {
res.on('data', (body) => {
if (res.statusCode === 200 && !self.waitDone) {
self.waitDone = true;
if (self.debug) {
console.log('status code is 200, creating workspace');
}
self.createWorkspace(self.che.hostname, 'local', self.dockerContent);
Log.getLogger().debug('status code is 200, creating workspace');
self.createWorkspace(self.dockerContent);
}
});
});
req.on('error', (e) => {
if (self.debug) {
console.log('with request: ' + e.message);
}
Log.getLogger().debug('with request: ' + e.message);
});
@ -384,4 +448,59 @@ export class CheFile {
}
/**
* Flatten a JSON object and return a map of string with key and value
* @param prefix the prefix to use in order to flatten given object instance
* @param data the JSON object
* @returns {Map<any, any><string, string>} the flatten map
*/
flatJson(prefix, data) : Map<string, string> {
var map = new Map<string, string>();
this.recurseFlatten(map, data, prefix);
return map;
}
/**
* Recursive method to iterate on elements of a JSON object.
* @param map the map containing the key being the property name and the value the JSON element value
* @param jsonData the data to parse
* @param prop the nme of the property being analyzed
*/
recurseFlatten(map : Map<string, string>, jsonData: any, prop: string) : void {
if (Object(jsonData) !== jsonData) {
if (this.isNumber(jsonData)) {
map.set(prop, jsonData);
} else {
map.set(prop, "'" + jsonData + "'");
}
} else if (Array.isArray(jsonData)) {
let arr : Array<any> = jsonData;
let l: number = arr.length;
if (l == 0) {
map.set(prop, '[]');
} else {
for(var i : number =0 ; i<l; i++) {
this.recurseFlatten(map, arr[i], prop + "[" + i + "]");
}
}
} else {
var isEmpty = true;
for (var p in jsonData) {
isEmpty = false;
this.recurseFlatten(map, jsonData[p], prop ? prop + "." + p : p);
}
if (isEmpty && prop)
map.set(prop, '{}');
}
}
isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*/
export class CheFileStruct {
server: CheFileServerStruct;
constructor() {
this.server = new CheFileServerStruct();
}
}
export type CheFileServerTypeStruct = 'local' | 'remote';
export class CheFileServerStruct {
type : CheFileServerTypeStruct;
ip: string;
port: number;
user: string;
pass: string;
startup: Array<String>;
constructor() {
this.startup = new Array<String>();
}
}
export class CheFileStructWorkspaceRuntime {
recipe: string;
}
export class CheFileStructWorkspaceCommand {
name: string;
}
export class CheFileStructWorkspace {
runtime: CheFileStructWorkspaceRuntime;
name: string;
commands : Array<CheFileStructWorkspaceCommand>;
constructor() {
this.commands = new Array<CheFileStructWorkspaceCommand>();
this.runtime = new CheFileStructWorkspaceRuntime();
}
}

View File

@ -12,6 +12,7 @@
/// <reference path='./typings/tsd.d.ts' />
import {PostCheck} from './post-check';
import {CheFile} from "./che-file";
import {Log} from "./log";
/**
* Entry point of this library providing commands.
@ -52,12 +53,12 @@ export class EntryPoint {
try {
let errorMessage = JSON.parse(error);
if (errorMessage.message) {
console.log('Error: ', errorMessage.message);
Log.getLogger().error(errorMessage.message);
} else {
console.log('Error: ', error.toString());
Log.getLogger().error(error.toString());
}
} catch (e) {
console.log('Error: ', error.toString());
Log.getLogger().error(error.toString());
}
process.exit(1);
});
@ -69,8 +70,8 @@ export class EntryPoint {
* Display the help.
*/
printHelp() : void {
console.log('Help: ');
console.log(' valid options are : [che-test|che-file]');
Log.getLogger().info('Help: ');
Log.getLogger().info(' valid options are : [che-test|che-file]');
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) 2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*/
/**
* Logging class allowing to log message
* @author Florent Benoit
*/
export class Log {
static debugEnabled : boolean = false;
static context : string = 'ECLIPSE CHE';
static logger : Log;
BLUE: string = '\u001b[34m';
GREEN : string = '\u001b[32m';
RED : string = '\u001b[31m';
ORANGE: string = '\u001b[33m';
NC : string = '\u001b[39m';
INFO_PREFIX: string = this.GREEN + 'INFO:' + this.NC;
DEBUG_PREFIX: string = this.BLUE + 'DEBUG:' + this.NC;
WARN_PREFIX: string = this.ORANGE + 'WARN:' + this.NC;
ERROR_PREFIX: string = this.RED + 'ERROR:' + this.NC;
static getLogger() : Log {
if (!Log.logger) {
Log.logger = new Log();
}
return Log.logger;
}
info(message : string, ...optional: Array<any>) {
this.log('info', message, optional);
}
warn(message : string, ...optional: Array<any>) {
this.log('warn', message, optional);
}
error(message : string, ...optional: Array<any>) {
this.log('error', message, optional);
}
debug(message : string, ...optional: Array<any>) {
if (Log.debugEnabled) {
this.log('debug', message, optional);
}
}
log(type: LogType, message: string, optional: Array<any>) {
var prefix: String;
if ('info' === type) {
prefix = this.INFO_PREFIX;
} else if ('debug' === type) {
prefix = this.DEBUG_PREFIX;
} else if ('warn' === type) {
prefix = this.WARN_PREFIX;
} else if ('error' === type) {
prefix = this.ERROR_PREFIX;
}
if (Log.context) {
prefix += ' ' + Log.context + ':';
}
if (optional) {
console.log(prefix, message, optional.join(' '));
} else {
console.log(prefix, message);
}
}
}
export type LogType = 'info' | 'debug' | 'warn' | 'error';

View File

@ -11,6 +11,7 @@
import {MessageBuilder} from './messagebuilder';
import {MessageBusSubscriber} from './messagebus-subscriber';
import {Log} from "./log";
/**
@ -36,11 +37,11 @@ export class MessageBus {
this.delaySend = [];
var client = websocketClient.on('connectFailed', function(error) {
console.log('Connect Error: ' + error.toString());
Log.getLogger().error('Connect Error: ' + error.toString());
});
client.on('error', error => console.log('websocketclient error', error.toString()));
client.on('error', error => Log.getLogger().error('websocketclient error', error.toString()));
client.on('connect', (connection) => {
this.websocketConnection = connection;
@ -55,12 +56,12 @@ export class MessageBus {
connection.on('error', (error) => {
if (!this.closed) {
console.log("Connection Error: " + error.toString());
Log.getLogger().error("Connection Error: " + error.toString());
}
});
connection.on('close', () => {
if (!this.closed) {
console.log('Websocket connection closed');
Log.getLogger().error('Websocket connection closed');
}
});
connection.on('message', (message) => {

View File

@ -19,6 +19,7 @@ import {WorkspaceEventMessageBusSubscriber} from './workspace-event-subscriber';
import {MessageBusSubscriber} from './messagebus-subscriber';
import {WorkspaceDisplayOutputMessageBusSubscriber} from './workspace-log-output-subscriber';
import {AuthData} from "./auth-data";
import {Log} from "./log";
/**
@ -53,6 +54,7 @@ export class PostCheck {
run() : Promise<string> {
Log.context = 'ECLIPSE CHE TEST/post-check';
let p = new Promise<any>( (resolve, reject) => {
var securedOrNot:string;
@ -61,7 +63,7 @@ export class PostCheck {
} else {
securedOrNot = '.';
}
console.log('PostCheck:: using hostname \"' + this.authData.getHostname() + '\" and port \"' + this.authData.getPort() + '\"' + securedOrNot);
Log.getLogger().info('using hostname \"' + this.authData.getHostname() + '\" and port \"' + this.authData.getPort() + '\"' + securedOrNot);
// we have a promise for auth, wait it
if (this.promiseAuth) {
@ -126,7 +128,7 @@ export class PostCheck {
var startWorkspacePromise:Promise<WorkspaceDto> = this.workspace.startWorkspace(workspaceDto.getId());
startWorkspacePromise.then(workspaceDto => {
return new Promise<WorkspaceDto>( (resolve, reject) => {
console.log('Workspace is now starting...');
Log.getLogger().info('Workspace is now starting...');
resolve(workspaceDto);
});
}, error => {

View File

@ -10,6 +10,7 @@
*/
import {Log} from "./log";
/**
* Build a default recipe.
* @author Florent Benoit
@ -34,7 +35,7 @@ export class RecipeBuilder {
// use synchronous API
try {
var stats = this.fs.statSync(dockerFilePath);
console.log('Using a custom project Dockerfile \'' + dockerFilePath + '\' for the setup of the workspace.');
Log.getLogger().info('Using a custom project Dockerfile \'' + dockerFilePath + '\' for the setup of the workspace.');
var content = this.fs.readFileSync(dockerFilePath, 'utf8');
return content;
} catch (e) {

View File

@ -13,6 +13,7 @@ import {MessageBusSubscriber} from './messagebus-subscriber';
import {MessageBus} from './messagebus';
import {AuthData} from './auth-data';
import {WorkspaceDto} from './dto/workspacedto';
import {Log} from "./log";
/**
* Logic on events received by the remote workspace. When workspace is going to running state, we close the websocket and display the IDE URL.
@ -39,14 +40,14 @@ export class WorkspaceEventMessageBusSubscriber implements MessageBusSubscriber
ideUrl = link.href;
}
});
console.log('Workspace is now running. Please connect to ' + ideUrl);
Log.getLogger().info('Workspace is now running. Please connect to ' + ideUrl);
this.messageBus.close();
} else if ('ERROR' === message.eventType) {
console.log('Error when starting the workspace', message);
Log.getLogger().error('Error when starting the workspace', message);
this.messageBus.close();
process.exit(1);
} else {
console.log('Event on workspace : ', message.eventType);
Log.getLogger().info('Event on workspace : ', message.eventType);
}
}

View File

@ -10,6 +10,7 @@
*/
import {MessageBusSubscriber} from './messagebus-subscriber';
import {Log} from "./log";
/**
* Class that will display to console all workspace output messages.
@ -19,7 +20,7 @@ export class WorkspaceDisplayOutputMessageBusSubscriber implements MessageBusSub
handleMessage(message: string) {
// maybe parse data to add colors
console.log(message);
Log.getLogger().info(message);
}
}

View File

@ -11,6 +11,7 @@
import {WorkspaceDto} from './dto/workspacedto';
import {AuthData} from "./auth-data";
import {Log} from "./log";
/**
* Workspace class allowing to manage a workspace, like create/start/stop, etc operations
@ -71,7 +72,7 @@ export class Workspace {
});
req.on('error', (err) => {
console.log('rejecting as we got error', err);
Log.getLogger().error('rejecting as we got error', err);
reject('HTTP error: ' + err);
});
@ -125,7 +126,7 @@ export class Workspace {
var req = this.http.request(options, (res) => {
res.on('error', (body)=> {
console.log('got the following error', body.toString());
Log.getLogger().error('got the following error', body.toString());
reject(body);
});