@axway/axway-central-cli
Version:
Manage APIs, services and publish to the Amplify Marketplace
1,109 lines (1,084 loc) • 45.8 kB
JavaScript
"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;