UNPKG

fh-fhc

Version:

A Command Line Interface for FeedHenry

870 lines (751 loc) 25.3 kB
/* globals i18n */ var util = require('util'); var log = require("./utils/log"); var fhc = require("./fhc"); var fhreq = require("./utils/request"); var Table = require('cli-table'); var ini = require('./utils/ini'); var moment = require("moment"); var _ = require("underscore"); const DT_FORMAT = 'YYYY-MM-DD h:mm:ss:SSS'; // TODO split into smaller modules with specific role. // Common API call to FeedHenry exports.doApiCall = function(host, uri, payload, errorMsg, cb) { fhreq.POST(host, uri, payload, function(err, remoteData, raw) { if (err) { return cb(err); } if (!remoteData) { return cb(i18n._("No remoteData returned. Raw response is: ") + util.inspect(raw)); } if (remoteData.msg !== undefined && remoteData.address !== undefined) { if (remoteData.address["login-status"] === 'none') { return cb(i18n._("Error, check your login details.. \n") + remoteData.msg); } } if (remoteData.status !== undefined && remoteData.status !== 'ok') { var msg = remoteData.message || remoteData.msg || util.inspect(remoteData); return cb(errorMsg + msg, remoteData); } return cb(undefined, remoteData); }); }; exports.doApiCallWithAuth = function(host, uri, payload, auth, errorMsg, cb) { fhreq.POSTAUTH(host, uri, payload, auth, function(err, remoteData, raw) { if (err) { return cb(err); } if (!remoteData) { return cb(i18n._("No remoteData returned. Raw response is: ") + util.inspect(raw)); } if (remoteData.msg !== undefined && remoteData.address !== undefined) { if (remoteData.address["login-status"] === 'none') { return cb(i18n._("Error, check your login details.. \n") + remoteData.msg); } } if (remoteData.status !== undefined && remoteData.status !== 'ok') { var msg = remoteData.message || remoteData.msg || util.inspect(remoteData); return cb(errorMsg + msg, remoteData); } return cb(undefined, remoteData); }); }; exports.doPutApiCall = function(host, uri, payload, cb) { fhreq.PUT(host, uri, payload, function(err, remoteData, raw, response) { if (err) { return cb(err); } if (!remoteData) { return cb(i18n._("No remoteData returned. Raw response is: ") + util.inspect(raw)); } if (remoteData.msg !== undefined && remoteData.address !== undefined) { if (remoteData.address["login-status"] === 'none') { return cb(i18n._("Error, check your login details.. \n") + remoteData.msg); } } if (response.statusCode !== 200) { return cb(raw); } if (remoteData.status !== undefined && remoteData.status !== 'ok') { var msg = remoteData.message || remoteData.msg; return cb(msg, remoteData); } return cb(undefined, remoteData); }); }; exports.doGetApiCall = function(host, uri, errorMsg, cb) { fhreq.GET(host, uri, function(err, remoteData, raw, response) { if (err) { return cb(err); } if (!remoteData) { return cb(i18n._("No remoteData returned. Raw response is: ") + util.inspect(raw)); } if (remoteData.msg !== undefined && remoteData.address !== undefined) { if (remoteData.address["login-status"] === 'none') { return cb(i18n._("Error, check your login details.. \n") + remoteData.msg); } } if (response.statusCode !== 200) { return cb(raw); } if (remoteData.status !== undefined && remoteData.status !== 'ok') { var msg = remoteData.message || remoteData.msg; return cb(errorMsg + msg, remoteData); } return cb(undefined, remoteData, response.status); }); }; exports.doDeleteApiCall = function(host, uri, payload, errorMsg, cb) { fhreq.DELETE(host, uri, payload, function(err, remoteData, raw) { if (err) { return cb(err); } if (!remoteData) { return cb(i18n._("No remoteData returned. Raw response is: ") + util.inspect(raw)); } if (remoteData.msg !== undefined && remoteData.address !== undefined) { if (remoteData.address["login-status"] === 'none') { return cb(i18n._("Error, check your login details.. \n") + remoteData.msg); } } if (remoteData.status !== undefined && remoteData.status !== 'ok') { var msg = remoteData.message || remoteData.msg; return cb(errorMsg + msg, remoteData); } return cb(undefined, remoteData); }); }; // END api calls // Poll remote job until its finished exports.waitForJob = function(cacheKey, start, cb) { var wantJson = ini.get('json'); setTimeout(function() { log.silly(util.format(i18n._("polling for cacheKey: %s start: %s to complete"), cacheKey, start), "Polling"); var cc = {}; cc.cacheKey = cacheKey; cc.start = start; var params = [cc]; var uri = 'box/srv/1.1/dat/log/read?cacheKeys=' + JSON.stringify(params); uri = uri.replace(/"/g, '%22'); fhreq.GET(fhreq.getFeedHenryUrl(), uri, function(err, remoteData) { if (err) { return cb(err); } if (remoteData[0] && remoteData[0].status !== undefined && remoteData[0].status === 'pending') { log.silly(remoteData[0].log, "waitforjob"); start++; if (!wantJson) { if (remoteData[0].progress) { console.log(i18n._("Progress: ") + remoteData[0].progress + "%"); } else { process.stdout.write('.'); } } return exports.waitForJob(cacheKey, start++, cb); } else { if (remoteData[0] && remoteData[0].status && remoteData[0].status === 'error') { return cb(remoteData); } else { return cb(undefined, remoteData); } } }); }, 3000); }; // common strlen function function strlen(str) { var s = str; if (typeof str === 'number') { s = "" + str; } if (typeof str === 'object') { s = util.inspect(str); } var len = 0; if (s) { len = s.length; } return len; } /** * Poll remote job until its finished * @param key * @param cb */ exports.waitFor = function(key, cb) { exports.waitForJob(key, 0, cb); }; //region Tables // common style for all our tables exports.style = function() { var color = ini.get('thcolor') || 'green'; var ret = { 'padding-left': 1, 'padding-right': 1, head: [color] }; return ret; }; /** * Creating a table to view log results (See https://www.elastic.co/guide/en/elasticsearch/reference/1.5/_the_search_api.html) * @param logSearchResults */ exports.createTableForSyslogs = function(logSearchResults) { var table = new Table({ head: ['Time', 'Message', 'Level'], style: exports.style() }); _.each((logSearchResults.hits || {}).hits || [], function(logEntry) { var messages = _.pick(logEntry._source, 'msg', 'error', 'err', 'message'); messages = _.map(_.compact(messages), function(message) { return message.replace(/\r?\n|\r/g,''); }); table.push([logEntry._source.time, messages.join(' '), logEntry._source.level]); }, this); return table; }; exports.createTableForSubmissions = function(submissionsResult) { var table = new Table({ head: ['ID', 'Form ID', 'Form Name', 'Project GUID', 'Project Name', 'Submitted At'], style: exports.style() }); var submissions = submissionsResult.submissions || []; _.each(submissions, function(submission) { table.push([submission._id, submission.formId, submission.formName, submission.appId, submission.appName, moment(submission.submissionCompletedTimestamp).format('MMMM Do YYYY, h:mm:ss a')]); }, this); return table; }; /** * A Table For Listing Data Sources * @param dataSources */ exports.createTableForDataSources = function(dataSources, service) { var table = new Table({ head: ['ID', 'Name', 'Service ID', 'Service Title', 'Endpoint', 'Refresh Interval', 'Number Of Audit Logs'], style: exports.style() }); dataSources = _.isArray(dataSources) ? dataSources : [dataSources]; _.each(dataSources, function(dataSource) { var title = service && service.title ? service.title : dataSource.service.title; table.push([dataSource._id, dataSource.name, dataSource.serviceGuid, title, dataSource.endpoint, moment.duration(dataSource.refreshInterval, 'minutes').humanize(), dataSource.numAuditLogEntries]); }, this); return table; }; /** * Creating A Table For Displaying Data Source Audit Logs * @param dataSource * @param options * - limit: Limiting number of data sources displayed */ exports.createTableForEnvDataSourcesAuditLogs = function(dataSource, options) { var table = new Table({ head: ['Service ID', 'Service Name', 'Endpoint', 'Status', 'Error', 'Data Length', 'Data Hash', 'Refresh Timestamp'], style: exports.style() }); var auditLogs = dataSource.auditLogs || []; _.each(_.first(auditLogs, options.limit || auditLogs.length), function(dsAuditLogEntry) { var currentStatus = dsAuditLogEntry.currentStatus; var errorMsg = currentStatus.error && currentStatus.error.userDetail ? currentStatus.error.userDetail : "NONE"; var dataLength = dsAuditLogEntry.data ? dsAuditLogEntry.data.length : 0; var dataHash = dsAuditLogEntry.dataHash || "NONE"; table.push([dsAuditLogEntry.service.guid, dsAuditLogEntry.service.title, dsAuditLogEntry.endpoint, currentStatus.status, errorMsg, dataLength, dataHash, moment(new Date(dsAuditLogEntry.updateTimestamp), DT_FORMAT).fromNow()]); }); return table; }; /** * A Table Describing Data Sources Deployed To An Environment * @param dataSources */ exports.createTableForEnvDataSources = function(dataSources) { var table = new Table({ head: ['ID', 'Name', 'Service ID', 'Service Name', 'Endpoint', 'Status', 'Error', 'Data Length', 'Last Refreshed'], style: exports.style() }); dataSources = _.isArray(dataSources) ? dataSources : [dataSources]; _.each(dataSources, function(dataSource) { var currentStatus = dataSource.currentStatus; var errorMsg = currentStatus.error && currentStatus.error.userDetail ? currentStatus.error.userDetail : "NONE"; var dataLength = dataSource.data ? dataSource.data.length : 0; table.push([dataSource._id, dataSource.name, dataSource.service.guid, dataSource.service.title, dataSource.endpoint, currentStatus.status, errorMsg, dataLength, moment(dataSource.lastRefreshed, DT_FORMAT).fromNow()]); }, this); return table; }; /** * Creating A Data Table For Validation Of Data Sources * @param dataSources */ exports.createTableForEnvDataSourcesValidation = function(dataSources) { var table = new Table({ head: ['Name', 'Service ID', 'Service Name', 'Endpoint', 'Valid', 'Message', 'Data Length'], style: exports.style() }); dataSources = _.isArray(dataSources) ? dataSources : [dataSources]; _.each(dataSources, function(dataSource) { var validationResult = dataSource.validationResult; var dataLength = dataSource.data ? dataSource.data.length : 0; table.push([dataSource.name, dataSource.service.guid, dataSource.service.title, dataSource.endpoint, validationResult.valid, validationResult.message, dataLength]); }, this); return table; }; exports.createTableForProjectApps = function(project, apps) { var table = new Table({ head: ['Project ID', 'ID', 'Title', 'Type', 'Last Modified'], style: exports.style() }); _.each(apps, function(app) { table.push([project, app.guid, app.title, app.type, moment(app.sysModified, DT_FORMAT).fromNow()]); }, this); return table; }; exports.createTableForProjects = function(projects) { var table = new Table({ head: ['ID', 'Title', 'No. Apps', 'Last Modified'], style: exports.style() }); _.each(projects, function(project) { var numApps = project.apps ? project.apps.length : 0; table.push([project.guid, project.title, numApps, moment(project.sysModified, DT_FORMAT).fromNow()]); }, this); return table; }; exports.createTableForUsers = function(users) { var table = new Table({ head: ['Id', 'Username','Email','Activated', 'Enabled', 'Blacklisted', 'Last Login'], style: exports.style() }); _.each(users, function(user) { table.push([user.guid, user.fields.username, user.fields.email, getYesNoFromBoolean(user.fields.activated), getYesNoFromBoolean(user.fields.enabled), getYesNoFromBoolean(user.fields.blacklisted), (user.fields.lastLogin) ? moment(user.fields.lastLogin, DT_FORMAT).fromNow(): '']); }, this); return table; }; /** * Utils - Inform a boolean value, true or false, and get a string as Yes or No * @param value * @returns {*} */ function getYesNoFromBoolean(value) { if (value) { return i18n._("Yes"); } return i18n._("No"); } exports.createTableForTemplates = function(templates, type) { var table; switch (type) { case 'projects': table = new Table({ head: ['Id', 'Name', 'Type', 'Category'], style: exports.style() }); _.each(templates, function(t) { table.push([t.id, t.name, t.type, t.category]); }, this); return table; case 'services': table = new Table({ head: ['Id', 'Name', 'Category'], style: exports.style() }); _.each(templates, function(t) { table.push([t.id, t.name, t.category]); }, this); return table; case 'apps': table = new Table({ head: ['Id', 'Name', 'Type', 'Category', 'Repo'], style: exports.style() }); _.each(templates, function(t) { table.push([t.id, t.name, t.type,t.category, t.repoUrl]); }, this); return table; default: return table; } }; exports.createTableForProperties = function(type, opts, properties) { var table = new Table({ head: ['guid', 'Name', 'Value'], style: exports.style() }); _.each(properties, function(prop) { prop.guid = prop.guid || "none"; var max = opts.max || 180; if (prop.value.length > max) { prop.value = "TRUNCATED TOO LONG TO DISPLAY. Try --json"; } table.push([prop.guid, prop.name, prop.value]); }, this); return table; }; exports.createTableForTeams = function(teams) { var table = new Table({ head: ['id', 'Name'], style: exports.style() }); _.each(teams, function(t) { table.push([t._id, t.name]); }); return table; }; exports.createTableForMbaasTargets = function(mbassTargets) { var table = new Table({ head: ['ID', 'Label', 'URL', 'Service Key', 'Username', 'Password', 'Modified','Size'], style: exports.style() }); _.each(mbassTargets, function(mbaas) { var key = "-"; var username = "-"; var pwd = "-"; var size = "-"; var mod = "-"; if (mbaas.servicekey) { key = mbaas.servicekey; } if (mbaas.username) { username = mbaas.username; } if (mbaas.password) { pwd = mbaas.password; } if (mbaas.size) { size = mbaas.size; } if (mbaas.modified) { mod = moment(mbaas.modified, DT_FORMAT).fromNow(); } table.push([mbaas.id, mbaas.label, mbaas.url, key, username, pwd, mod, size]); }); return table; }; exports.createTableForEnvironments = function(environments) { var table = new Table({ head: ['ID', 'Label', 'Deploy on Create', 'Deploy on Update', 'MBaaS Targets', 'Modified'], style: exports.style() }); _.each(environments, function(env) { var targetId = 'none'; if (env.target) { targetId = env.target.id; } // Display undefined options as 'false' var autoDeployOnCreate = env.autoDeployOnCreate; if (typeof autoDeployOnCreate === 'undefined') { autoDeployOnCreate = false; } var autoDeployOnUpdate = env.autoDeployOnUpdate; if (typeof autoDeployOnUpdate === 'undefined') { autoDeployOnUpdate = false; } table.push([env.id, env.label, autoDeployOnCreate, autoDeployOnUpdate, targetId, moment(env.modified, DT_FORMAT).fromNow()]); }); return table; }; exports.createTableForStatus = function(result) { var table = new Table({ head: ['Status', result.status], style: exports.style() }); table.push(['amqp', result.summary.amqp]); table.push(['db', result.summary.db]); table.push(['cache', result.summary.cache]); table.push(['git', result.summary.git]); return table; }; exports.createTableForRuntimes = function(runtimes) { var table = new Table({ head: ['Name', 'Description', 'Version', 'Npm Version'], style: exports.style() }); _.each(runtimes, function(runtime) { var npmVersion = " - "; if (runtime.npm && runtime.npm.version) { npmVersion = runtime.npm.version; } table.push([runtime.name, runtime.description, runtime.version, npmVersion]); }); return table; }; exports.createTableForCredentials = function(credentials) { var table = new Table({ head: ['ID', 'Name', 'Platform', 'Type'], style: exports.style() }); _.each(credentials, function(bundle) { table.push([bundle.id, bundle.bundleName, bundle.platform, bundle.bundleType]); }, this); return table; }; //endregion exports.createTableForNotifications = function(notifications) { var maxId = 24, maxUser = 25, maxTS = 20, maxType = 18, maxMessage = 25; for (var i = 0; i < notifications.length; i++) { var noti = notifications[i].fields; if (strlen(noti.eventType) > maxType) { maxType = strlen(noti.eventType); } if (strlen(noti.message) > maxMessage) { maxMessage = strlen(noti.message); } if (strlen(noti.updatedBy) > maxUser) { maxUser = strlen(noti.updatedBy); } } if (maxType > exports.maxTableCell) { maxType = exports.maxTableCell; } if (maxMessage > exports.maxTableCell) { maxMessage = exports.maxTableCell; } var table = new Table({ head: ['Guid', 'When', 'Updated By', 'Event Name', 'Detail'], colWidths: [maxId + 2, maxTS + 2, maxUser + 2, maxType + 2, maxMessage + 2], style: exports.style() }); _.each(notifications, function(notification) { var not = notification.fields; table.push([ notification.guid, moment(not.sysCreated, DT_FORMAT).fromNow(), not.updatedBy, not.eventType, not.message.replace(/[\r?\n]/g, " ") ]); }); return table; }; exports.createTableForAppEnvVars = function(appEnvVars, envValue) { var table = new Table({ head: ['ID', 'Modified', 'Name', 'Value'], style: exports.style() }); if (appEnvVars.length > 0) { _.each(appEnvVars, function(env) { var modified = moment(env.sysCreated, DT_FORMAT).fromNow(); var value = ""; if (env.varValues && env.varValues[envValue]) { value = env.varValues[envValue]; if ( env.masked ) { value = "*"; } } table.push([env.guid || "n/a", modified, env.varName, value]); }); } return table; }; function listApps(projectId, cb) { if (!cb) { cb = projectId; projectId = null; } if (projectId) { exports.doGetApiCall(fhreq.getFeedHenryUrl(), "box/api/projects/" + projectId, i18n._("Error reading app: "), function(err, data) { if (err) { return cb(err); } return cb(undefined, {list: data.apps, status: 'ok'}); }); } else { var payload = {}; exports.doApiCall(fhreq.getFeedHenryUrl(), "box/srv/1.1/ide/" + fhc.curTarget + "/app/list", payload, i18n._("Error reading app: "), cb); } } // returns an array of App Ids function formatIds(err, data, cb) { if (err) { return cb(err); } var ids = []; if (!err) { for (var i = 0; i < data.list.length; i++) { var app = data.list[i]; ids.push(app.id); } } return cb(err, ids); } function getAppIds(cb) { listApps(function(err, data) { formatIds(err, data, cb); }); } // common read app function readApp(projectId, appId, cb) { if (projectId) { exports.doGetApiCall(fhreq.getFeedHenryUrl(), "box/api/projects/" + projectId + "/apps/" + appId, i18n._("Error reading app: "), cb); } else { var payload = { payload: { guid: appId } }; exports.doApiCall(fhreq.getFeedHenryUrl(), "box/srv/1.1/ide/" + fhc.curTarget + "/app/read", payload, i18n._("Error reading app: "), cb); } } // Create a generic 'Name/Value' Table function createNVTable(pairs) { var nvs = []; var maxName = 4, maxValue = 5; _.each(pairs, function(value, name) { if (strlen(name) > maxName) { maxName = strlen(name); } if (strlen(value) > maxValue) { maxValue = strlen(value); } nvs.push([name, util.inspect(value)]); }); if (maxValue > exports.maxTableCell) { maxValue = exports.maxTableCell; } var table = new Table({ head: ['Name', 'Value'], colWidths: [maxName + 2, maxValue + 2], style: exports.style() }); for (var i = 0; i < nvs.length; i++) { table.push(nvs[i]); } return table; } function uppercaseRegexMatch(m, p1, p2) { return p1 + p2.toUpperCase(); } // Create a generic Table from a single object. The object keys will be the table headers. function createObjectTable(obj) { var heads = [], colWidths = [], vals = []; _.each(obj, function(v, k) { v = String(v); heads.push(k.replace(/(^|\s)([a-z])/g, uppercaseRegexMatch)); vals.push(v); var width = Math.max(strlen(k), strlen(v)) + 2; if (width > exports.maxTableCell) { width = exports.maxTableCell; } colWidths.push(width); }); var table = new Table({ head: heads, colWidths: colWidths, style: exports.style() }); table.push(vals); return table; } //Create a generic Table for a list of objects //header object should define the key and max width function createListTable(headers, list) { var heads = [], colWidths = [], cols = []; _.each(headers, function(v, k) { heads.push(k.replace(/(^|\s)([a-z])/g, uppercaseRegexMatch)); cols.push(k); var width = Math.max(strlen(k), v) + 2; if (width > exports.maxTableCell) { width = exports.maxTableCell; } colWidths.push(width); }); var table = new Table({ head: heads, colWidths: colWidths, style: exports.style() }); for (var i = 0; i < list.length; i++) { var obj = list[i]; var out = []; for (var j = 0; j < cols.length; j++) { var col_name = cols[j]; out.push(obj[col_name]); } table.push(out); } return table; } /** * Creating A Table For Displaying MBaaS export jobs * @param appDataList */ exports.createTableForDevices = function(devices) { var table = new Table({ head: ["CUID","Name","Blacklisted", "Disabled", "Type", "Created", "Modified"], style: exports.style() }); var list = devices || []; _.each(list, function(dev) { table.push([dev.fields.cuid, dev.fields.name, dev.fields.blacklisted, dev.fields.disabled, dev.type, moment(dev.fields.sysCreated, DT_FORMAT).fromNow(), moment(dev.fields.sysModified, DT_FORMAT).fromNow()]); }); return table; }; /** * Creating A Table For Displaying MBaaS export jobs * @param appDataList */ exports.createTableForAppData = function(appDataList) { var table = new Table({ head: ['Job ID', 'App ID', 'Environment', 'Status'], style: exports.style() }); var list = appDataList || []; _.each(list, function(data) { var jobId = data._id; var appId = data.appid; var env = data.environment; var status = data.status; table.push([jobId, appId, env, status]); }); return table; }; // Annoyingly, 'request' returns nulls, the rest of FHC uses undefined, so return undefined to keep // things consistent. exports.nullToUndefined = function(err) { return err === null ? undefined : err; }; // utility function for calculating the max width of a field in a collection of objects. Used for table building. function maxField(collection, field, header) { var fields = _.pluck(collection, field); var sizes = _.map(fields, function(f) { return strlen(f); }); sizes.push(strlen(field)); sizes.push(strlen(header)); return _.max(sizes); } // generic create table function, should be used for new tables instead of the legacy ways above.. function createTableFromArray(headers, fields, values) { var colWidths = _.map(fields, function(f, index) { return maxField(values, f, headers[index]) + 2; }); var table = new Table({ head: headers, colWidths: colWidths, style: exports.style() }); _.each(values, function(t) { var vals = _.map(fields, function(f) { if (t[f] === true) { return i18n._('Yes') ; } else if (t[f] === false) { return i18n._('No') ; } return t[f] ? t[f] : ''; }); table.push(vals); }); return table; } function createTableForEventAlert(values) { var headers = ['ID', 'Name', 'Emails', 'Enabled', 'Categories', 'Names', 'Severities']; var fields = ['guid','alertName', 'emails', 'enabled', 'eventCategories', 'eventNames', 'eventSeverities']; return createTableFromArray(headers, fields, values); } exports.createNVTable = createNVTable; exports.createObjectTable = createObjectTable; exports.createListTable = createListTable; exports.readApp = readApp; exports.getAppIds = getAppIds; exports.strlen = strlen; exports.maxTableCell = 100; exports.createTableFromArray = createTableFromArray; exports.createTableForEventAlert = createTableForEventAlert;