Merge branch 'dev'

This commit is contained in:
Julien Fontanet 2014-05-26 14:26:41 +02:00
commit ba149efa4a
8 changed files with 247 additions and 241 deletions

View File

@ -1,4 +1,4 @@
# XO CLI
# XO-CLI
[![Build Status](https://img.shields.io/travis/vatesfr/xo-cli/master.svg)](http://travis-ci.org/vatesfr/xo-cli)
[![Dependency Status](https://david-dm.org/vatesfr/xo-cli/status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-cli)
[![devDependency Status](https://david-dm.org/vatesfr/xo-cli/dev-status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-cli#info=devDependencies)
@ -18,14 +18,43 @@ npm install -g xo-cli
#### Register your XO instance
```
xo-cli register --host http://xo.my-company.net/api/ --email admin@admin.net --password admin
xo-cli --register http://xo.my-company.net/api/admin@admin.net admin
```
Note: only a token will be saved in the configuration file.
#### Adds a new Xen server
#### List available commands
```
xo-cli add-server --host xen1.my-company.net --user root --password secure%password
xo-cli --list-commands
```
#### Execute a command
The same syntax is used for all commands: `xo-cli <command> <param
name>=<value>...`
E.g., adding a new server:
```
xo-cli server.add my.server.net root secret-password
42
```
The return value is the identifier of this new server in XO.
## Contributing
Contributions are *very* welcome, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xo-cli/issues)
you've encountered;
- fork and create a pull request.
## License
XO-CLI is released under the [AGPL
v3](http://www.gnu.org/licenses/agpl-3.0-standalone.html).

View File

@ -4,4 +4,4 @@
//====================================================================
require('../')(process.argv.slice(2));
require('exec-promise')(require('..'));

View File

@ -1,216 +0,0 @@
'use strict';
//====================================================================
var _ = require('lodash');
var nomnom = require('nomnom');
var Promise = require('bluebird');
//--------------------------------------------------------------------
var config = require('./config');
var prompt = require('./prompt');
var Xo = require('./xo');
//====================================================================
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.stdin.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,
});
});
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('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');
});
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('whoami', {
description: 'displays information about the current user',
}, function () {
return connect().then(function (xo) {
return xo.send('session.getUser');
}).then(function (user) {
if (user)
{
console.log('You are signed in as', user.email);
console.log('Your global permission is', user.permission);
}
else
{
console.log('Your are not signed in.');
}
});
});
// 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);
}
process.exit(0);
}).catch(function (error) {
if (error === undefined)
{
// Nothing to do.
undefined;
}
else if (_.isNumber(error))
{
process.exit(error);
}
else
{
console.error(error.stack || error);
}
process.exit(1);
});
};

View File

@ -1,38 +1,44 @@
{
"name": "xo-cli",
"version": "0.1.1",
"license": "AGPL3",
"description": "Basic CLI for Xen-Orchestra",
"author": "Julien Fontanet <julien.fontanet@vates.fr>",
"keywords": [
"xo",
"xen-orchestra",
"xen",
"orchestra"
],
"homepage": "https://github.com/vatesfr/xo-cli",
"bugs": {
"url": "https://github.com/vatesfr/xo-cli/issues"
"bugs": "https://github.com/vatesfr/xo-cli/issues",
"author": "Julien Fontanet <julien.fontanet@vates.fr>",
"preferGlobal": true,
"main": "./src/cli.js",
"directories": {
"bin": "./bin",
"man": "./man"
},
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xo-cli"
},
"dependencies": {
"bluebird": "^1.2.2",
"bluebird": "^1.2.4",
"chalk": "^0.4.0",
"inquirer": "^0.4.1",
"exec-promise": "^0.2.4",
"inquirer": "^0.5.0",
"l33teral": "^2.0.4",
"lodash": "^2.4.1",
"mkdirp": "^0.3.5",
"nomnom": "^1.6.2",
"mkdirp": "^0.5.0",
"multiline": "^0.3.4",
"ws": "^0.4.31",
"xdg": "^0.1.1"
},
"devDependencies": {
"chai": "^1.9.1",
"mocha": "^1.18.2"
"mocha": "^1.19.0"
},
"scripts": {
"test": "mocha cli.spec.js"
},
"main": "cli.js",
"directories": {
"bin": "./bin"
},
"preferGlobal": true,
"license": "AGPL3"
}
}

173
packages/xo-cli/src/cli.js Normal file
View File

@ -0,0 +1,173 @@
'use strict';
//====================================================================
var _ = require('lodash');
var Promise = require('bluebird');
var multiline = require('multiline');
var chalk = require('chalk');
//--------------------------------------------------------------------
var config = require('./config');
var prompt = require('./prompt');
var Xo = require('./xo');
//====================================================================
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';
}
var xo = new Xo(config.server);
return xo.call('session.signInWithToken', {
token: config.token,
}).return(xo);
});
};
var wrap = function (val) {
return function () {
return val;
};
};
//====================================================================
exports = module.exports = function (args) {
if (!args || !args.length) {
return help();
}
var fnName = args[0].replace(/^--|-\w/g, function (match) {
if (match === '--')
{
return '';
}
return match[1].toUpperCase();
});
if (fnName in exports) {
return exports[fnName](args.slice(1));
}
return exports.call(args);
};
//--------------------------------------------------------------------
var help = exports.help = wrap(multiline.stripIndent(function () {/*
Usage:
xo-cli --register [<XO-Server URL>] [<username>] [<password>]
Registers the XO instance to use.
xo-cli --list-commands [--json]
Returns the list of available commands on the current XO instance.
xo-cli <command> [<name>=<value>]...
Executes a command on the current XO instance.
*/}));
exports.version = wrap('xo-cli v'+ require('../package').version);
exports.register = function (args) {
var xo;
return Promise.try(function () {
xo = new Xo(args[0]);
return xo.call('session.signInWithPassword', {
email: args[1],
password: args[2],
});
}).then(function (user) {
console.log('Successfully logged with', user.email);
return xo.call('token.create');
}).then(function (token) {
return config.set({
server: args[0],
token: token,
});
});
};
exports.unregister = function () {
return config.unset([
'server',
'token',
]);
}
exports.listCommands = function (args) {
return connect().then(function (xo) {
return xo.call('system.getMethodsInfo');
}).then(function (methods) {
if (args.indexOf('--json') !== -1)
{
return methods;
}
methods = _.pairs(methods);
methods.sort(function (a, b) {
a = a[0];
b = b[0];
if (a < b) {
return -1;
}
return +(a > b);
});
var str = [];
methods.forEach(function (method) {
var name = method[0];
var info = method[1];
str.push(chalk.bold.blue(name));
_.each(info.params || [], function (info, name) {
str.push(' ');
if (info.optional) {
str.push('[');
}
str.push(name, '=<', info.type || 'unknown', '>');
if (info.optional) {
str.push(']');
}
});
str.push('\n');
if (info.description) {
str.push(' ', info.description, '\n');
}
});
return str.join('');
});
};
var PARAM_RE = /^([^=]+)=(.*)$/;
exports.call = function (args) {
if (!args.length) {
throw 'missing command name';
}
var method = args.shift();
var params = {};
args.forEach(function (arg) {
var matches;
if (!(matches = arg.match(PARAM_RE))) {
throw 'invalid arg: '+arg;
}
params[matches[1]] = matches[2];
});
return connect().then(function (xo) {
return xo.call(method, params);
});
};

View File

@ -46,3 +46,13 @@ exports.set = function (data) {
return save(_.extend(config, data));
});
};
exports.unset = function (paths) {
return load().then(function (config) {
var l33tConfig = l33t(config);
[].concat(paths).forEach(function (path) {
l33tConfig.purge(path, true);
});
return save(config);
});
};

View File

@ -14,6 +14,12 @@ var WebSocket = (this && 'WebSocket' in this) ? this.WebSocket : require('ws');
//====================================================================
var notConnected = function () {
throw new Error('not connected');
};
//====================================================================
var Xo = function (url) {
this._url = url;
@ -102,9 +108,7 @@ _.extend(Xo.prototype, {
socket.on('close', function () {
// Closes accesses.
this.send = function () {
throw new Error('not connected');
};
this.send = notConnected;
// Fails all waiting requests.
_.each(this._deferreds, function (deferred) {
@ -121,7 +125,7 @@ _.extend(Xo.prototype, {
return deferred.promise;
},
send: function (method, params) {
call: function (method, params) {
return this.connect().then(function () {
var socket = this._socket;