UNPKG

@axway/axway-central-cli

Version:

Manage APIs, services and publish to the Amplify Marketplace

298 lines (290 loc) 15.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefinitionsManager = void 0; var _snooplogg = _interopRequireDefault(require("snooplogg")); var _easyTable = _interopRequireDefault(require("easy-table")); var _findIndex = _interopRequireDefault(require("lodash/findIndex")); var _findLastIndex = _interopRequireDefault(require("lodash/findLastIndex")); var _omit = _interopRequireDefault(require("lodash/omit")); var _max2 = _interopRequireDefault(require("lodash/max")); var _min2 = _interopRequireDefault(require("lodash/min")); var _chalk = require("chalk"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 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); } const { log } = (0, _snooplogg.default)('central:class.DefinitionsManager'); /** * Get / fetch / set specs. */ class DefinitionsManager { constructor(apiServerClient) { _defineProperty(this, "apiServerClient", void 0); _defineProperty(this, "specs", void 0); _defineProperty(this, "cli", new Map()); _defineProperty(this, "resources", new Map()); this.apiServerClient = apiServerClient; } /** * Private */ /** * A reducer for sorting ResourceDefinition by references in the sortByReferences's "fromTo" list. * Function utilize current "fromTo" list which is expected to be passed in { sorted, unsorted } form * and "from" list which is used to find references for entities in the "fromTo" list. * IF resource's "to" references still in "unsorted" list - put it back to unsorted also * (its too early for the resource to be sorted). * ELSE resource's "to" links are in the "from" list or/and current "sorted" list - find max index * and append to "right-most" position of the "sorted" list * @param curr * @param defsFrom */ reduceByReferenceLinks(curr, defsFrom) { return curr.unsorted.reduce((a, c, _, arr) => { // IF any unsorted reference found, push current definition to unsorted too and skip const unsortedRefs = c.spec.references.toResources.find(ref => { return (0, _findLastIndex.default)(arr, def => { var _def$spec$scope; return def.spec.kind === ref.kind && ((_def$spec$scope = def.spec.scope) === null || _def$spec$scope === void 0 ? void 0 : _def$spec$scope.kind) === ref.scopeKind; }) !== -1; }); if (!!unsortedRefs) { a.unsorted.push(c); } else { var _max, _min; // ELSE all refs are in pre populated or in sorted lists, calculate const startIndex = (_max = (0, _max2.default)(c.spec.references.toResources.map(ref => { // find index in current sorted array const sortedListIndex = (0, _findLastIndex.default)(a.sorted, def => { var _def$spec$scope2; return def.spec.kind === ref.kind && ((_def$spec$scope2 = def.spec.scope) === null || _def$spec$scope2 === void 0 ? void 0 : _def$spec$scope2.kind) === ref.scopeKind; }); // find index in "from-only" array const fromListIndex = (0, _findLastIndex.default)(defsFrom, def => { var _def$spec$scope3; return def.spec.kind === ref.kind && ((_def$spec$scope3 = def.spec.scope) === null || _def$spec$scope3 === void 0 ? void 0 : _def$spec$scope3.kind) === ref.scopeKind; }); // this should never happen only if the api-server is missing some corresponding // references so nothing found if (sortedListIndex === -1 && fromListIndex === -1) { var _c$spec$scope; log('reduceByReferenceLinks, startIndex error, def: ', JSON.stringify(c, null, 2), '\nref: ', ref); throw Error(`References calculation error, startIndex for kind: ${c.spec.kind} in scope: ${(_c$spec$scope = c.spec.scope) === null || _c$spec$scope === void 0 ? void 0 : _c$spec$scope.kind}`); } // if nothing found in sorted and pre return null so it will put it back to unsorted else return sortedListIndex === -1 ? 0 : sortedListIndex; }))) !== null && _max !== void 0 ? _max : null; const stopIndex = (_min = (0, _min2.default)(c.spec.references.fromResources.map(ref => { const i = (0, _findIndex.default)(a.sorted, def => { var _def$spec$scope4; return def.spec.kind === ref.kind && ((_def$spec$scope4 = def.spec.scope) === null || _def$spec$scope4 === void 0 ? void 0 : _def$spec$scope4.kind) === ref.scopeKind; }); return i === -1 ? null : i; }))) !== null && _min !== void 0 ? _min : null; if (startIndex && stopIndex && startIndex >= stopIndex || startIndex === null) { var _c$spec$scope2; log('reduceByReferenceLinks, indexes error, definition: ', JSON.stringify(c, null, 2)); throw Error(`References calculation error, indexes for kind: ${c.spec.kind} in scope: ${(_c$spec$scope2 = c.spec.scope) === null || _c$spec$scope2 === void 0 ? void 0 : _c$spec$scope2.kind}`); } else { a.sorted.splice(startIndex + 1, 0, c); } } return a; }, { sorted: curr.sorted, unsorted: [] }); } /** * Utility for sorting ResourceDefinition by refs. Its grouping resources into 4 arrays and * iterates over the "fromTo" list with "reduceByReferenceLinks" reducer until its completely sorted. * @param defs list of resources to sort */ sortByReferences(defs) { // 1. Sort by references into 4 arrays const groupedDefs = defs.reduce((a, c) => { const fromRefsNum = c.spec.references.fromResources.length; const toRefsNum = c.spec.references.toResources.length; if (fromRefsNum && toRefsNum) a.fromTo.push(c);else if (!fromRefsNum && !toRefsNum) a.noRefs.push(c);else if (!toRefsNum) a.from.push(c);else if (!fromRefsNum) a.to.push(c); return a; }, { noRefs: [], from: [], fromTo: [], to: [] }); // 2. Iterate over "fromTo" defs until its completely sorted let result = this.reduceByReferenceLinks({ sorted: [], unsorted: groupedDefs.fromTo }, groupedDefs.from); let loopCount = 0; // just in case, circuit breaker; while (result.unsorted.length > 0 && loopCount <= 1000) { result = this.reduceByReferenceLinks(result, groupedDefs.from); loopCount += 1; } // On average function should not take more than 5 loops currently. // Lets signal that something is wrong here. if (loopCount === 1000) throw Error('Definition references calculation error, max loop count reached'); return [...groupedDefs.noRefs, ...groupedDefs.from, ...result.sorted, ...groupedDefs.to]; } /** * Public * Constructs two maps per resource group(e.g. management, catalog) * First map is for the nested 'cli' object and the other is for the nested 'resource' object * Created by stripping the 'definitions' group from the specs object, leaving only the 'management' and 'catalog' groups as of 6/7(this will dynamically update in case new groups are added on api-server) * Then iterating over that specs object and pushing the cli and resource objects for each group into arrays, which are used to initialize the final maps */ async init() { log('init'); this.specs = await this.apiServerClient.getSpecs(); const filteredSpecs = (0, _omit.default)(this.specs, 'definitions'); const cliArray = []; const resourcesArray = []; for (const [key] of Object.entries(filteredSpecs)) { resourcesArray.push(...filteredSpecs[key].resources); cliArray.push(...filteredSpecs[key].cli); } this.cli = new Map(cliArray); this.resources = new Map(resourcesArray); return this; } getAllWordsList() { if (!this.specs) return []; const result = []; this.cli.forEach(v => { result.push(v.spec.names.plural, v.spec.names.singular, ...v.spec.names.shortNames); }); return result; } getAllKindsList() { if (!this.specs) throw Error('DefinitionManager.specs is not initialized.'); const result = new Set([]); this.resources.forEach(v => { result.add(v.spec.kind); }); return result; } /** * Get the map with resources definitions sorted by references. * Used to identify the correct order in which resources should be created / removed. * @returns {Map<string,ResourceDefinition>} map where key is ResourceDefinition.name, value is ResourceDefinition */ getSortedKindsMap() { if (!this.specs) throw Error('DefinitionManager.specs is not initialized.'); // For each spec modify the references: // 1. since cli do not support creating or updating sub-resources we are ignoring references and removing them atm. // 2. if it is a scoped resource, add a manual "to" reference to the scope resource and a corresponding "from" // reference in the scope resource this.resources.forEach(definition => { // 1. remove the references from the sub-resources and circular references (to self) // TODO: circular references support: https://jira.axway.com/browse/APIGOV-20808 definition.spec.references.toResources = definition.spec.references.toResources.filter(ref => { var _definition$spec$scop; return !ref.from && !(ref.kind === definition.spec.kind && ref.scopeKind === ((_definition$spec$scop = definition.spec.scope) === null || _definition$spec$scop === void 0 ? void 0 : _definition$spec$scop.kind)); }); definition.spec.references.fromResources = definition.spec.references.fromResources.filter(ref => { var _definition$spec$scop2; return !ref.from && !(ref.kind === definition.spec.kind && ref.scopeKind === ((_definition$spec$scop2 = definition.spec.scope) === null || _definition$spec$scop2 === void 0 ? void 0 : _definition$spec$scop2.kind)); }); // 2. add references between scope and scoped resources if (!!definition.spec.scope) { const scopeDef = [...this.resources.values()].find(res => res.spec.kind === definition.spec.scope.kind); // mind the non-null assertion here // modify current definition by adding "toResources" link to scopeDef if (!definition.spec.references.toResources.find(ref => ref.kind === scopeDef.spec.kind)) { definition.spec.references.toResources.push({ kind: scopeDef.spec.kind, // @ts-ignore // NOTE: not used value, adding just to indicate it's manual nature. types: ['CALCULATED'] }); } // modify related "scope" definition by adding "fromResources" link to current definition if (!scopeDef.spec.references.fromResources.find(ref => ref.kind === definition.spec.kind)) { scopeDef.spec.references.fromResources.push({ kind: definition.spec.kind, // @ts-ignore // NOTE: not used value, adding just to indicate it's manual nature. types: ['CALCULATED'], scopeKind: definition.spec.scope.kind }); } } }); // execute the sorting, note that the returning map is using the "name" field as keys. const res = this.sortByReferences([...this.resources.values()]); return new Map(res.map(v => [v.name, v])); } getDefsTableForHelpMsg() { if (!this.specs) return 'No resources found.'; const t = new _easyTable.default(); // create the 'axway central get' table this.cli.forEach(v => { var _this$resources$get, _this$resources$get2, _this$resources, _this$resources$get3, _this$resources$get3$; // grab the resource group const group = v.metadata.scope.name; t.cell('RESOURCE', `${v.spec.names.plural}`, () => (0, _chalk.cyan)(v.spec.names.plural)); t.cell('SHORT NAMES', [...(v.spec.names.shortNamesAlias || v.spec.names.shortNames)].join(',')); t.cell('RESOURCE KIND', (_this$resources$get = this.resources.get(v.spec.resourceDefinition)) === null || _this$resources$get === void 0 ? void 0 : _this$resources$get.spec.kind); t.cell('SCOPED', (_this$resources$get2 = this.resources.get(v.spec.resourceDefinition)) !== null && _this$resources$get2 !== void 0 && _this$resources$get2.spec.scope ? 'true' : 'false'); t.cell('SCOPE KIND', (_this$resources = this.resources) === null || _this$resources === void 0 ? void 0 : (_this$resources$get3 = _this$resources.get(v.spec.resourceDefinition)) === null || _this$resources$get3 === void 0 ? void 0 : (_this$resources$get3$ = _this$resources$get3.spec.scope) === null || _this$resources$get3$ === void 0 ? void 0 : _this$resources$get3$.kind); t.cell('RESOURCE GROUP', group); t.newRow(); }); return t.sort(['RESOURCE']).toString(); } findDefsByKind(kind) { log('findDefsByKind: ', kind); const res = [...this.resources].reduce((a, [_, def]) => { if (def.spec.kind === kind) { a.push({ resource: def, cli: [...this.cli].find(([_, cliDef]) => cliDef.spec.resourceDefinition === def.name)[1], scope: def.spec.scope ? [...this.resources].find(([_, resDef]) => resDef.spec.kind === def.spec.scope.kind)[1] : undefined }); } return a; }, []); return res.length ? res : null; } /** * Returns set of related definitions if word is known. * @param word word to search for * @returns {object | null} { * resource: definition of the resource * cli: cli definition of the resource * scope: scope resource definition, can support multiple scopes (only for scoped resources, otherwise it is undefined) * } or null if no definitions found for this word. */ findDefsByWord(word) { log('findDefsByWord: ', word); if (!this.specs) return null; const cliKv = [...this.cli].filter(([_, v]) => { var _v$spec$names$shortNa; return v.spec.names.plural === word || v.spec.names.singular === word || v.spec.names.shortNames.includes(word) || ((_v$spec$names$shortNa = v.spec.names.shortNamesAlias) === null || _v$spec$names$shortNa === void 0 ? void 0 : _v$spec$names$shortNa.includes(word)); }); // no match found returning null if (!cliKv.length) return null; const result = [...this.cli].reduce((a, [_, cliDef]) => { var _cliDef$spec$names$sh; if (cliDef.spec.names.plural === word || cliDef.spec.names.singular === word || cliDef.spec.names.shortNames.includes(word) || (_cliDef$spec$names$sh = cliDef.spec.names.shortNamesAlias) !== null && _cliDef$spec$names$sh !== void 0 && _cliDef$spec$names$sh.includes(word)) { var _this$findDefsByKind; // note: mind non-null assertion const resource = this.resources.get(cliDef.spec.resourceDefinition); const scope = resource.spec.scope ? (_this$findDefsByKind = this.findDefsByKind(resource.spec.scope.kind)) === null || _this$findDefsByKind === void 0 ? void 0 : _this$findDefsByKind[0].resource : null; a.push({ resource, cli: cliDef, scope: !!scope ? scope : undefined }); } return a; }, []); return result; } } exports.DefinitionsManager = DefinitionsManager;