/* * Copyright (c) 2016 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2020-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ var request = require('request-promise-native'); var colors = require('colors/safe'); var Table = require('cli-table'); var consoleLib = require('console') var os = require('os') module.exports = function() { this._ = require('lodash-getpath') // The main entry point into the library. This function is used to do // cluster health checks and to propagate the commands to multiple // servers. this.maxctrl = function(argv, cb) { this.argv = argv if (!argv.hosts || argv.hosts.length < 1) { argv.reject("No hosts defined") } return pingCluster(argv.hosts) .then(function() { var promises = [] var rval = [] argv.hosts.forEach(function(i) { promises.push(cb(i) .then(function(output) { if (argv.hosts.length > 1) { rval.push(colors.yellow(i)) } rval.push(output) })) }) return Promise.all(promises) .then(function() { argv.resolve(argv.quiet ? undefined : rval.join(os.EOL)) }, function(err) { argv.reject(err) }) }, function(err) { // One of the HTTP request pings to the cluster failed, log the error argv.reject(JSON.stringify(err.error, null, 4)) }) } // Request a resource collection and format it as a table this.getCollection = function (host, resource, fields) { return doRequest(host, resource, function(res) { var header = [] fields.forEach(function(i) { header.push(Object.keys(i)) }) var table = getTable(header) res.data.forEach(function(i) { row = [] fields.forEach(function(p) { var v = _.getPath(i, p[Object.keys(p)[0]], '') if (Array.isArray(v)) { v = v.join(', ') } row.push(v) }) table.push(row) }) return table.toString() }) } // Request a part of a resource as a collection this.getSubCollection = function (host, resource, subres, fields) { return doRequest(host, resource, function(res) { var header = [] fields.forEach(function(i) { header.push(Object.keys(i)) }) var table = getTable(header) _.getPath(res.data, subres, []).forEach(function(i) { row = [] fields.forEach(function(p) { var v = _.getPath(i, p[Object.keys(p)[0]], '') if (Array.isArray(v) && typeof(v[0]) != 'object') { v = v.join(', ') } else if (typeof(v) == 'object') { v = JSON.stringify(v, null, 4) } row.push(v) }) table.push(row) }) return table.toString() }) } // Request a single resource and format it as a key-value list this.getResource = function (host, resource, fields) { return doRequest(host, resource, function(res) { var table = getList() fields.forEach(function(i) { var k = Object.keys(i)[0] var path = i[k] var v = _.getPath(res.data, path, '') if (Array.isArray(v) && typeof(v[0]) != 'object') { v = v.join(', ') } else if (typeof(v) == 'object') { v = JSON.stringify(v, null, 4) } var o = {} o[k] = v table.push(o) }) return table.toString() }) } this.updateValue = function(host, resource, key, value) { var body = {} // Convert string booleans into JSON booleans if (value == "true") { value = true } else if (value == "false") { value = false } _.set(body, key, value) return doRequest(host, resource, null, { method: 'PATCH', body: body }) } // Helper for converting endpoints to acutal URLs this.getUri = function(host, secure, endpoint) { var base = 'http://' if (secure) { base = 'https://' } return base + argv.user + ':' + argv.password + '@' + host + '/v1/' + endpoint } this.OK = function() { return Promise.resolve(colors.green('OK')) } // Helper for executing requests and handling their responses, returns a // promise that is fulfilled when all requests successfully complete. The // promise is rejected if any of the requests fails. this.doAsyncRequest = function(host, resource, cb, obj) { args = obj || {} args.uri = getUri(host, this.argv.secure, resource) args.json = true args.timeout = this.argv.timeout return request(args) .then(function(res) { if (res && cb) { // Request OK, returns data return cb(res) } else { // Request OK, no data or data is ignored return OK() } }, function(err) { if (err.response && err.response.body) { return error('Server at '+ err.response.request.uri.host +' responded with status code ' + err.statusCode + ' to `' + err.response.request.method +' ' + resource + '`:' + JSON.stringify(err.response.body, null, 4)) } else if (err.statusCode) { return error('Server at '+ err.response.request.uri.host +' responded with status code ' + err.statusCode + ' to `' + err.response.request.method +' ' + resource + '`') } else if (err.error) { return error(JSON.stringify(err.error, null, 4)) } else { return error('Undefined error: ' + JSON.stringify(err, null, 4)) } }) } this.doRequest = function(host, resource, cb, obj) { return doAsyncRequest(host, resource, cb, obj) } this.error = function(err) { return Promise.reject(colors.red('Error: ') + err) } } var tsvopts = { chars: { 'top': '' , 'top-mid': '' , 'top-left': '' , 'top-right': '' , 'bottom': '' , 'bottom-mid': '' , 'bottom-left': '' , 'bottom-right': '' , 'left': '' , 'left-mid': '' , 'mid': '' , 'mid-mid': '' , 'right': '' , 'right-mid': '' , 'middle': ' ' }, style: { 'padding-left': 0, 'padding-right': 0, compact: true }, } function getList() { var opts = { style: { head: ['cyan'] } } if (this.argv.tsv) { opts = _.assign(opts, tsvopts) } return new Table(opts) } // Creates a table-like array for output. The parameter is an array of header names function getTable(headobj) { for (i = 0; i < headobj.length; i++) { headobj[i] = colors.cyan(headobj[i]) } var opts if (this.argv.tsv) { opts = _.assign(opts, tsvopts) } else { opts = { head: headobj } } return new Table(opts) } function pingCluster(hosts) { var promises = [] hosts.forEach(function(i) { promises.push(request(getUri(i, false, ''))) }) return Promise.all(promises) }