@axway/axway-central-cli
Version:
Manage APIs, services and publish to the Amplify Marketplace
409 lines (394 loc) • 21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _cliKit = require("cli-kit");
var _ora = _interopRequireDefault(require("ora"));
var _CompositeError = require("./CompositeError");
var _resultsRenderers = require("./resultsRenderers");
var _types = require("./types");
var _utils = require("./utils");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
var _Renderer_brand = /*#__PURE__*/new WeakSet();
class Renderer {
constructor(console, output) {
/**
* Creates an error message for the given ApiResponseError title and detail strings.
* @param prefix String to be prefixed to the message.
* @param title Optional title such as "Validation error", "Forbidden error", etc.
* @param detail Optional error detail explaining exactly what went wrong.
* @returns Returns an error message to be outputted to the console.
*/
_classPrivateMethodInitSpec(this, _Renderer_brand);
_defineProperty(this, "spinner", void 0);
_defineProperty(this, "_console", void 0);
_defineProperty(this, "output", void 0);
this.spinner = process.env['DEBUG'] || !!output ? null : (0, _ora.default)({
spinner: 'dots3'
});
this._console = console;
this.output = output;
}
/**
* Start the spinner and return self
* @param text text to display near the spinner
*/
startSpin(text) {
this.spinner && this.spinner.start(text);
return this;
}
/**
* Stop the spinner
*/
stopSpin() {
this.spinner && this.spinner.stop();
return this;
}
/**
* Replaces the text shown by startSpin(). Intended to show progress.
* @param text The text to displayed next to the spinner.
*/
updateSpinText(text) {
if (this.spinner) {
this.spinner.text = text;
}
}
/**
* Print simple text to console
* @param text text to render
*/
console(text) {
this._console.log(text);
}
/**
* Render success message. If output param has been provided use simple console
* otherwise use spinner.
* @param text text to render
* @param spinnerOnly optional, if its true message will be rendered
* only when spinner is in use (which mean no output param has been provided)
*/
success(text, spinnerOnly = false) {
this.output && !spinnerOnly ? this.console(text) : this.spinner && this.spinner.succeed((0, _cliKit.chalk)`{greenBright ${text}}`);
}
/**
* Render warning message. If output param has been provided use simple console
* otherwise use spinner.
* @param text text to render
* @param spinnerOnly optional, if its true message will be rendered
* only when spinner is in use (which mean no output param has been provided)
*/
warning(text, spinnerOnly = false) {
this.output && !spinnerOnly ? this.console(text) : this.spinner && this.spinner.warn((0, _cliKit.chalk)`{yellow ${text}}`);
}
/**
* Render error message. If output param has been provided use simple console
* otherwise use spinner.
* @param text text to render
* @param spinnerOnly optional, if true message will be rendered
* only when spinner is in use (which mean no output param has been provided)
*/
error(text, spinnerOnly = false) {
this.output && !spinnerOnly ? this.console(text) : this.spinner && this.spinner.fail((0, _cliKit.chalk)`{red ${text}}`);
}
/**
* A helper returning `"<resource kind>/<resource name>" in the scope "<scope kind>/<scope name>"`
* string used to print bulk results (in "bulkResult" or "bulkCreateOrUpdateResult") or individually (see the "delete" cmd)
* @param {GenericResource} resource resource for witch the string should be created
* @returns {string}
*/
resourceAndScopeKinds(resource) {
var _resource$metadata;
// prettier-ignore
return `"${resource.kind}/${resource.name}"${!!((_resource$metadata = resource.metadata) !== null && _resource$metadata !== void 0 && _resource$metadata.scope) ? ` in the scope "${resource.metadata.scope.kind}/${resource.metadata.scope.name}"` : ``}`;
}
/**
* A helper returning `"<kind>/<name>" subresource "<subResourceNaem>" in the scope "<scopeKind>/<scopeName>"`
* string used to print bulk results (in "bulkResult" or "bulkCreateOrUpdateResult") or individually (see the "delete" cmd)
* @param {GenericResource} resource resource for witch the string should be created
* @param {string} subResourceName of the subresource that was updated
* @returns {string}
*/
subResourceAndScopeKinds(resource, subResourceName) {
var _resource$metadata2;
let message = `"${resource.kind}/${resource.name}" subresource "${subResourceName}"`;
if ((_resource$metadata2 = resource.metadata) !== null && _resource$metadata2 !== void 0 && _resource$metadata2.scope) {
message += ` in the scope "${resource.metadata.scope.kind}/${resource.metadata.scope.name}"`;
}
return message;
}
/**
* Render bulk call result.
* If error is happening - render as simple output (even if "output" param has been provided)
* @param bulkResult bulk response from ApiServerClient
* @param simpleSuccessMsg message to display for each created "kind/name"
*/
bulkResult(bulkResult, simpleSuccessMsg) {
if (bulkResult.error.length) {
var _bulkResult$warning;
(_bulkResult$warning = bulkResult.warning) === null || _bulkResult$warning === void 0 ? void 0 : _bulkResult$warning.forEach(r => this.warning(`${this.resourceAndScopeKinds(r)} was created with an autogenerated logical name.`));
bulkResult.success.forEach(r => this.success(`${this.resourceAndScopeKinds(r)} ${simpleSuccessMsg}`));
bulkResult.error.forEach(r => this.anyError(r.error, `"${r.kind}/${r.name}" `, true));
} else if (this.output) {
var _bulkResult$warning2;
let results = bulkResult.success;
if ((_bulkResult$warning2 = bulkResult.warning) !== null && _bulkResult$warning2 !== void 0 && _bulkResult$warning2.length) {
results = bulkResult.warning.concat(results);
}
(0, _resultsRenderers.renderResponse)(this._console, results, this.output);
} else {
var _bulkResult$warning3;
(_bulkResult$warning3 = bulkResult.warning) === null || _bulkResult$warning3 === void 0 ? void 0 : _bulkResult$warning3.forEach(r => this.warning(`${this.resourceAndScopeKinds(r)} was created with an autogenerated logical name.`));
bulkResult.success.forEach(r => this.success(`${this.resourceAndScopeKinds(r)} ${simpleSuccessMsg}`));
}
}
/**
* Render bulk call result.
* If error is happening - render as simple output (even if "output" param has been provided)
* @param bulkResult bulk response from ApiServerClient
* @param simpleSuccessMsg message to display for each created "kind/name"
*/
productizationResult(bulkResultMap) {
bulkResultMap.forEach((value, key) => {
console.log('\n\n' + 'API Service: ' + key);
if (value.warning && value.warning.length > 0) {
var _value$warning;
(_value$warning = value.warning) === null || _value$warning === void 0 ? void 0 : _value$warning.forEach(r => this.success(`${this.resourceAndScopeKinds(r)} was created successfully with an autogenerated logical name.`));
}
if (value.error.length > 0) {
value.error.forEach(r => this.anyError(r.error, `"${r.kind}/${r.name}" `, true));
if (key) this.warning('Unable to productize API Service ' + "'" + key + "' for the above errors.");
} else {
console.log('API Service ' + "'" + key + "' has been successfully productized.");
}
});
}
/**
* Render bulk "apply" result (with different success messages).
* If error is happening - render as simple output (even if "output" param has been provided)
* @param results array of responses from createOrUpdate ApiServerClient
*/
bulkCreateOrUpdateResult(results) {
if (results.every(r => {
var _r$error$length, _r$error;
return ((_r$error$length = (_r$error = r.error) === null || _r$error === void 0 ? void 0 : _r$error.length) !== null && _r$error$length !== void 0 ? _r$error$length : 0) === 0;
}) && this.output) {
// Output responses as JSON/YAML, but only if no error responses were received.
const dataArray = results.map(r => r.data).filter(r => r != null);
(0, _resultsRenderers.renderResponse)(this._console, dataArray, this.output);
} else {
// Log results.
for (const result of results) {
var _result$error;
if (result.data) {
if (result.wasAutoNamed) {
this.warning(this.resourceAndScopeKinds(result.data) + ' was created with an autogenerated logical name.');
} else if (result.wasCreated) {
this.success(this.resourceAndScopeKinds(result.data) + ' has successfully been created.');
} else if (result.wasMainResourceChanged) {
this.success(this.resourceAndScopeKinds(result.data) + ' has successfully been updated.');
}
}
(_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.forEach(r => this.anyError(r.error, `"${r.kind}/${r.name}" `, true));
if (!result.wasMainResourceChanged && result.data) {
var _result$updatedSubRes;
(_result$updatedSubRes = result.updatedSubResourceNames) === null || _result$updatedSubRes === void 0 ? void 0 : _result$updatedSubRes.forEach(name => this.success(this.subResourceAndScopeKinds(result.data, name) + ' has successfully been updated.'));
}
}
}
}
renderGetResults(bulkResultsArray, successMsg, langDef) {
// sort all results by success / error
// IMPORTANT: mind the response.data! non-null assertion later on, should be covered by this loop
const sortedResults = bulkResultsArray.reduce((a, c) => {
c.response.error ? a.error.push(c) : a.success.push(c);
return a;
}, {
success: [],
error: []
});
if (this.output) {
/**
* IF bulkResultsArray.length === 1 this means user query only for a single resource (entire list or by name,
* no comma separated: "axway central get env" or "axway central get env abc") so result should
* represent list or a single resource based on the received result from the server. In this case setting the
* dataToRender to first "success" element (sortedResults will also include only one element in this case).
*/
let dataToRender = [];
if (bulkResultsArray.length === 1) {
var _sortedResults$succes, _sortedResults$succes2;
dataToRender = ((_sortedResults$succes = sortedResults.success[0]) === null || _sortedResults$succes === void 0 ? void 0 : (_sortedResults$succes2 = _sortedResults$succes.response) === null || _sortedResults$succes2 === void 0 ? void 0 : _sortedResults$succes2.data) || {};
} else {
/**
* ELSE user query for multiple resources (ie: "wh,secret"), which means all result should be presented as
* an array even if its the only one, so flatten the responses data and create a single array for rendering
*/
dataToRender = sortedResults.success.reduce((a, v) => {
Array.isArray(v.response.data) ? a.push(...v.response.data) : a.push(v.response.data);
return a;
}, []);
}
if (langDef) {
dataToRender = this.formatLanguageDefinitionGetResults(dataToRender);
}
(0, _resultsRenderers.renderResponse)(this._console, dataToRender, this.output);
} else {
// stop spinner and render a table for each successful request
if (sortedResults.success.length) this.success(successMsg, true);
const sortedSuccess = sortedResults.success.reduce((a, c) => {
Array.isArray(c.response.data) && !c.response.data.length ? a.empty.push(c) : a.notEmpty.push(c);
return a;
}, {
empty: [],
notEmpty: []
});
// if all results are empty, render just once, otherwise render only successful results
if (!sortedSuccess.notEmpty.length && sortedSuccess.empty.length) {
(0, _resultsRenderers.renderResponse)(this._console, sortedSuccess.empty[0].response.data, this.output, sortedSuccess.empty[0].columns);
} else {
sortedSuccess.notEmpty.forEach(v => (0, _resultsRenderers.renderResponse)(this._console, v.response.data, this.output, v.columns));
}
}
// rendering errors only if there are zero successful results,
// also assuming only first (and only) error will be in api-server error response in case of 404
if (!sortedResults.success.length) sortedResults.error.forEach(v => v.response.error && this.error(`Error: ${v.response.error[0].detail || v.response.error[0].title}`));
}
/**
* Render any kind of error
* @param error error or ApiServer error to render
* @param prefixMsg a string to put before the error details (works only for api-server / known errors)
* @param ignoreOutputParam if provided as true will ignore the output param and always render
* the error message as set of strings. Currently used in bulk result renderers in case of any errors.
*/
formatLanguageDefinitionGetResults(data) {
let dataArr = [];
if (!Array.isArray(data)) {
dataArr.push(data);
} else {
dataArr = data;
}
dataArr.forEach(data => {
var _data$metadata, _data$metadata2, _data$metadata3, _data$metadata4, _data$metadata5, _data$metadata5$scope, _data$languages;
data === null || data === void 0 ? true : (_data$metadata = data.metadata) === null || _data$metadata === void 0 ? true : delete _data$metadata.audit;
data === null || data === void 0 ? true : (_data$metadata2 = data.metadata) === null || _data$metadata2 === void 0 ? true : delete _data$metadata2.acl;
data === null || data === void 0 ? true : (_data$metadata3 = data.metadata) === null || _data$metadata3 === void 0 ? true : delete _data$metadata3.accessRights;
data === null || data === void 0 ? true : (_data$metadata4 = data.metadata) === null || _data$metadata4 === void 0 ? true : delete _data$metadata4.references;
data === null || data === void 0 ? true : (_data$metadata5 = data.metadata) === null || _data$metadata5 === void 0 ? true : (_data$metadata5$scope = _data$metadata5.scope) === null || _data$metadata5$scope === void 0 ? true : delete _data$metadata5$scope.title;
data === null || data === void 0 ? true : (_data$languages = data.languages) === null || _data$languages === void 0 ? true : delete _data$languages.metadata;
});
return dataArr;
}
/**
* Render any kind of error
* @param error error or ApiServer error to render
* @param prefixMsg a string to put before the error details (works only for api-server / known errors)
* @param ignoreOutputParam if provided as true will ignore the output param and always render
* the error message as set of strings. Currently used in bulk result renderers in case of any errors.
*/
anyError(error, prefixMsg = '', ignoreOutputParam = false) {
var _error$errors;
if ((this.output === _types.OutputTypes.json || this.output === _types.OutputTypes.yaml) && !ignoreOutputParam) {
// Output given error to a JSON or YAML format.
let response;
if ((0, _utils.isApiServerErrorType)(error) || (0, _utils.isApiServerErrorResponseType)(error)) {
if (error instanceof Error && error.name === 'HTTPError' && error.errors) {
// The HTTP response has an errors array. Only log that part.
response = error.errors;
} else {
response = error;
}
} else if (error instanceof _CompositeError.CompositeError) {
response = error.toDictionary();
} else {
response = {
name: error === null || error === void 0 ? void 0 : error.name,
message: error === null || error === void 0 ? void 0 : error.message
};
}
(0, _resultsRenderers.renderResponse)(this._console, response, this.output);
} else if (error instanceof _CompositeError.CompositeError) {
// A hierarchy of nested errors was provided. Log it with appropriate indentation.
const logCompositeError = compositeError => {
const indentation = this.output ? '' : ' ';
this.error(compositeError.toNestedMessageArray().join(`\n${indentation}`));
};
if (error.message) {
// Root error has a message. Log entire error hierarchy as a single error.
logCompositeError(error);
} else {
// Root error does not have a message. Log child errors separately as a flat error message list.
for (const nestedError of error.nestedErrors) {
if (nestedError instanceof _CompositeError.CompositeError) {
logCompositeError(nestedError);
} else {
this.error(nestedError.message);
}
}
}
} else if (error instanceof Error && error.name !== 'HTTPError') {
// ELSE IF some generic error is happening (and its not an instance of "got" HTTPError)
const message = error.name === 'AbortError' ? `Error: couldn't connect to Amplify Central` : `${error.name}: ${error.message}`;
this.error(message);
} else if ((0, _utils.isApiServerErrorResponseType)(error) && (_error$errors = error.errors) !== null && _error$errors !== void 0 && _error$errors.length) {
// We were given an array of ApiServerError types. Log them separately.
for (const nextError of error.errors) {
this.anyError(nextError, prefixMsg, ignoreOutputParam);
}
} else {
// ELSE this is ApiServer error or ApiServer error response
// using just first error from api response since all bulk operations executed one-by-one for now
const err = error;
switch (err.status) {
// TODO: some pasta here: 401 thrown manually on data service, fix it?
// https://jira.axway.com/browse/APIGOV-20818
case 401:
this.error(_assertClassBrand(_Renderer_brand, this, _createApiServerErrorMessage).call(this, prefixMsg, err.title || "Looks like you're not authenticated!"));
this.console('\nTry running:');
this.console((0, _cliKit.chalk)`{cyan axway auth login}`);
this.console('Or if using a service account:');
this.console((0, _cliKit.chalk)`{cyan axway auth login --client-id <Service Account Client ID> --secret-file <Private Key>}`);
break;
case 400:
this.error(_assertClassBrand(_Renderer_brand, this, _createApiServerErrorMessage).call(this, prefixMsg, err.title, err.detail));
if (err.source) this.console(_cliKit.chalk.gray(`Caused by: ${JSON.stringify(err.source)}`));
break;
case 403:
case 404:
case 409:
case 500:
case 0:
// status 0 used for internal errors (see ApiServerClient.deleteSingleResource wait logic)
this.error(_assertClassBrand(_Renderer_brand, this, _createApiServerErrorMessage).call(this, prefixMsg, err.title, err.detail));
break;
default:
// rare case, should almost never happen.
if (error instanceof Error) {
this.error(error.toString());
} else {
this.error(`An unknown error occurred, try different output formats (for ex.: "-o ${_types.OutputTypes.json}") to find out more details.`);
}
}
}
}
}
exports.default = Renderer;
function _createApiServerErrorMessage(prefix, title, detail) {
let message = prefix;
if (message.length > 0 && !message.endsWith(' ')) {
message += ' ';
}
message += title || 'Error';
if (detail) {
if (!message.endsWith(':') && !message.endsWith('.') && !message.endsWith('!')) {
message += ':';
}
message += ' ' + detail;
}
return message;
}