UNPKG

@axway/axway-central-cli

Version:

Manage APIs, services and publish to the Amplify Marketplace

1,109 lines (1,084 loc) 45.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApiServerVersions = exports.ApiServerClient = void 0; var _assign = _interopRequireDefault(require("lodash/assign")); var _isEmpty = _interopRequireDefault(require("lodash/isEmpty")); var _pickBy = _interopRequireDefault(require("lodash/pickBy")); var _snooplogg = _interopRequireDefault(require("snooplogg")); var _CacheController = require("./CacheController"); var _dataService = require("./dataService"); var _types = require("./types"); var _utils = require("./utils"); var _chalk = _interopRequireDefault(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.ApiServerClient'); /** * ApiServer backend types */ let ApiServerVersions = exports.ApiServerVersions = /*#__PURE__*/function (ApiServerVersions) { ApiServerVersions["v1alpha1"] = "v1alpha1"; return ApiServerVersions; }({}); /** * Client's types */ class ApiServerClient { /** * Init temporary file if "data" is provided - write data to file (as YAML at the moment) * @param {object} data optional data to write while creating file */ constructor({ baseUrl, region, account, useCache, team, forceGetAuthInfo } = {}) { _defineProperty(this, "baseUrl", void 0); _defineProperty(this, "region", void 0); _defineProperty(this, "useCache", void 0); _defineProperty(this, "account", void 0); _defineProperty(this, "team", void 0); _defineProperty(this, "forceGetAuthInfo", void 0); log(`initializing client with params: baseUrl = ${baseUrl}, region = ${region}, account = ${account}, useCache = ${useCache}, team = ${team}`); this.baseUrl = baseUrl; this.account = account; this.region = region; this.useCache = useCache === undefined ? true : useCache; // using cache by default this.team = team; this.forceGetAuthInfo = forceGetAuthInfo; } /** * Build resource url based on its ResourceDefinition and passed scope def and name. * Note that for scope url part both name and def needed. * The returned URL path is expected to be appended to the base URL. */ buildResourceUrlPath({ resourceDef, resourceName, scopeDef, scopeName, version = ApiServerVersions.v1alpha1, forceDelete = false, expand, langDef, fieldSet, embed }) { const groupUrl = `/${resourceDef.metadata.scope.name}/${version}`; const scopeUrl = scopeName && scopeDef ? `/${scopeDef.spec.plural}/${encodeURIComponent(scopeName)}` : ''; const resourceUrl = `/${resourceDef.spec.plural}`; const nameUrl = resourceName ? `/${encodeURIComponent(resourceName)}` : ''; const embedSet = new Set(embed === null || embed === void 0 ? void 0 : embed.split(',')); const expandSet = new Set(expand === null || expand === void 0 ? void 0 : expand.split(',')); if (langDef) { var _fieldSet; (_fieldSet = fieldSet) !== null && _fieldSet !== void 0 ? _fieldSet : fieldSet = new Set(); fieldSet.add('languages').add('group').add('apiVersion').add('name').add('kind').add('metadata'); expandSet.add('languages'); let languageTypesArr = []; Object.keys(_types.LanguageTypes).forEach(key => languageTypesArr.push((0, _utils.ValueFromKey)(_types.LanguageTypes, key))); langDef.split(',').forEach(code => { if (languageTypesArr.includes(code)) { embedSet.add(`languages-${code.trim()}.resource`); expandSet.add(`languages-${code.trim()}`); fieldSet.add(`languages-${code.trim()}.values`); } else if (code.trim().length > 0) { console.log(_chalk.default.yellow(`\n\'${code}\' language code is not supported. Allowed language codes: ${_types.LanguageTypes.French} | ${_types.LanguageTypes.German} | ${_types.LanguageTypes.US} | ${_types.LanguageTypes.Portugese}.'`)); } }); } let url = `${groupUrl}${scopeUrl}${resourceUrl}${nameUrl}`; if (forceDelete || embedSet.size || expandSet.size || fieldSet) { const queryParams = []; if (forceDelete) { queryParams.push('forceDelete=true'); } if (embedSet.size) { queryParams.push('embed=' + [...embedSet].join(',')); } if (expandSet.size) { queryParams.push('expand=' + [...expandSet].join(',')); } if (fieldSet) { // If field set is empty, then return no fields. This is intentional. queryParams.push('fields=' + [...fieldSet].join(',')); } url += '?' + queryParams.join('&'); } return url; } /** * Generates an array of PUT requests for sub-resources based on resource input * * @param {Object} args function expects arguments as an object * @param {GenericResource} args.resource resource input (not the APIs response) * @param {string} args.resourceName resource name * @param {string} args.subResourceName subresource name * @param {ResourceDefinition} args.resourceDef resource definition * @param {string} [args.scopeName] scope name * @param {ResourceDefinition} [args.scopeDef] scope definition * @param {string} [args.version] api's version * @returns {Promise<Array<() => Promise<any> | null>} returns an array of "request creators" functions * that will be used in {@link resolveSubResourcesRequests} to create sub-resources when needed */ async generateSubResourcesRequests({ resource, resourceName, subResourceName, resourceDef, scopeDef, scopeName, version, createAction, language }) { var _resourceDef$spec$sub, _resourceDef$spec$sub2; const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account, team: this.team }); const urlPath = this.buildResourceUrlPath({ resourceDef, resourceName, scopeDef, scopeName, version }); const knownSubResourcesNames = (_resourceDef$spec$sub = (_resourceDef$spec$sub2 = resourceDef.spec.subResources) === null || _resourceDef$spec$sub2 === void 0 ? void 0 : _resourceDef$spec$sub2.names) !== null && _resourceDef$spec$sub !== void 0 ? _resourceDef$spec$sub : []; const foundSubResources = (0, _pickBy.default)(resource, (_, key) => { if (key.startsWith('x-') || knownSubResourcesNames.includes(key)) { return !subResourceName || subResourceName === key; } return false; }); if (language) { const langSubResourcesNames = (0, _utils.createLanguageSubresourceNames)(language); langSubResourcesNames.forEach(name => { if (!Object.keys(foundSubResources).includes(name) && name !== 'languages') { console.log(_chalk.default.yellow(`\n\'${name}\' subresource definition not found, hence create/update cannot be performed on \'${name}\' subresource.`)); } }); Object.keys(foundSubResources).forEach(subRes => { if (!langSubResourcesNames.includes(subRes)) { // For create, only delete the language subresources that are not passed in the 'language' argument. if (createAction) { if (subRes.includes('languages')) { delete foundSubResources[subRes]; } } // For update, delete all the subresources except the ones passed in the 'language' argument. else { delete foundSubResources[subRes]; } } }); } return (0, _isEmpty.default)(foundSubResources) ? null : Object.keys(foundSubResources).map(key => { return { name: key, operation: () => service.put(`${urlPath}/${key}?fields=${key}`, { [key]: foundSubResources[key] }).catch(err => Promise.reject({ name: key, requestError: err })) }; }); } /** * Executes sub-resources requests generated by {@link generateSubResourcesRequests} * * @param {GenericResource} mainResourceResponse API response of the main resource update/create * @param {Array<() => Promise<any>> | null} pendingCalls an array of "request creators" functions for sub-resources * @returns {ApiServerClientSingleResult} returns mainResourceResponse merged with successful sub-resources results * and error details if encountered */ async resolveSubResourcesRequests(mainResourceResponse, pendingCalls) { var _result$error2, _result$error3; if (!pendingCalls) { return { data: mainResourceResponse, error: null }; } log(`resolving sub-resources, pending calls = ${pendingCalls.length}.`); // note: errors set to an empty array initially, will reset to null if no errors found const result = { data: null, updatedSubResourceNames: [], error: [] }; const subResourcesCombined = (await Promise.allSettled(pendingCalls.map(async next => { var _result$updatedSubRes; const opResult = await next.operation(); (_result$updatedSubRes = result.updatedSubResourceNames) === null || _result$updatedSubRes === void 0 ? void 0 : _result$updatedSubRes.push(next.name); return opResult; }))).reduce((a, c) => { var _c$reason$requestErro; if (c.status === 'fulfilled') { return { ...a, ...c.value }; } // expecting only a valid ApiServer error response here // re-throw if something different, so it should be handled by command's catch block. if ((_c$reason$requestErro = c.reason.requestError) !== null && _c$reason$requestErro !== void 0 && _c$reason$requestErro.errors && Array.isArray(c.reason.requestError.errors)) { var _result$error; // note: if APIs are going to return more details this details override will not be needed, just push as in other methods (_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.push(...c.reason.requestError.errors.map(e => ({ ...e, detail: `sub-resource "${c.reason.name}" ${e.detail}` }))); return a; } throw c.reason; }, {}); result.data = (0, _assign.default)(mainResourceResponse, subResourcesCombined); if (!((_result$error2 = result.error) !== null && _result$error2 !== void 0 && _result$error2.length)) result.error = null; // reset errors to null if none encountered log(`resolving sub-resources is complete, data received = ${!(0, _isEmpty.default)(subResourcesCombined)}, errors = ${(_result$error3 = result.error) === null || _result$error3 === void 0 ? void 0 : _result$error3.length}.`); return result; } /** * Check if resources are deleted by making a fetch call for the resources */ checkForResources(resources, sortedDefsArray) { return Promise.all(resources.map(resource => { var _resource$metadata2, _resource$metadata3, _resource$metadata3$s; const resourceDef = sortedDefsArray.find(def => { var _def$spec$scope, _resource$metadata, _resource$metadata$sc; return def.spec.kind === resource.kind && ((_def$spec$scope = def.spec.scope) === null || _def$spec$scope === void 0 ? void 0 : _def$spec$scope.kind) === ((_resource$metadata = resource.metadata) === null || _resource$metadata === void 0 ? void 0 : (_resource$metadata$sc = _resource$metadata.scope) === null || _resource$metadata$sc === void 0 ? void 0 : _resource$metadata$sc.kind); }); const scopeDef = !!((_resource$metadata2 = resource.metadata) !== null && _resource$metadata2 !== void 0 && _resource$metadata2.scope) ? sortedDefsArray.find(def => def.spec.kind === resource.metadata.scope.kind && !def.spec.scope) : undefined; const scopeName = (_resource$metadata3 = resource.metadata) === null || _resource$metadata3 === void 0 ? void 0 : (_resource$metadata3$s = _resource$metadata3.scope) === null || _resource$metadata3$s === void 0 ? void 0 : _resource$metadata3$s.name; if (resourceDef) { return this.getResourceByName({ resourceDef, resourceName: resource.name, scopeDef, scopeName }); } else return null; })); } /** * SINGLE RESOURCE CALLS */ /** * Create a single resource. * @param resources resource to create */ async createResource({ resourceDef, resource, scopeDef, scopeName, withSubResources = true, language }) { log(`createResource, spec.kind = ${resourceDef.spec.kind}, name = ${resource.name}`); const result = { data: null, error: null, pending: null, warning: false }; try { const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account, team: this.team, forceGetAuthInfo: this.forceGetAuthInfo }); const version = resource.apiVersion === undefined ? (0, _utils.getLatestServedAPIVersion)(resourceDef) : resource.apiVersion; const urlPath = this.buildResourceUrlPath({ resourceDef, scopeDef, scopeName, version }); const response = await service.post(urlPath, (0, _utils.sanitizeMetadata)(resource)); if (!resource.name) { log('createResource, resource does not have a logical name'); result.warning = true; } const pendingSubResources = await this.generateSubResourcesRequests({ resource, resourceName: response.name, resourceDef, scopeDef, scopeName, version, createAction: true, language }); log(`createResource, pendingSubResources = ${pendingSubResources === null || pendingSubResources === void 0 ? void 0 : pendingSubResources.length}`); if (withSubResources) { const { data: subResData, error: subResError } = await this.resolveSubResourcesRequests(response, pendingSubResources); result.data = subResData; result.error = subResError; } else { result.data = response; result.pending = pendingSubResources; } } catch (e) { log('createResource, error: ', e); // expecting only a valid ApiServer error response here // re-throw if something different, so it should be handled by command's catch block. if (e.errors && Array.isArray(e.errors)) { result.error = e.errors; } else throw e; } if (!!result.data) { result.data = (0, _utils.sanitizeMetadata)(result.data); } return result; } /** * Update a single resource. * @param resources resource to create */ async updateResource({ resourceDef, resource, scopeDef, scopeName, subResourceName, language }) { log(`updateResource, spec.kind = ${resourceDef.spec.kind}, name = ${resource.name}`); const result = { data: null, error: null, pending: null }; const canUpdateMainResource = !language && !subResourceName; const version = resource.apiVersion === undefined ? (0, _utils.getLatestServedAPIVersion)(resourceDef) : resource.apiVersion; if (canUpdateMainResource) { try { const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account, team: this.team }); const urlPath = this.buildResourceUrlPath({ resourceDef, resourceName: resource.name, scopeDef, scopeName, version }); result.data = await service.put(urlPath, (0, _utils.sanitizeMetadata)(resource)); } catch (e) { log('updateResource, error', e); // expecting only a valid ApiServer error response here // re-throw if something different, so it should be handled by command's catch block. if (e.errors && Array.isArray(e.errors)) { result.error = e.errors; } else { throw e; } } } result.pending = await this.generateSubResourcesRequests({ resource, resourceName: resource.name, subResourceName, resourceDef, scopeDef, scopeName, version, createAction: false, language }); if (!result.data && !result.pending && subResourceName) { result.error = [{ status: 0, title: '', detail: `sub-resource "${subResourceName}" not found.`, meta: { instanceId: '', tenantId: '', authenticatedUserId: '', transactionId: '' } }]; } if (result.data) { result.data = (0, _utils.sanitizeMetadata)(result.data); } return result; } /** * Update sub resource on the resource. * @param resources resource to be updated * @param subResourceName sub resource name to be updated */ async updateSubResource({ resourceDef, resource, subResourceName, scopeDef, scopeName }) { log(`updateSubResource, spec.kind = ${resourceDef.spec.kind}, name = ${resource.name}`); const result = { data: null, error: null, pending: null }; const version = (0, _utils.getLatestServedAPIVersion)(resourceDef); try { var _resourceDef$spec$sub3, _resourceDef$spec$sub4; const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account, team: this.team }); const knownSubResourcesNames = (_resourceDef$spec$sub3 = (_resourceDef$spec$sub4 = resourceDef.spec.subResources) === null || _resourceDef$spec$sub4 === void 0 ? void 0 : _resourceDef$spec$sub4.names) !== null && _resourceDef$spec$sub3 !== void 0 ? _resourceDef$spec$sub3 : []; const foundSubResources = (0, _pickBy.default)(resource, (_, key) => subResourceName == key && knownSubResourcesNames.includes(key)); const resourceName = resource.name; const urlPath = this.buildResourceUrlPath({ resourceDef, resourceName, scopeDef, scopeName, version }); service.put(`${urlPath}/${subResourceName}?fields=${subResourceName}`, { [subResourceName]: foundSubResources[subResourceName] }); } catch (e) { log('updateSubResource, error', e); // expecting only a valid ApiServer error response here // re-throw if something different, so it should be handled by command's catch block. if (e.errors && Array.isArray(e.errors)) { result.error = e.errors; } else throw e; } if (!!result.data) result.data = (0, _utils.sanitizeMetadata)(result.data); return result; } /** * Delete a resources by name. * @param opts = { * resourceDef - required, resource definition * resourceName - required * scopeDef - optional scope resource definition, used only if @param opts.scopeName provided too * scopeName - optional name of the scope, used only if scoped @param opts.scopeDef provided too * version - apis version (using alpha1 by default currently) * wait - if provided, a followup GET call will be executed to confirm if the resource removed. * } */ async deleteResourceByName({ resourceDef, resourceName, scopeDef, scopeName, wait, forceDelete = false, resourceAPIVersion }) { log(`deleteResourceByName, spec.kind = ${resourceDef.spec.kind}, name = ${resourceName}, scope.kind = ${scopeDef === null || scopeDef === void 0 ? void 0 : scopeDef.spec.kind}, scope.name = ${scopeName}`); const result = { data: null, error: null }; const version = resourceAPIVersion === undefined ? (0, _utils.getLatestServedAPIVersion)(resourceDef) : resourceAPIVersion; try { const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account, team: this.team, forceGetAuthInfo: this.forceGetAuthInfo }); const urlPath = this.buildResourceUrlPath({ resourceDef, resourceName, scopeDef, scopeName, version, forceDelete }); const response = await service.delete(urlPath); // note: delete "response" value from api-server is translated to an empty string currently. // If its true, constructing a simple representation from provided data (definition, name, scope name) // and manually set it as the "data" key. result.data = response === '' ? (0, _utils.buildGenericResource)({ resourceDef, resourceName, scopeName }) : response; if (wait) { await new Promise(resolve => setTimeout(async () => { const res = await this.getResourceByName({ resourceDef, resourceName, scopeDef, scopeName }); if (!!res.data) { result.data = null; result.error = [{ detail: 'resource has not been deleted yet.', status: 0 }]; } resolve({}); }, _types.WAIT_TIMEOUT)); } } catch (e) { log('deleteResourceByName, error: ', e); // expecting only a valid ApiServer error response here // re-throw if something different so it should be handled by command's catch block. if (e.errors && Array.isArray(e.errors)) { result.error = e.errors; } else throw e; } return result; } /** * Get resources count. * @param opts = { * resourceDef - required, resource definition * resourceName - optional, resource name * scopeDef - optional scope resource definition, used only if @param opts.scopeName provided too * scopeName - optional name of the scope, used only if scoped @param opts.scopeDef provided too * query - Optional RSQL query filter * } */ async getResourceCount({ resourceDef, resourceName, scopeDef, scopeName, query }) { const version = (0, _utils.getLatestServedAPIVersion)(resourceDef); try { const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account, team: this.team, forceGetAuthInfo: this.forceGetAuthInfo }); const urlPath = this.buildResourceUrlPath({ resourceDef, resourceName, scopeDef, scopeName, version }); const response = await service.head(urlPath, query); return response; } catch (e) { log('getResourceCount, error: ', e); // re-throw throw e; } } /** * Get a resources list. * @param opts = { * resourceDef - required, resource definition * scopeDef - optional scope resource definition, used only if @param opts.scopeName provided too * scopeName - optional name of the scope, used only if scoped @param opts.scopeDef provided too * version - apis version (using alpha1 by default currently) * query - Optional RSQL query filter * progressListener - Optional callback invoked multiple times with download progress * } */ async getResourcesList({ resourceDef, scopeDef, scopeName, query, progressListener, expand, langDef, fieldSet }) { log(`getResourcesList, spec.kind = ${resourceDef.spec.kind}`); const version = (0, _utils.getLatestServedAPIVersion)(resourceDef); const result = { data: null, error: null }; try { const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account, team: this.team, forceGetAuthInfo: this.forceGetAuthInfo }); const urlPath = this.buildResourceUrlPath({ resourceDef, scopeDef, scopeName, version, expand, langDef, fieldSet }); const response = await service.getWithPagination(urlPath, query, 50, {}, progressListener); result.data = response; } catch (e) { log('getResourcesList, error: ', e); // expecting only a valid ApiServer error response here // re-throw if something different so it should be handled by command's catch block. if (e.errors && Array.isArray(e.errors)) { result.error = e.errors; } else throw e; } return result; } /** * Get a resources by name. * @param opts = { * resourceDef - required, resource definition * resourceName - required * scopeDef - optional scope resource definition, used only if @param opts.scopeName provided too * scopeName - optional name of the scope, used only if scoped @param opts.scopeDef provided too * version - apis version (using alpha1 by default currently) * } */ async getResourceByName({ resourceDef, resourceName, scopeDef, scopeName, expand, langDef, fieldSet, resourceVersion, embed }) { log(`getResourceByName, spec.kind = ${resourceDef.spec.kind}, name = ${resourceName}`); const version = resourceVersion === undefined ? (0, _utils.getLatestServedAPIVersion)(resourceDef) : resourceVersion; const result = { data: null, error: null }; try { const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account, team: this.team, forceGetAuthInfo: this.forceGetAuthInfo }); const urlPath = this.buildResourceUrlPath({ resourceDef, resourceName, scopeDef, scopeName, version, expand, langDef, fieldSet, embed: embed }); const response = await service.get(urlPath); result.data = response; } catch (e) { log('getResourceByName, error: ', e); // expecting only a valid ApiServer error response here // re-throw if something different so it should be handled by command's catch block. if (e.errors && Array.isArray(e.errors)) { result.error = e.errors; } else throw e; } return result; } /** * Fetch definition endpoints to get specs for available resources. * Note that only "management" group is used currently. * @returns { group1: { resources: Map, cli: Map }, group2: { ... }, groupN: { ... } } */ async getSpecs(version = ApiServerVersions.v1alpha1) { log(`get specs`); try { const specs = {}; const service = await (0, _dataService.dataService)({ baseUrl: this.baseUrl, region: this.region, account: this.account }); const groups = await service.getWithPagination(`/definitions/${version}/groups`); for (const group of groups) { let resources = []; let cli = []; const cachedGroup = _CacheController.CacheController.get(`groups-${group.name}-${version}`); let cacheUpdated = false; if (this.useCache && cachedGroup && cachedGroup.resourceVersion === group.metadata.resourceVersion) { log(`valid ${group.name}/${version} found in cache`); resources = cachedGroup.resources; cli = cachedGroup.cli; } else { log(`no valid ${group.name}/${version} found in cache or cache usage is not set`); [resources, cli] = await Promise.all([service.getWithPagination(`/definitions/${version}/groups/${group.name}/resources`), service.getWithPagination(`/definitions/${version}/groups/${group.name}/commandlines`)]); _CacheController.CacheController.set(`groups-${group.name}-${version}`, { resourceVersion: group.metadata.resourceVersion, resources, cli }); cacheUpdated = true; } specs[group.name] = { resources: new Map(), cli: new Map() }; for (const r of resources) { specs[group.name].resources.set(r.name, r); } for (const c of cli) { specs[group.name].cli.set(c.name, c); } if (cacheUpdated) _CacheController.CacheController.writeToFile(); } return specs; } catch (e) { log('get specs, error: ', e); throw e; } } /** * BULK CALLS */ /** * Bulk creation of resources. * There is no endpoint for bulk create so executing them one-by-one. Order of calls calculated by * sorting of the array of resources with "compareResourcesByKindAsc". * @param resources array of resources to create */ async bulkCreate(resources, sortedDefsMap, exitOnError = false) { log(`bulk create`); const sortedDefsArray = Array.from(sortedDefsMap.values()); const pendingSubResources = []; const bulkResult = { success: [], error: [], warning: [] }; for (const resource of resources) { var _resource$metadata7, _resource$metadata8, _resource$metadata8$s; const resourceDef = sortedDefsArray.find(def => { var _def$spec$scope2, _resource$metadata4, _resource$metadata4$s; return def.spec.kind === resource.kind && ((_def$spec$scope2 = def.spec.scope) === null || _def$spec$scope2 === void 0 ? void 0 : _def$spec$scope2.kind) === ((_resource$metadata4 = resource.metadata) === null || _resource$metadata4 === void 0 ? void 0 : (_resource$metadata4$s = _resource$metadata4.scope) === null || _resource$metadata4$s === void 0 ? void 0 : _resource$metadata4$s.kind); }); if (!resourceDef) { var _resource$metadata5, _resource$metadata5$s; let errorMessage = `No resource definition found for "kind/${resource.kind}"`; if (!!((_resource$metadata5 = resource.metadata) !== null && _resource$metadata5 !== void 0 && (_resource$metadata5$s = _resource$metadata5.scope) !== null && _resource$metadata5$s !== void 0 && _resource$metadata5$s.kind)) { var _resource$metadata6, _resource$metadata6$s; errorMessage += ` in the scope "${(_resource$metadata6 = resource.metadata) === null || _resource$metadata6 === void 0 ? void 0 : (_resource$metadata6$s = _resource$metadata6.scope) === null || _resource$metadata6$s === void 0 ? void 0 : _resource$metadata6$s.kind}".`; } else { errorMessage += ' with no scope.'; } bulkResult.error.push({ name: resource.name || 'Unknown name', kind: resource.kind, error: new Error(errorMessage) }); continue; } const scopeDef = !!((_resource$metadata7 = resource.metadata) !== null && _resource$metadata7 !== void 0 && _resource$metadata7.scope) ? sortedDefsArray.find(def => def.spec.kind === resource.metadata.scope.kind && !def.spec.scope) : undefined; const scopeName = (_resource$metadata8 = resource.metadata) === null || _resource$metadata8 === void 0 ? void 0 : (_resource$metadata8$s = _resource$metadata8.scope) === null || _resource$metadata8$s === void 0 ? void 0 : _resource$metadata8$s.name; const res = await this.createResource({ resource, resourceDef, scopeDef, scopeName }); if (res.data && !res.error) { // note: bulk operation requires creation of sub-resources after all main resources created // since a sub-resource might have a reference to another resource. if (!!res.pending) { var _res$warning; pendingSubResources.push({ mainResult: res.data, pendingCalls: res.pending, withWarning: (_res$warning = res.warning) !== null && _res$warning !== void 0 ? _res$warning : false }); } else { var _bulkResult$warning; if (res.warning) (_bulkResult$warning = bulkResult.warning) === null || _bulkResult$warning === void 0 ? void 0 : _bulkResult$warning.push(res.data);else bulkResult.success.push(res.data); } } else if (res.error) { for (const nextError of res.error) { bulkResult.error.push({ name: resource.name || 'Unknown name', kind: resource.kind, error: nextError }); } if (exitOnError) { return bulkResult; } } } // creating sub-resources for (const p of pendingSubResources) { const subResResult = await this.resolveSubResourcesRequests(p.mainResult, p.pendingCalls); if (subResResult.data && !subResResult.error) { var _bulkResult$warning2; if (p.withWarning) (_bulkResult$warning2 = bulkResult.warning) === null || _bulkResult$warning2 === void 0 ? void 0 : _bulkResult$warning2.push(subResResult.data);else bulkResult.success.push(subResResult.data); } else if (subResResult.error) { for (const nextError of subResResult.error) { bulkResult.error.push({ name: p.mainResult.name, kind: p.mainResult.kind, error: nextError }); } } } return bulkResult; } /** * Bulk creation of resources. * There is no endpoint for bulk create so executing them one-by-one. Order of calls calculated by * sorting of the array of resources with "compareResourcesByKindAsc". * @param resources array of resources to create */ async bulkCreateOrUpdate(resources, sortedDefsMap, language, subResourceName) { log(`bulk create or update`); const sortedDefsArray = Array.from(sortedDefsMap.values()); const applyResults = []; for (const resource of resources) { var _resource$metadata12, _resource$metadata13, _resource$metadata13$, _resource$name2, _singleResult$error, _applyResult$error3; const resourceDef = sortedDefsArray.find(def => { var _def$spec$scope3, _resource$metadata9, _resource$metadata9$s; return def.spec.kind === resource.kind && ((_def$spec$scope3 = def.spec.scope) === null || _def$spec$scope3 === void 0 ? void 0 : _def$spec$scope3.kind) === ((_resource$metadata9 = resource.metadata) === null || _resource$metadata9 === void 0 ? void 0 : (_resource$metadata9$s = _resource$metadata9.scope) === null || _resource$metadata9$s === void 0 ? void 0 : _resource$metadata9$s.kind); }); // the check below is already happening when loading the specs but checking again just in case. if (!resourceDef) { var _resource$metadata10, _resource$metadata10$, _resource$name; let errorMessage = `No resource definition found for "kind/${resource.kind}"`; if (!!((_resource$metadata10 = resource.metadata) !== null && _resource$metadata10 !== void 0 && (_resource$metadata10$ = _resource$metadata10.scope) !== null && _resource$metadata10$ !== void 0 && _resource$metadata10$.kind)) { var _resource$metadata11, _resource$metadata11$; errorMessage += ` in the scope "${(_resource$metadata11 = resource.metadata) === null || _resource$metadata11 === void 0 ? void 0 : (_resource$metadata11$ = _resource$metadata11.scope) === null || _resource$metadata11$ === void 0 ? void 0 : _resource$metadata11$.kind}".`; } else { errorMessage += ' with no scope.'; } applyResults.push({ error: [{ name: (_resource$name = resource.name) !== null && _resource$name !== void 0 ? _resource$name : 'Unknown name', kind: resource.kind, error: new Error(errorMessage) }] }); continue; } const scopeDef = !!((_resource$metadata12 = resource.metadata) !== null && _resource$metadata12 !== void 0 && _resource$metadata12.scope) ? sortedDefsArray.find(def => def.spec.kind === resource.metadata.scope.kind && !def.spec.scope) : undefined; const scopeName = (_resource$metadata13 = resource.metadata) === null || _resource$metadata13 === void 0 ? void 0 : (_resource$metadata13$ = _resource$metadata13.scope) === null || _resource$metadata13$ === void 0 ? void 0 : _resource$metadata13$.name; const resourceName = (_resource$name2 = resource.name) !== null && _resource$name2 !== void 0 ? _resource$name2 : 'Unknown name'; // only making getResource call if resource has a name let getResult = resource.name ? await this.getResourceByName({ resourceDef, resourceName: resource.name, scopeDef, scopeName, resourceVersion: resource.apiVersion }) : null; // Create new resources first let singleResult; const shouldCreate = !getResult || !!(getResult !== null && getResult !== void 0 && getResult.error) && getResult.error[0].status === 404; if (shouldCreate) { // Resource not found. Create a new resource. singleResult = await this.createResource({ resource, resourceDef, scopeDef, scopeName, language }); } else if (getResult.data) { // Resource found. Update the existing resource. singleResult = await this.updateResource({ resource: resource, resourceDef, scopeDef, scopeName, language, subResourceName }); } else { // Something is going wrong - more than one error in api server response, re-throw in the same // structure as ApiServerErrorResponse so renderer.anyError can pick this up. throw { errors: getResult.error }; } // Store the results of the above create/update. const applyResult = { data: singleResult.data, wasCreated: shouldCreate && !!singleResult.data, wasAutoNamed: shouldCreate && singleResult.warning, wasMainResourceChanged: !!singleResult.data, error: [] }; (_singleResult$error = singleResult.error) === null || _singleResult$error === void 0 ? void 0 : _singleResult$error.forEach(nextError => { var _applyResult$error; return (_applyResult$error = applyResult.error) === null || _applyResult$error === void 0 ? void 0 : _applyResult$error.push({ name: resourceName, kind: resource.kind, error: nextError }); }); applyResults.push(applyResult); // Create or update any pending subresources. if (singleResult.pending) { var _singleResult$data, _subResResult$error; const pendingData = (_singleResult$data = singleResult.data) !== null && _singleResult$data !== void 0 ? _singleResult$data : (0, _utils.sanitizeMetadata)((0, _utils.buildGenericResource)({ resourceName: resourceName, resourceDef: resourceDef, scopeName: scopeName })); const subResResult = await this.resolveSubResourcesRequests(pendingData, singleResult.pending); if (subResResult.data) { applyResult.data = subResResult.data; } applyResult.updatedSubResourceNames = subResResult.updatedSubResourceNames; (_subResResult$error = subResResult.error) === null || _subResResult$error === void 0 ? void 0 : _subResResult$error.forEach(error => { var _applyResult$error2; return (_applyResult$error2 = applyResult.error) === null || _applyResult$error2 === void 0 ? void 0 : _applyResult$error2.push({ name: resourceName, kind: resource.kind, error: error }); }); } // Delete the result's error array if it is empty. if (!((_applyResult$error3 = applyResult.error) !== null && _applyResult$error3 !== void 0 && _applyResult$error3.length)) { delete applyResult.error; } } return applyResults; } /** * Bulk deletion of resources. * Order of calls calculated by sorting of the array of resources with "compareResourcesByKindDesc". * @param resources array of resources to create */ async bulkDelete(resources, sortedDefsMap, wait, forceDelete) { log(`bulk delete`); const sortedDefsArray = Array.from(sortedDefsMap.values()); const bulkResult = { success: [], error: [] }; for (const resource of resources) { try { var _resource$metadata15, _resource$metadata16, _resource$metadata16$; const resourceDef = sortedDefsArray.find(def => { var _def$spec$scope4, _resource$metadata14, _resource$metadata14$; return def.spec.kind === resource.kind && ((_def$spec$scope4 = def.spec.scope) === null || _def$spec$scope4 === void 0 ? void 0 : _def$spec$scope4.kind) === ((_resource$metadata14 = resource.metadata) === null || _resource$metadata14 === void 0 ? void 0 : (_resource$metadata14$ = _resource$metadata14.scope) === null || _resource$metadata14$ === void 0 ? void 0 : _resource$metadata14$.kind); }); const scopeDef = !!((_resource$metadata15 = resource.metadata) !== null && _resource$metadata15 !== void 0 && _resource$metadata15.scope) ? sortedDefsArray.find(def => def.spec.kind === resource.metadata.scope.kind && !def.spec.scope) : undefined; const scopeName = (_resource$metadata16 = resource.metadata) === null || _resource$metadata16 === void 0 ? void 0 : (_resource$metadata16$ = _resource$metadata16.scope) === null || _resource$metadata16$ === void 0 ? void 0 : _resource$metadata16$.name; if (!resourceDef) { var _resource$metadata17, _resource$metadata17$; let errorMessage = `No resource definition found for "kind/${resource.kind}"`; if (!!((_resource$metadata17 = resource.metadata) !== null && _resource$metadata17 !== void 0 && (_resource$metadata17$ = _resource$metadata17.scope) !== null && _resource$metadata17$ !== void 0 && _resource$metadata17$.kind)) { var _resource$metadata18, _resource$metadata18$; errorMessage += ` in the scope "${(_resource$metadata18 = resource.metadata) === null || _resource$metadata18 === void 0 ? void 0 : (_resource$metadata18$ = _resource$metadata18.scope) === null || _resource$metadata18$ === void 0 ? void 0 : _resource$metadata18$.kind}".`; } else { errorMessage += ' with no scope.'; } bulkResult.error.push({ name: resource.name || 'Unknown name', kind: resource.kind, error: new Error(errorMessage) }); continue; } const res = await this.deleteResourceByName({ resourceName: resource.name, resourceDef, scopeDef, scopeName, forceDelete, resourceAPIVersion: resource.apiVersion }); if (res.error) { for (const nextError of res.error) { bulkResult.error.push({ name: resource.name, kind: resource.kind, error: nextError }); } } else { // deleteResourceByName is constructing a resource representation using buildGenericResource as res.data, // but provided in a file resources might contain more data so using them currently bulkResult.success.push(resource); } } catch (e) { // expecting only a valid ApiServer error response here // re-throw if something different so it should be handled by command's catch block. if (e.errors && Array.isArray(e.errors)) { for (const nextError of e.errors) { bulkResult.error.push({ name: resource.name, kind: resource.kind, error: nextError }); } } else { throw e; } } } if (wait) { let pendingResources = []; pendingResources = await this.checkForResources(resources, sortedDefsArray); const pendingDeletingResource = pendingResources.some(res => res === null || res === void 0 ? void 0 : res.data); if (pendingDeletingResource) { setTimeout(async () => { pendingResources = await this.checkForResources(resources, sortedDefsArray); }, _types.WAIT_TIMEOUT); const stillPending = pendingResources.some(res => res === null || res === void 0 ? void 0 : res.data); if (stillPending) { const pendingResNames = pendingResources.map(res => { var _res$data; return res === null || res === void 0 ? void 0 : (_res$data = res.data) === null || _res$data === void 0 ? void 0 : _res$data.name; }); bulkResult.success.forEach((res, index) => pendingResNames.includes(res.name) && bulkResult.success.splice(index, 1)); pendingResources.forEach(res => { if (res !== null && res !== void 0 && res.data) { bulkResult.error.push({ ...res.data, error: { detail: 'Not deleted yet.' } }); } }); } else return bulkResult; } else return bulkResult; } return bulkResult; } } exports.ApiServerClient = ApiServerClient;