@axway/axway-central-cli
Version:
Manage APIs, services and publish to the Amplify Marketplace
476 lines (450 loc) • 21.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FormatString = FormatString;
exports.KeyValueMapToNameValueArray = KeyValueMapToNameValueArray;
exports.ValueFromKey = ValueFromKey;
exports.parseScopeParam = exports.loadAndVerifySpecs = exports.loadAndVerifyApigeeXCredentialFile = exports.isWindows = exports.isValidJson = exports.isApiServerErrorType = exports.isApiServerErrorResponseType = exports.hbsCompare = exports.getResourceDefinition = exports.getLatestServedAPIVersion = exports.getConfig = exports.createLanguageSubresourceNames = exports.configFile = exports.compareResourcesByKindDesc = exports.compareResourcesByKindAsc = exports.buildTemplate = exports.buildGenericResource = void 0;
exports.sanitizeMetadata = sanitizeMetadata;
exports.writeToFile = exports.writeTemplates = exports.wait = exports.verifyScopeParam = exports.verifyFile = exports.transformSimpleFilters = void 0;
var _fsExtra = require("fs-extra");
var _handlebars = _interopRequireDefault(require("handlebars"));
var _jsYaml = require("js-yaml");
var _os = require("os");
var _path = require("path");
var _ApiServerClient = require("./ApiServerClient");
var _CompositeError = require("./CompositeError");
var _types = require("./types");
var _chalk = _interopRequireDefault(require("chalk"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const isWindows = exports.isWindows = /^win/.test(process.platform);
const configFile = exports.configFile = (0, _path.join)((0, _os.homedir)(), '.axway', 'central.json');
const getConfig = async () => !(0, _fsExtra.existsSync)(configFile) ? Promise.resolve({}) : await (0, _fsExtra.readJson)(configFile);
exports.getConfig = getConfig;
const verifyFile = specFilePath => {
let stats;
let fileExtension;
try {
stats = (0, _fsExtra.lstatSync)(specFilePath);
fileExtension = (0, _path.extname)(specFilePath);
} catch (e) {
throw new Error(`Couldn't find the definition file: ${specFilePath}`);
}
if (!stats.isFile()) {
throw new Error(`Couldn't load the definition file: ${specFilePath}`);
} else if (stats.size >= _types.MAX_FILE_SIZE) {
throw new Error(`File size too large`);
} else if (fileExtension !== '.yaml' && fileExtension !== '.yml' && fileExtension !== '.json') {
throw new Error(`File extension is invalid, please provide '.yaml' or '.yml' or '.json' file`);
}
};
exports.verifyFile = verifyFile;
const writeToFile = (path, data) => {
try {
(0, _fsExtra.writeFileSync)(path, data);
} catch (e) {
// if parser is failing, rethrow with our own error
throw new Error(`Error while writing the yaml file to: ${path}`);
}
};
/**
* Checks if the passed item can be converted to a JSON or is a valid JSON object.
* @param item item to check
* @returns true if the item can be converted, false otherwise.
*/
exports.writeToFile = writeToFile;
const isValidJson = item => {
let parsedItem = typeof item !== 'string' ? JSON.stringify(item) : item;
try {
parsedItem = JSON.parse(parsedItem);
} catch (e) {
return false;
}
return typeof parsedItem === 'object' && item !== null;
};
/**
* Loads and parse file from path, accepts JSON and YAML files. Also completing validation on "kind" values.
* @param specFilePath file path
* @param allowedKinds array of allowed "kind" values
*/
exports.isValidJson = isValidJson;
const loadAndVerifySpecs = async (specFilePath, allowedKinds, skipKindCheck) => {
// Load the given JSON or YAML file.
let docs = [];
let isMissingName = false;
try {
docs = (0, _jsYaml.loadAll)(await (0, _fsExtra.readFile)(specFilePath, 'utf8'));
} catch (e) {
throw new Error(e.reason && e.reason.includes('null byte') ? `File encoding is invalid, please make sure it is using UTF-8` : `File content is invalid.`);
}
// if user pass an array of json objects, docs const will have nested array, workaround for this:
if ((0, _path.extname)(specFilePath) === '.json' && docs.length === 1 && Array.isArray(docs[0])) {
docs = docs[0];
}
// Do not continue if given an empty file.
if (!docs.length) throw new Error(`File is empty.`);
// Validate all entries in the file.
const errors = [];
const createErrorPrefix = (index, kind, name) => {
return `Entry ${index + 1}, "${kind}/${name || 'Unknown name'}"`;
};
for (let index = 0; index < docs.length; index++) {
// Verify document is defined/valid.
const doc = docs[index];
if (typeof doc !== 'object' || !doc) {
errors.push(new Error(`${createErrorPrefix(index)}: Entry format is invalid.`));
continue;
}
// Set a flag if at least 1 name is messing in file.
if (!doc.name) {
isMissingName = true;
}
if (!skipKindCheck) {
// Validate resource kind.
if (!doc.kind) {
errors.push(Error(`${createErrorPrefix(index, doc.kind, doc.name)}: The "kind" field is missing.` + `\nCurrently supported values are (case sensitive): ${[...allowedKinds.values()].join(', ')}`));
} else if (!allowedKinds.has(doc.kind)) {
errors.push(new Error(`${createErrorPrefix(index, doc.kind, doc.name)}: Kind "${doc.kind}" is unsupported.` + `\nCurrently supported values are (case sensitive): ${[...allowedKinds.values()].join(', ')}`));
}
}
// TODO: Validate "metadata.scope.kind" if available. Requires DefinitionManager.getSortedKindsMap() result.
}
if (errors.length > 0) {
throw new _CompositeError.CompositeError(errors);
}
// File's contents appears to be valid. Return loaded info.
return {
docs,
isMissingName
};
};
/**
* Generate a GenericResource instance from resource definition, resource name, and scope name. Used
* in some rendering logic for the "delete" command.
* Note that generated metadata includes only scope info.
* @param {ResourceDefinition} resourceDef resource definition
* @param {string} resourceName resource name
* @param {string} scopeName optional scope name
* @returns {GenericResource} generic resource representation
*/
exports.loadAndVerifySpecs = loadAndVerifySpecs;
const buildGenericResource = ({
resourceDef,
resourceName,
scopeName
}) => {
if (resourceName) {
var _resourceDef$spec, _resourceDef$spec2, _resourceDef$spec2$sc;
return {
apiVersion: resourceDef === null || resourceDef === void 0 ? void 0 : resourceDef.apiVersion,
group: resourceDef === null || resourceDef === void 0 ? void 0 : resourceDef.group,
title: resourceName,
name: resourceName,
kind: resourceDef === null || resourceDef === void 0 ? void 0 : resourceDef.spec.kind,
attributes: {},
tags: [],
metadata: resourceDef !== null && resourceDef !== void 0 && (_resourceDef$spec = resourceDef.spec) !== null && _resourceDef$spec !== void 0 && _resourceDef$spec.scope && scopeName ? {
scope: {
kind: resourceDef === null || resourceDef === void 0 ? void 0 : (_resourceDef$spec2 = resourceDef.spec) === null || _resourceDef$spec2 === void 0 ? void 0 : (_resourceDef$spec2$sc = _resourceDef$spec2.scope) === null || _resourceDef$spec2$sc === void 0 ? void 0 : _resourceDef$spec2$sc.kind,
name: scopeName
}
// note: forced conversion here only because using generated resources for rendering simple text
} : undefined,
spec: {}
};
} else {
var _resourceDef$spec3, _resourceDef$spec4, _resourceDef$spec4$sc;
return {
apiVersion: resourceDef === null || resourceDef === void 0 ? void 0 : resourceDef.apiVersion,
group: resourceDef === null || resourceDef === void 0 ? void 0 : resourceDef.group,
kind: resourceDef === null || resourceDef === void 0 ? void 0 : resourceDef.spec.kind,
attributes: {},
tags: [],
metadata: resourceDef !== null && resourceDef !== void 0 && (_resourceDef$spec3 = resourceDef.spec) !== null && _resourceDef$spec3 !== void 0 && _resourceDef$spec3.scope && scopeName ? {
scope: {
kind: resourceDef === null || resourceDef === void 0 ? void 0 : (_resourceDef$spec4 = resourceDef.spec) === null || _resourceDef$spec4 === void 0 ? void 0 : (_resourceDef$spec4$sc = _resourceDef$spec4.scope) === null || _resourceDef$spec4$sc === void 0 ? void 0 : _resourceDef$spec4$sc.kind,
name: scopeName
}
// note: forced conversion here only because using generated resources for rendering simple text
} : undefined,
spec: {}
};
}
};
/**
* Returns true if error object is of type ApiServerError
* @param err error object to check
*/
exports.buildGenericResource = buildGenericResource;
const isApiServerErrorType = err => {
const cast = err;
return !!cast.status && !!cast.title && !!cast.detail;
};
/**
* Returns true if error object is of type ApiServerErrorResponse
* @param err error object to check
*/
exports.isApiServerErrorType = isApiServerErrorType;
const isApiServerErrorResponseType = err => {
const cast = err;
return !!cast.errors && Array.isArray(cast.errors);
};
/**
* Wrapper to return a compare function for sorting by "kind" value in array of resources in ascending
* order. Order based on "sortedKindValues" array.
* @param {Array<ResourceDefinition>} sortedResourceDefs array of ResourceDefinitions defining a required order of the resources.
* @returns {Function} Array sorting function
*/
exports.isApiServerErrorResponseType = isApiServerErrorResponseType;
const compareResourcesByKindAsc = sortedResourceDefs => (a, b) => sortedResourceDefs.findIndex(def => {
var _def$spec$scope, _a$metadata, _a$metadata$scope;
return def.spec.kind === a.kind && ((_def$spec$scope = def.spec.scope) === null || _def$spec$scope === void 0 ? void 0 : _def$spec$scope.kind) === ((_a$metadata = a.metadata) === null || _a$metadata === void 0 ? void 0 : (_a$metadata$scope = _a$metadata.scope) === null || _a$metadata$scope === void 0 ? void 0 : _a$metadata$scope.kind);
}) - sortedResourceDefs.findIndex(def => {
var _def$spec$scope2, _b$metadata, _b$metadata$scope;
return def.spec.kind === b.kind && ((_def$spec$scope2 = def.spec.scope) === null || _def$spec$scope2 === void 0 ? void 0 : _def$spec$scope2.kind) === ((_b$metadata = b.metadata) === null || _b$metadata === void 0 ? void 0 : (_b$metadata$scope = _b$metadata.scope) === null || _b$metadata$scope === void 0 ? void 0 : _b$metadata$scope.kind);
});
/**
* Wrapper to return a compare function for sorting by "kind" value in array of resources in descending
* order. Order based on "sortedKindValues" array.
* @param {Array<ResourceDefinition>} sortedResourceDefs array of ResourceDefinitions defining a required order of the resources.
* @returns {Function} Array sorting function
*/
exports.compareResourcesByKindAsc = compareResourcesByKindAsc;
const compareResourcesByKindDesc = sortedResourceDefs => (a, b) => sortedResourceDefs.findIndex(def => {
var _def$spec$scope3, _b$metadata2, _b$metadata2$scope;
return def.spec.kind === b.kind && ((_def$spec$scope3 = def.spec.scope) === null || _def$spec$scope3 === void 0 ? void 0 : _def$spec$scope3.kind) === ((_b$metadata2 = b.metadata) === null || _b$metadata2 === void 0 ? void 0 : (_b$metadata2$scope = _b$metadata2.scope) === null || _b$metadata2$scope === void 0 ? void 0 : _b$metadata2$scope.kind);
}) - sortedResourceDefs.findIndex(def => {
var _def$spec$scope4, _a$metadata2, _a$metadata2$scope;
return def.spec.kind === a.kind && ((_def$spec$scope4 = def.spec.scope) === null || _def$spec$scope4 === void 0 ? void 0 : _def$spec$scope4.kind) === ((_a$metadata2 = a.metadata) === null || _a$metadata2 === void 0 ? void 0 : (_a$metadata2$scope = _a$metadata2.scope) === null || _a$metadata2$scope === void 0 ? void 0 : _a$metadata2$scope.kind);
});
/**
* Api-server returns the "resourceVersion" in metadata object as a counter for resource updates.
* If a user will send this key in the payload it will throw an error so using this helper to sanitizing metadata on
* the updates.
* @param doc resource data
* @returns {GenericResource} resource data without metadata.resourceVersion key
*/
exports.compareResourcesByKindDesc = compareResourcesByKindDesc;
function sanitizeMetadata(doc) {
var _doc$metadata;
if (doc !== null && doc !== void 0 && (_doc$metadata = doc.metadata) !== null && _doc$metadata !== void 0 && _doc$metadata.resourceVersion) {
delete doc.metadata.resourceVersion;
}
return doc;
}
const buildTemplate = (templateFunc, input) => {
const template = _handlebars.default.compile(templateFunc(), {
noEscape: true
});
return template(input);
};
/**
* Takes in a set of parameters (values), compiles a string utilizing the provided handlebars templating function (templateFunc)
* and the buildTemplate function with those parameters, and finally writes the output string to a file of the name that was provided (fileName).
* @param {string} fileName
* @param {object} values
* @param {()=>string} templateFunc
*/
exports.buildTemplate = buildTemplate;
const writeTemplates = (fileName, values, templateFunc) => {
const data = buildTemplate(templateFunc, values);
writeToFile(fileName, data);
};
/**
* helper function to extend the handlebars built-in helpers functionality to add a way to compare equality of two strings or numbers
* @param {string|number} lvalue
* @param {string|number} rvalue
* @param {{fn:(} options
*/
exports.writeTemplates = writeTemplates;
const hbsCompare = () => {
_handlebars.default.registerHelper('compare', (context, lvalue, rvalue, options) => {
let operator = options.hash.operator || '===';
let operators = {
'==': (l, r) => {
return l == r;
},
'===': (l, r) => {
return l === r;
},
'!=': (l, r) => {
return l != r;
},
'<': (l, r) => {
return l < r;
},
'>': (l, r) => {
return l > r;
},
'<=': (l, r) => {
return l <= r;
},
'>=': (l, r) => {
return l >= r;
},
typeof: (l, r) => {
return typeof l == r;
}
};
var result = operators[operator](lvalue, rvalue);
if (result) {
return options.fn(context);
} else {
return options.inverse(context);
}
});
};
/**
* Parse and verify scope param, returns undefined if param is undefined. Throws an error if "Kind" is unknown.
* @param scopeParam raw scope param value
* @returns {ParsedScopeParam | undefined}
*/
exports.hbsCompare = hbsCompare;
const parseScopeParam = scopeParam => {
if (!scopeParam) return undefined;
let sp = scopeParam.toString();
if (sp.indexOf('/') === -1) return {
name: scopeParam
};else {
const name = sp.substring(scopeParam.indexOf('/') + 1);
const kind = sp.substring(0, scopeParam.indexOf('/'));
if (!name.length || !kind.length) throw Error(`invalid scope (-s/--scope) parameter value.` + `\nPlease use "--scope <scope kind>/<scope name>" or "--scope <scope name>" formats.`);
return {
name,
kind
};
}
};
/**
* Verify parsed scope param:
* 1. scope kind should be known.
* 2. scope kind should match at least one in a resource "scoped" definitions ("non-scoped" definition will be ignored).
* @param allKinds all available kinds.
* @param defs resource definitions where at least one should match the scope kind if some "scoped" resources are there.
* @param scopeParam parsed scope param.
*/
exports.parseScopeParam = parseScopeParam;
const verifyScopeParam = (allKinds, defs, scopeParam) => {
const allowedScopeKinds = new Set();
defs.forEach(defs => !!defs.scope && allowedScopeKinds.add(defs.scope.spec.kind));
if (scopeParam !== null && scopeParam !== void 0 && scopeParam.kind) {
if (!allKinds.has(scopeParam.kind)) throw new Error(`unsupported kind value "${scopeParam.kind}" in the "--scope" param.` + `\nCurrently supported values are (case sensitive): ${[...allKinds.values()].join(', ')}`);
if (allowedScopeKinds.size > 0 && !allowedScopeKinds.has(scopeParam.kind)) throw Error(`scope kind "${scopeParam.kind}" is invalid.` + `\n"${defs[0].resource.spec.kind}" resource might exist in the following scopes: ${[...allowedScopeKinds.values()].join(', ')}`);
}
};
/**
* Transforms simple filters(title, attribute, tag) into an RSQL-formatted query string the GET API supports.
* @param {string} title The title the user wants to filter the resource list by.
* @param {string} attribute The attribute(key=value) the user wants to filter the resource list by.
* @param {string} tag The tag the user wants to filter the resource list by.
* @returns {string} transformedFilter, the RSQL formatted query string
*/
exports.verifyScopeParam = verifyScopeParam;
const transformSimpleFilters = (title, attribute, tag) => {
const titleFilter = title ? `title=='*${title}*'` : '';
const attributeKey = attribute && attribute.split('=')[0];
const attributeValue = attribute && attribute.split('=')[1];
const attributeFilter = attributeKey && attributeValue ? `attributes.${attributeKey}==${attributeValue}` : '';
const tagFilter = tag ? `tags==${tag}` : '';
const formattedFilter = `${titleFilter && `${titleFilter};`}${attributeFilter && `${attributeFilter};`}${tagFilter}`;
const transformedFilter = formattedFilter.charAt(formattedFilter.length - 1) === ';' ? formattedFilter.slice(0, -1) : formattedFilter;
return transformedFilter;
};
exports.transformSimpleFilters = transformSimpleFilters;
function FormatString(str, ...val) {
for (let index = 0; index < val.length; index++) {
str = str.replace(`{${index}}`, val[index]);
}
return str;
}
/**
* Loads and parse file from path, accepts JSON file. Also validates additional details.
* @param credentialFilePath file path
*/
const loadAndVerifyApigeeXCredentialFile = async credentialFilePath => {
// Load the given JSON file.
let fileInfo = '';
try {
fileInfo = await (0, _fsExtra.readJson)(credentialFilePath);
} catch (e) {
throw new Error(e.reason);
}
// Do not continue if given an empty file.
if (fileInfo === '') throw new Error(`File is empty.`);
// TODO: Validate additional details if needed
// Return loaded info.
return fileInfo;
};
exports.loadAndVerifyApigeeXCredentialFile = loadAndVerifyApigeeXCredentialFile;
function KeyValueMapToNameValueArray(m) {
let array = [];
m.forEach((value, key) => {
array.push({
name: key,
value: value
});
});
if (array.length == 0) {
return undefined;
}
return array;
}
const createLanguageSubresourceNames = langCode => {
const langCodeArr = langCode.split(',');
let langSubresourceNamesArr = ['languages'];
let languageTypesArr = [];
Object.keys(_types.LanguageTypes).forEach(key => languageTypesArr.push(ValueFromKey(_types.LanguageTypes, key)));
langCodeArr.forEach(langCode => {
if (langCode.trim() != '') {
if (!languageTypesArr.includes(langCode)) {
console.log(_chalk.default.yellow(`\n\'${langCode}\' language code is not supported, hence create/update cannot be performed on \'languages-${langCode}\. Allowed language codes: ${_types.LanguageTypes.French} | ${_types.LanguageTypes.German} | ${_types.LanguageTypes.US} | ${_types.LanguageTypes.Portugese}.'`));
} else {
langSubresourceNamesArr.push(`languages-${langCode.trim()}`);
}
}
});
return langSubresourceNamesArr;
};
exports.createLanguageSubresourceNames = createLanguageSubresourceNames;
function ValueFromKey(stringEnum, key) {
for (const k of Object.values(stringEnum)) {
if (k === stringEnum[key]) return k;
}
return undefined;
}
const getLatestServedAPIVersion = resourceDef => {
let apiVersions = resourceDef.spec.apiVersions;
if (apiVersions && apiVersions.length > 0) {
for (const version of apiVersions) {
if (version.served && !version.deprecated) {
return version.name;
}
}
return _ApiServerClient.ApiServerVersions.v1alpha1;
}
// if the apiVersions are not set on the resource definition, fallback to v1alpha1 version
return _ApiServerClient.ApiServerVersions.v1alpha1;
};
/**
* Wait for the given milliseconds
* @param {number} ms The given time to wait
* @returns {Promise} A fulfilled promise after the given time has passed
*/
exports.getLatestServedAPIVersion = getLatestServedAPIVersion;
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
/**
* Fetch resource definition of given kind and scope kind if exists
* @param {ResourceDefinition[]} sortedDefsArray The given time to wait
* @param {Kind} kind The kind of the resource
* @param {Kind} scopeKind The scope kind of the resource
*/
exports.wait = wait;
const getResourceDefinition = async (sortedDefsArray, kind, scopeKind) => {
const resourceDefinition = sortedDefsArray.find(def => {
var _def$spec$scope5;
return scopeKind ? def.spec.kind === kind && ((_def$spec$scope5 = def.spec.scope) === null || _def$spec$scope5 === void 0 ? void 0 : _def$spec$scope5.kind) === scopeKind : def.spec.kind === kind;
});
return resourceDefinition;
};
exports.getResourceDefinition = getResourceDefinition;