UNPKG

@axway/axway-central-cli

Version:

Manage APIs, services and publish to the Amplify Marketplace

409 lines (394 loc) 21 kB
"use strict"; 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; }