Merge branch 'dev'
This commit is contained in:
commit
ba149efa4a
@ -1,4 +1,4 @@
|
|||||||
# XO CLI
|
# XO-CLI
|
||||||
[](http://travis-ci.org/vatesfr/xo-cli)
|
[](http://travis-ci.org/vatesfr/xo-cli)
|
||||||
[](https://david-dm.org/vatesfr/xo-cli)
|
[](https://david-dm.org/vatesfr/xo-cli)
|
||||||
[](https://david-dm.org/vatesfr/xo-cli#info=devDependencies)
|
[](https://david-dm.org/vatesfr/xo-cli#info=devDependencies)
|
||||||
@ -18,14 +18,43 @@ npm install -g xo-cli
|
|||||||
#### Register your XO instance
|
#### 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.
|
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).
|
||||||
|
@ -4,4 +4,4 @@
|
|||||||
|
|
||||||
//====================================================================
|
//====================================================================
|
||||||
|
|
||||||
require('../')(process.argv.slice(2));
|
require('exec-promise')(require('..'));
|
||||||
|
@ -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);
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,38 +1,44 @@
|
|||||||
{
|
{
|
||||||
"name": "xo-cli",
|
"name": "xo-cli",
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
|
"license": "AGPL3",
|
||||||
"description": "Basic CLI for Xen-Orchestra",
|
"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",
|
"homepage": "https://github.com/vatesfr/xo-cli",
|
||||||
"bugs": {
|
"bugs": "https://github.com/vatesfr/xo-cli/issues",
|
||||||
"url": "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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/vatesfr/xo-cli"
|
"url": "https://github.com/vatesfr/xo-cli"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^1.2.2",
|
"bluebird": "^1.2.4",
|
||||||
"chalk": "^0.4.0",
|
"chalk": "^0.4.0",
|
||||||
"inquirer": "^0.4.1",
|
"exec-promise": "^0.2.4",
|
||||||
|
"inquirer": "^0.5.0",
|
||||||
"l33teral": "^2.0.4",
|
"l33teral": "^2.0.4",
|
||||||
"lodash": "^2.4.1",
|
"lodash": "^2.4.1",
|
||||||
"mkdirp": "^0.3.5",
|
"mkdirp": "^0.5.0",
|
||||||
"nomnom": "^1.6.2",
|
"multiline": "^0.3.4",
|
||||||
"ws": "^0.4.31",
|
"ws": "^0.4.31",
|
||||||
"xdg": "^0.1.1"
|
"xdg": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^1.9.1",
|
"chai": "^1.9.1",
|
||||||
"mocha": "^1.18.2"
|
"mocha": "^1.19.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha cli.spec.js"
|
"test": "mocha cli.spec.js"
|
||||||
},
|
}
|
||||||
"main": "cli.js",
|
|
||||||
"directories": {
|
|
||||||
"bin": "./bin"
|
|
||||||
},
|
|
||||||
"preferGlobal": true,
|
|
||||||
"license": "AGPL3"
|
|
||||||
}
|
}
|
||||||
|
173
packages/xo-cli/src/cli.js
Normal file
173
packages/xo-cli/src/cli.js
Normal 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);
|
||||||
|
});
|
||||||
|
};
|
@ -46,3 +46,13 @@ exports.set = function (data) {
|
|||||||
return save(_.extend(config, 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);
|
||||||
|
});
|
||||||
|
};
|
@ -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) {
|
var Xo = function (url) {
|
||||||
this._url = url;
|
this._url = url;
|
||||||
|
|
||||||
@ -102,9 +108,7 @@ _.extend(Xo.prototype, {
|
|||||||
|
|
||||||
socket.on('close', function () {
|
socket.on('close', function () {
|
||||||
// Closes accesses.
|
// Closes accesses.
|
||||||
this.send = function () {
|
this.send = notConnected;
|
||||||
throw new Error('not connected');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fails all waiting requests.
|
// Fails all waiting requests.
|
||||||
_.each(this._deferreds, function (deferred) {
|
_.each(this._deferreds, function (deferred) {
|
||||||
@ -121,7 +125,7 @@ _.extend(Xo.prototype, {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
send: function (method, params) {
|
call: function (method, params) {
|
||||||
return this.connect().then(function () {
|
return this.connect().then(function () {
|
||||||
var socket = this._socket;
|
var socket = this._socket;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user