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)
|
||||
[](https://david-dm.org/vatesfr/xo-cli)
|
||||
[](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).
|
||||
|
@ -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",
|
||||
"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
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));
|
||||
});
|
||||
};
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user