First working version.

This commit is contained in:
Julien Fontanet 2014-03-25 15:33:16 +01:00
parent b526067eeb
commit d9e615e696
6 changed files with 254 additions and 168 deletions

View File

@ -10,11 +10,22 @@
#### [npm](https://npmjs.org/package/xo-cli)
```
npm install -f xo-cli
npm install -g xo-cli
```
## Usage
#### Register your XO instance
```
xo-cli add-server
xo-cli register --host xo.my-company.net --email admin@admin.net --password admin
```
Note: only a token will be saved in the configuration file.
#### Adds a new Xen server
```
xo-cli add-server --host xen1.my-company.net --user root --password secure%password
```

View File

@ -8,14 +8,171 @@ var Promise = require('bluebird');
//--------------------------------------------------------------------
var config = require('./config');
var prompt = require('./prompt');
var Xo = require('./xo');
//====================================================================
// Handles a promise in a CLI environment.
var handlePromise = function (promise) {
promise.then(function (value) {
var connect = function () {
return config.load().bind({}).then(function (config) {
if (!config.server)
{
throw 'no server to connect to!';
}
if (!config.token)
{
throw 'no token available';
}
this.xo = new Xo(config.server);
return this.xo.send('session.signInWithToken', {
token: config.token,
});
}).then(function () {
return this.xo;
}).bind();
};
//====================================================================
module.exports = function (argv) {
var command;
var commandName;
var commandOpts;
var registerCommand = function (name, def, fn) {
var cmdParser = nomnom.command(name);
if (def.description)
{
cmdParser.help(def.description);
}
if (def.args)
{
var interactive = process.stdout.isTTY;
_.each(def.args, function (def, name) {
cmdParser.option(name, {
abbr: def.abbr,
flag: (def.type === 'boolean'),
help: def.description,
// Do not mark options as required if the program is run in
// interactive mode, they will be asked.
required: !interactive && def.required,
});
});
// TODO: alters `fn` to prompt for each missing argument.
fn = (function (fn, args) {
return function (opts) {
var prompts = [];
_.each(args, function (def, name) {
if (!(name in opts))
{
prompts.push({
name: name,
message: def.prompt || def.description,
type: def.promptType || 'input',
});
}
});
if (prompts.length)
{
return prompt(prompts).then(function (answers) {
return fn(_.extend(opts, answers));
});
}
return fn(opts);
};
})(fn, def.args);
}
cmdParser.callback(function (opts) {
command = fn;
commandName = name;
commandOpts = opts;
});
};
registerCommand('register', {
description: 'registers the XO instance',
args: {
host: {
description: 'host/ip optionally followed by `:port` if not 80',
required: true,
},
email: {
description: 'email to use to connect',
required: true,
},
password: {
description: 'password to use to connect',
promptType: 'password',
required: true,
},
}
}, function (opts) {
return Promise.bind({opts: opts || {}}).then(function () {
this.xo = new Xo(opts.host);
return this.xo.send('session.signInWithPassword', {
email: this.opts.email,
password: this.opts.password,
});
}).then(function (user) {
console.log('Successfully logged with', user.email);
return this.xo.send('token.create');
}).then(function (token) {
console.log('Token created:', token);
return config.set({
server: this.opts.host,
token: token,
});
}).bind();
});
registerCommand('add-server', {
description: 'adds a new Xen server',
args: {
host: {
description: 'host of the server',
required: true,
},
username: {
description: 'username to use to connect',
required: true,
},
password: {
description: 'password to use to connect',
promptType: 'password',
required: true,
},
},
}, function (opts) {
return connect().then(function (connection) {
return connection.send('server.add', {
host: opts.host,
username: opts.username,
password: opts.password,
});
}).return('ok');
});
// TODO: handle global `--config FILE` option.
nomnom.parse(argv);
// Executes the selected command.
Promise.try(command, [commandOpts]).then(function (value) {
if (_.isString(value))
{
console.log(value);
@ -23,165 +180,20 @@ var handlePromise = function (promise) {
process.exit(0);
}).catch(function (error) {
if (_.isString(error))
if (error === undefined)
{
console.error(error);
// Nothing to do.
undefined;
}
else if (_.isNumber(error))
{
process.exit(error);
}
else
{
console.error(error.stack || error);
}
process.exit(1);
});
};
//--------------------------------------------------------------------
var connect = function (opts) {
var config, xo;
return Promise.try(function () {
opts || (opts = {});
// TODO: reads the configuration file.
config = {
server: 'ws://localhost:9000/api/',
};
if (!config.server)
{
throw 'no server to connect to!';
}
xo = new Xo(config.server);
if (config.token && !opts.noAutoSignIn)
{
return xo.send('session.signInWithToken', {
token: config.token,
});
}
});
};
//--------------------------------------------------------------------
var addServer = function (host, username, password) {
return connect().then(function (connection) {
return connection.send('server.add', {
host: host,
username: username,
password: password,
});
}).return('ok');
};
var register = function (url, email, password) {
var xo;
return Promise.try(function () {
xo = new Xo(url);
return xo.send('session.signInWithPassword', {
email: email,
password: password,
});
}).then(function (user) {
console.log('Successfully logged with', user.email);
return xo.send('token.create');
}).then(function (token) {
return getConfig().set({
server: url,
token: token,
});
});
};
//====================================================================
module.exports = function (argv) {
nomnom.command('register')
.help('signs in XO using email/password')
.options({
url: {
help: 'URL of the API endpoint',
},
email: {
help: 'email to use to connect',
},
password: {
help: 'password to use to connect',
},
})
.callback(function (opts) {
Promise.try(function () {
if (opts.url)
{
return prompt.input('URL').then(function (url) {
opts.url = url;
});
}
}).then(function () {
if (!opts.email)
{
return prompt.input('Email').then(function (email) {
opts.email = email;
});
}
}).then(function () {
if (!opts.password)
{
return prompt.password('Password').then(function (password) {
opts.password = password;
});
}
}).then(function () {
return register(opts.url, opts.email, opts.password);
});
})
;
nomnom.command('add-server')
.help('adds a new server')
.options({
host: {
help: 'hostname or ip of the server',
},
username: {
help: 'username to use to connect',
},
password: {
help: 'password to use to connect',
},
})
.callback(function (opts) {
Promise.try(function () {
if (opts.host)
{
return prompt.input('Hostname or ip').then(function (host) {
opts.host = host;
});
}
}).then(function () {
if (!opts.username)
{
return prompt.input('Username').then(function (username) {
opts.username = username;
});
}
}).then(function () {
if (!opts.password)
{
return prompt.password('Password').then(function (password) {
opts.password = password;
});
}
}).then(function () {
return addServer(opts.host, opts.username, opts.password);
});
})
;
nomnom.parse(argv);
};

View File

@ -2,10 +2,47 @@
//====================================================================
var xdg = require('xdg').basedir;
var fs = require('fs');
//--------------------------------------------------------------------
var _ = require('lodash');
var l33t = require('l33teral');
var mkdirp = require('mkdirp');
var Promise = require('bluebird');
var xdg = require('xdg');
//====================================================================
var Config = function () {
this.data =
var configPath = xdg.basedir.configPath('xo-cli');
var configFile = configPath +'/config.json';
var mkdirp = Promise.promisify(mkdirp);
var readFile = Promise.promisify(fs.readFile);
var writeFile = Promise.promisify(fs.writeFile);
//====================================================================
var load = exports.load = function () {
return readFile(configFile).then(JSON.parse).catch(function () {
return {};
});
};
exports.get = function (path) {
return load().then(function (config) {
return l33t(config).tap(path);
});
};
var save = exports.save = function (config) {
return mkdirp(configPath).then(function () {
return writeFile(configFile, JSON.stringify(config));
});
};
exports.set = function (data) {
return load().then(function (config) {
return save(_.extend(config, data));
});
};

View File

@ -13,7 +13,9 @@
"bluebird": "^1.1.0",
"chalk": "^0.4.0",
"inquirer": "^0.4.1",
"l33teral": "^2.0.3",
"lodash": "^2.4.1",
"mkdirp": "^0.3.5",
"nomnom": "^1.6.2",
"ws": "^0.4.31",
"xdg": "^0.1.1"
@ -30,5 +32,6 @@
"bin": {
"xo-cli": "bin/xo-cli"
},
"preferGlobal": true,
"license": "AGPL3"
}

View File

@ -15,7 +15,7 @@ var prompts = module.exports = function (prompts) {
return deferred.promise;
};
exports.input = function (message) {
prompts.input = function (message) {
return prompts({
type: 'input',
name: 'question',
@ -23,7 +23,7 @@ exports.input = function (message) {
}).get('question');
};
exports.password = function (message) {
prompts.password = function (message) {
return prompts({
type: 'password',
name: 'question',

View File

@ -8,7 +8,9 @@ var Promise = require('bluebird');
// Supports browsers.
// FIXME: wraps in an anonymous function.
var WebSocket = 'WebSocket' in window ? window.WebSocket : require('ws');
// jshint ignore: start
var WebSocket = (this && 'WebSocket' in this) ? this.WebSocket : require('ws');
// jshint ignore: end
//====================================================================
@ -32,6 +34,13 @@ var Xo = function (url) {
};
_.extend(Xo.prototype, {
close: function () {
if (this._socket)
{
this._socket.close();
}
},
connect: function () {
if (this.status === 'connected')
{
@ -48,13 +57,22 @@ _.extend(Xo.prototype, {
socket.on('open', function () {
this.status = 'connected';
// Reopens accesses.
// (Re)Opens accesses.
delete this.send;
// Resolves the promise.
deferred.resolve();
}.bind(this));
socket.on('message', function (event) {
socket.on('message', function (data) {
// `ws` API is lightly different from standard API.
if (data.data)
{
data = data.data;
}
// TODO: Wraps in a promise to prevent releasing the Zalgo.
var response = JSON.parse(event.data);
var response = JSON.parse(data);
var id = response.id;
@ -80,7 +98,7 @@ _.extend(Xo.prototype, {
message: 'invalid response received',
object: response,
});
});
}.bind(this));
socket.on('close', function () {
// Closes accesses.
@ -95,6 +113,11 @@ _.extend(Xo.prototype, {
this._deferreds = {};
}.bind(this));
socket.on('error', function (error) {
// Fails the connect promise if possible.
deferred.reject(error);
});
return deferred.promise;
},
@ -114,7 +137,7 @@ _.extend(Xo.prototype, {
var deferred = this._deferreds[id] = Promise.defer();
return deferred.promise;
});
}.bind(this));
},
});