From 29398b98697192296c94a16f3658af43c1f8fe1c Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 3 Apr 2014 10:19:31 +0200 Subject: [PATCH 1/7] Various updates. --- packages/xo-cli/package.json | 26 +++++++++++++++---------- packages/xo-cli/{ => src}/cli.js | 30 +++++++++++++++++++++++++++-- packages/xo-cli/{ => src}/config.js | 0 packages/xo-cli/{ => src}/prompt.js | 0 packages/xo-cli/{ => src}/xo.js | 0 5 files changed, 44 insertions(+), 12 deletions(-) rename packages/xo-cli/{ => src}/cli.js (87%) rename packages/xo-cli/{ => src}/config.js (100%) rename packages/xo-cli/{ => src}/prompt.js (100%) rename packages/xo-cli/{ => src}/xo.js (100%) diff --git a/packages/xo-cli/package.json b/packages/xo-cli/package.json index 5bfc92e04..7c2f8a9db 100644 --- a/packages/xo-cli/package.json +++ b/packages/xo-cli/package.json @@ -1,11 +1,22 @@ { "name": "xo-cli", "version": "0.1.1", + "license": "AGPL3", "description": "Basic CLI for Xen-Orchestra", - "author": "Julien Fontanet ", + "keywords": [ + "xo", + "xen-orchestra", + "xen", + "orchestra" + ], "homepage": "https://github.com/vatesfr/xo-cli", - "bugs": { - "url": "https://github.com/vatesfr/xo-cli" + "bugs": "https://github.com/vatesfr/xo-cli/issues", + "author": "Julien Fontanet ", + "preferGlobal": true, + "main": "./src/cli.js", + "directories": { + "bin": "./bin", + "man": "./man" }, "repository": { "type": "git", @@ -24,16 +35,11 @@ }, "devDependencies": { "chai": "^1.9.0", + "human-format": "^0.1.0", "mocha": "^1.18.0", "mocha-promise": "0.0.1" }, "scripts": { "test": "mocha cli.spec.js" - }, - "main": "cli.js", - "bin": { - "xo-cli": "bin/xo-cli" - }, - "preferGlobal": true, - "license": "AGPL3" + } } diff --git a/packages/xo-cli/cli.js b/packages/xo-cli/src/cli.js similarity index 87% rename from packages/xo-cli/cli.js rename to packages/xo-cli/src/cli.js index 502e1d945..192c707e4 100644 --- a/packages/xo-cli/cli.js +++ b/packages/xo-cli/src/cli.js @@ -76,7 +76,7 @@ module.exports = function (argv) { { prompts.push({ name: name, - message: def.prompt || def.description, + message: def.prompt || def.description || name, type: def.promptType || 'input', }); } @@ -167,6 +167,23 @@ module.exports = function (argv) { }).bind(); }); + registerCommand('list-commands', {}, function () { + return connect().then(function (xo) { + return xo.send('system.listMethods').then(JSON.stringify); + }); + }); + registerCommand('show-command', { + args: { + name: { + required: true, + }, + }, + }, function (opts) { + return connect().then(function (xo) { + return xo.send('system.methodSignature', {name: opts.name}).then(console.log); + }); + }); + registerCommand('whoami', { description: 'displays information about the current user', }, function () { @@ -186,7 +203,16 @@ module.exports = function (argv) { }); // TODO: handle global `--config FILE` option. - nomnom.parse(argv); + nomnom + .option('version', { + flag: true, + help: 'prints the current version of xo-cli', + callback: function () { + return 'xo-cli version '+ require('../package').version; + }, + }) + .parse(argv) + ; // Executes the selected command. Promise.try(command, [commandOpts]).then(function (value) { diff --git a/packages/xo-cli/config.js b/packages/xo-cli/src/config.js similarity index 100% rename from packages/xo-cli/config.js rename to packages/xo-cli/src/config.js diff --git a/packages/xo-cli/prompt.js b/packages/xo-cli/src/prompt.js similarity index 100% rename from packages/xo-cli/prompt.js rename to packages/xo-cli/src/prompt.js diff --git a/packages/xo-cli/xo.js b/packages/xo-cli/src/xo.js similarity index 100% rename from packages/xo-cli/xo.js rename to packages/xo-cli/src/xo.js From 84e6228f9020bc098402ea1967d1ca5e70d7bf8b Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Sat, 24 May 2014 17:09:10 +0200 Subject: [PATCH 2/7] Various updates. --- packages/xo-cli/bin/xo-cli | 2 +- packages/xo-cli/package.json | 15 ++++++----- packages/xo-cli/src/cli.js | 50 +++++++++++++++--------------------- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/packages/xo-cli/bin/xo-cli b/packages/xo-cli/bin/xo-cli index 77127e56d..bfe13c7d4 100755 --- a/packages/xo-cli/bin/xo-cli +++ b/packages/xo-cli/bin/xo-cli @@ -4,4 +4,4 @@ //==================================================================== -require('../')(process.argv.slice(2)); +require('exec-promise')(require('..')); diff --git a/packages/xo-cli/package.json b/packages/xo-cli/package.json index 7c2f8a9db..1a571b498 100644 --- a/packages/xo-cli/package.json +++ b/packages/xo-cli/package.json @@ -23,20 +23,21 @@ "url": "https://github.com/vatesfr/xo-cli" }, "dependencies": { - "bluebird": "^1.1.0", + "bluebird": "^1.2.4", "chalk": "^0.4.0", - "inquirer": "^0.4.1", - "l33teral": "^2.0.3", + "exec-promise": "^0.2.4", + "inquirer": "^0.5.0", + "l33teral": "^2.0.4", "lodash": "^2.4.1", - "mkdirp": "^0.3.5", + "mkdirp": "^0.5.0", "nomnom": "^1.6.2", "ws": "^0.4.31", "xdg": "^0.1.1" }, "devDependencies": { - "chai": "^1.9.0", - "human-format": "^0.1.0", - "mocha": "^1.18.0", + "chai": "^1.9.1", + "human-format": "^0.1.3", + "mocha": "^1.19.0", "mocha-promise": "0.0.1" }, "scripts": { diff --git a/packages/xo-cli/src/cli.js b/packages/xo-cli/src/cli.js index 192c707e4..d2992b6a1 100644 --- a/packages/xo-cli/src/cli.js +++ b/packages/xo-cli/src/cli.js @@ -3,7 +3,7 @@ //==================================================================== var _ = require('lodash'); -var nomnom = require('nomnom'); +var nomnom = require('nomnom')(); var Promise = require('bluebird'); //-------------------------------------------------------------------- @@ -14,6 +14,15 @@ var Xo = require('./xo'); //==================================================================== +//`nomnom.print()` version which does not use `process.exit()`. +nomnom.print = function (str, code) { + throw ''+ str; + + // TODO: handles code. +}; + +//==================================================================== + var connect = function () { return config.load().bind({}).then(function (config) { if (!config.server) @@ -180,7 +189,9 @@ module.exports = function (argv) { }, }, function (opts) { return connect().then(function (xo) { - return xo.send('system.methodSignature', {name: opts.name}).then(console.log); + return xo.send('system.methodSignature', { + name: opts.name + }).then(console.log); }); }); @@ -203,40 +214,19 @@ module.exports = function (argv) { }); // TODO: handle global `--config FILE` option. - nomnom + var opts = nomnom .option('version', { flag: true, help: 'prints the current version of xo-cli', - callback: function () { - return 'xo-cli version '+ require('../package').version; - }, }) .parse(argv) ; + if (opts.version) + { + return 'xo-cli version '+ require('../package').version; + } + // 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); - }); + return command(commandOpts); }; From 061d8dc94f00d92625570f389fe58cf2dd17f94e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 26 May 2014 13:33:56 +0200 Subject: [PATCH 3/7] Use API introspection. --- packages/xo-cli/package.json | 2 +- packages/xo-cli/src/cli.js | 300 +++++++++++++---------------------- packages/xo-cli/src/xo.js | 12 +- 3 files changed, 118 insertions(+), 196 deletions(-) diff --git a/packages/xo-cli/package.json b/packages/xo-cli/package.json index 1a571b498..9be374ea3 100644 --- a/packages/xo-cli/package.json +++ b/packages/xo-cli/package.json @@ -30,7 +30,7 @@ "l33teral": "^2.0.4", "lodash": "^2.4.1", "mkdirp": "^0.5.0", - "nomnom": "^1.6.2", + "multiline": "^0.3.4", "ws": "^0.4.31", "xdg": "^0.1.1" }, diff --git a/packages/xo-cli/src/cli.js b/packages/xo-cli/src/cli.js index d2992b6a1..8f92fdc2e 100644 --- a/packages/xo-cli/src/cli.js +++ b/packages/xo-cli/src/cli.js @@ -3,8 +3,9 @@ //==================================================================== var _ = require('lodash'); -var nomnom = require('nomnom')(); var Promise = require('bluebird'); +var multiline = require('multiline'); +var chalk = require('chalk'); //-------------------------------------------------------------------- @@ -14,15 +15,6 @@ var Xo = require('./xo'); //==================================================================== -//`nomnom.print()` version which does not use `process.exit()`. -nomnom.print = function (str, code) { - throw ''+ str; - - // TODO: handles code. -}; - -//==================================================================== - var connect = function () { return config.load().bind({}).then(function (config) { if (!config.server) @@ -37,7 +29,7 @@ var connect = function () { this.xo = new Xo(config.server); - return this.xo.send('session.signInWithToken', { + return this.xo.call('session.signInWithToken', { token: config.token, }); }).then(function () { @@ -45,188 +37,114 @@ var connect = function () { }).bind(); }; +var wrap = function (val) { + return function () { + return val; + }; +}; + //==================================================================== -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 || name, - 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('list-commands', {}, function () { - return connect().then(function (xo) { - return xo.send('system.listMethods').then(JSON.stringify); - }); - }); - registerCommand('show-command', { - args: { - name: { - required: true, - }, - }, - }, function (opts) { - return connect().then(function (xo) { - return xo.send('system.methodSignature', { - name: opts.name - }).then(console.log); - }); - }); - - 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. - var opts = nomnom - .option('version', { - flag: true, - help: 'prints the current version of xo-cli', - }) - .parse(argv) - ; - - if (opts.version) - { - return 'xo-cli version '+ require('../package').version; +exports = module.exports = function (args) { + if (!args || !args.length) { + return help(); } - // Executes the selected command. - return command(commandOpts); + 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 [] [] [] + Registers the XO instance to use. + + xo-cli --list-commands [--json] + Returns the list of available commands on the current XO instance. + + xo-cli [=]... + Executes a command on the current XO instance. +*/})); + +exports.version = wrap('xo-cli v'+ require('../package').version); + +exports.register = function (args) { +}; + +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); + }); }; diff --git a/packages/xo-cli/src/xo.js b/packages/xo-cli/src/xo.js index 290bdd3ab..678a68689 100644 --- a/packages/xo-cli/src/xo.js +++ b/packages/xo-cli/src/xo.js @@ -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; From e7620025600c42b29f9777a00e715dcf2de0fae3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 26 May 2014 13:42:36 +0200 Subject: [PATCH 4/7] Remove unused deps. --- packages/xo-cli/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/xo-cli/package.json b/packages/xo-cli/package.json index 9be374ea3..307808081 100644 --- a/packages/xo-cli/package.json +++ b/packages/xo-cli/package.json @@ -36,9 +36,7 @@ }, "devDependencies": { "chai": "^1.9.1", - "human-format": "^0.1.3", - "mocha": "^1.19.0", - "mocha-promise": "0.0.1" + "mocha": "^1.19.0" }, "scripts": { "test": "mocha cli.spec.js" From 3981c772a26902133557b952c8abff72dc6fc8e4 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 26 May 2014 13:57:18 +0200 Subject: [PATCH 5/7] --register and --unregister. --- packages/xo-cli/src/cli.js | 25 +++++++++++++++++++++++++ packages/xo-cli/src/config.js | 10 ++++++++++ 2 files changed, 35 insertions(+) diff --git a/packages/xo-cli/src/cli.js b/packages/xo-cli/src/cli.js index 8f92fdc2e..0ceab90a6 100644 --- a/packages/xo-cli/src/cli.js +++ b/packages/xo-cli/src/cli.js @@ -83,8 +83,33 @@ var help = exports.help = wrap(multiline.stripIndent(function () {/* 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'); diff --git a/packages/xo-cli/src/config.js b/packages/xo-cli/src/config.js index 2bba89430..47129eade 100644 --- a/packages/xo-cli/src/config.js +++ b/packages/xo-cli/src/config.js @@ -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); + }); +}; From 97ad0483ecbbb1f363636f2823c5b659f74b1bb3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 26 May 2014 14:01:55 +0200 Subject: [PATCH 6/7] Minor change. --- packages/xo-cli/src/cli.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/xo-cli/src/cli.js b/packages/xo-cli/src/cli.js index 0ceab90a6..397939ed4 100644 --- a/packages/xo-cli/src/cli.js +++ b/packages/xo-cli/src/cli.js @@ -27,14 +27,12 @@ var connect = function () { throw 'no token available'; } - this.xo = new Xo(config.server); + var xo = new Xo(config.server); - return this.xo.call('session.signInWithToken', { + return xo.call('session.signInWithToken', { token: config.token, - }); - }).then(function () { - return this.xo; - }).bind(); + }).return(xo); + }); }; var wrap = function (val) { From e30a7b3849a4bb1aa478b89d624502d797d532e5 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 26 May 2014 14:22:08 +0200 Subject: [PATCH 7/7] README update. --- packages/xo-cli/README.md | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/xo-cli/README.md b/packages/xo-cli/README.md index ac3ecdf9a..68b1059f3 100644 --- a/packages/xo-cli/README.md +++ b/packages/xo-cli/README.md @@ -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 =...` + +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).