@zowe/core-for-zowe-sdk
Version:
Core libraries shared by Zowe SDK packages
282 lines • 15.3 kB
JavaScript
;
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Services = void 0;
const imperative_1 = require("@zowe/imperative");
const ApimlConstants_1 = require("./ApimlConstants");
const JSONC = require("comment-json");
/**
* Class to handle listing services on APIML gateway.
* @export
* @class Services
*/
class Services {
/**
* Forms a list of APIML service attributes needed to query APIML for every
* REST service for every loaded command group. This information can later
* be used to create every connection profile required for every loaded
* command group.
*
* @throws {ImperativeError} When Imperative.init() has not been called
* before getPluginApimlConfigs().
* @returns The APIML service attributes needed to query APIML.
*/
static getPluginApimlConfigs() {
const apimlConfigs = [];
imperative_1.ImperativeExpect.toNotBeNullOrUndefined(imperative_1.ImperativeConfig.instance.loadedConfig, "Imperative.init() must be called before getPluginApimlConfigs()");
// Get the APIML configs from the loaded imperative config
for (const apimlConfig of imperative_1.ImperativeConfig.instance.loadedConfig.apimlConnLookup || []) {
apimlConfigs.push(Object.assign(Object.assign({}, apimlConfig), { connProfType: apimlConfig.connProfType || imperative_1.ImperativeConfig.instance.loadedConfig.profiles[0].type, pluginName: imperative_1.ImperativeConfig.instance.hostPackageName }));
}
// Load APIML configs from all plugins
for (const pluginCfgProps of imperative_1.PluginManagementFacility.instance.allPluginCfgProps) {
for (const apimlConfig of pluginCfgProps.impConfig.apimlConnLookup || []) {
apimlConfigs.push(Object.assign(Object.assign({}, apimlConfig), { connProfType: apimlConfig.connProfType || pluginCfgProps.impConfig.profiles[0].type, pluginName: pluginCfgProps.pluginName }));
}
}
return apimlConfigs;
}
/**
* Calls the services endpoint of the APIML gateway to obtain a list of
* services that support Single Sign-On. This list is compared against a
* list of APIML service attributes defined in CLI plug-in configs. When a
* service's API ID is present in both lists, a profile info object is
* generated to store CLI profile info for connecting to that service.
* @static
* @param session Session with APIML connection info
* @param configs APIML service attributes defined by CLI plug-ins
* @throws {ImperativeError} When session object is undefined or missing
* authentication info, or the REST request fails
* @returns List of objects containing profile info for APIML services
* @memberof Services
*/
static getServicesByConfig(session, configs) {
return __awaiter(this, void 0, void 0, function* () {
imperative_1.Logger.getAppLogger().trace("Services.getByConfig()");
imperative_1.ImperativeExpect.toNotBeNullOrUndefined(session, "Required session must be defined");
if (session.ISession.type === "basic") {
imperative_1.ImperativeExpect.toNotBeNullOrUndefined(session.ISession.user, "User name for API ML basic login must be defined.");
imperative_1.ImperativeExpect.toNotBeNullOrUndefined(session.ISession.password, "Password for API ML basic login must be defined.");
}
else {
imperative_1.ImperativeExpect.toMatchRegExp(session.ISession.tokenType, "^apimlAuthenticationToken.*", `Token type (${session.ISession.tokenType}) for API ML token login must start with 'apimlAuthenticationToken'.`);
imperative_1.ImperativeExpect.toNotBeNullOrUndefined(session.ISession.tokenValue, "Token value for API ML token login must be defined.");
}
// Perform GET request on APIML services endpoint
const services = yield imperative_1.RestClient.getExpectJSON(session, ApimlConstants_1.ApimlConstants.SERVICES_ENDPOINT);
const ssoServices = services.filter(({ apiml }) => { var _a, _b; return (_b = (_a = apiml.authentication) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.supportsSso; });
const profInfos = [];
// Loop through every APIML service that supports SSO
for (const service of ssoServices) {
// Loop through every API advertised by this service
for (const apiInfo of service.apiml.apiInfo) {
// Loop through any IApimlSvcAttrs object with a matching API ID
for (const config of configs.filter(({ apiId }) => apiId === apiInfo.apiId)) {
if (config.gatewayUrl == null || apiInfo.gatewayUrl === config.gatewayUrl) {
// Update or create IApimlProfileInfo object for this service ID and profile type
let profInfo = profInfos.find(({ profName, profType }) => profName === service.serviceId && profType === config.connProfType);
if (profInfo == null) {
profInfo = {
profName: service.serviceId,
profType: config.connProfType,
basePaths: [],
pluginConfigs: new Set(),
gatewayUrlConflicts: {}
};
profInfos.push(profInfo);
}
if (!profInfo.basePaths.includes(apiInfo.basePath)) {
if (apiInfo.gatewayUrl === config.gatewayUrl || apiInfo.defaultApi) {
const numGatewayUrls = new Set(new Array(...profInfo.pluginConfigs)
.filter(({ gatewayUrl }) => gatewayUrl)
.map(({ gatewayUrl }) => gatewayUrl)).size;
profInfo.basePaths.splice(numGatewayUrls, 0, apiInfo.basePath);
}
else {
profInfo.basePaths.push(apiInfo.basePath);
}
}
profInfo.pluginConfigs.add(config);
profInfo.gatewayUrlConflicts[config.pluginName] = [
...profInfo.gatewayUrlConflicts[config.pluginName] || [],
apiInfo.gatewayUrl
];
}
}
}
}
// Filter base paths and detect conflicts in profile info array
for (const profInfo of profInfos) {
// Find the set of gateway URLs common to all CLI plug-ins
const commonGatewayUrls = Object.values(profInfo.gatewayUrlConflicts)
.reduce((a, b) => a.filter(x => b.includes(x)));
if (commonGatewayUrls.length > 0) {
const serviceIdPrefix = new RegExp(`^/${profInfo.profName}/`);
profInfo.basePaths = profInfo.basePaths.filter(basePath => commonGatewayUrls.includes(basePath.replace(serviceIdPrefix, "")));
profInfo.gatewayUrlConflicts = {};
}
}
return profInfos;
});
}
/**
* Converts apiml profile information to team config profile objects to be stored in a zowe.config.json file
* @param profileInfoList List of apiml profiles
* @returns List of config profile objects
* @example
* Input: IApimlProfileInfo[] = [
* {
* profName: 'zosmf',
* profType: 'zosmf',
* basePaths: [ '/zosmf/api/v1' ],
* pluginConfigs: Set(1) { [IApimlSvcAttrsLoaded] },
* conflictTypes: [ 'profType' ]
* },
* {
* profName: 'ibmzosmf',
* profType: 'zosmf',
* basePaths: [ '/ibmzosmf/api/v1' ],
* pluginConfigs: Set(1) { [IApimlSvcAttrsLoaded] },
* conflictTypes: [ 'profType' ]
* }
* ]
* Output: IConfig = {
* "profiles": {
* "zosmf": {
* "type": "zosmf",
* "properties": {
* "basePath": "/zosmf/api/v1"
* }
* },
* "ibmzosmf": {
* "type": "zosmf",
* "properties": {
* "basePath": "/ibmzosmf/api/v1"
* }
* }
* },
* "defaults": {
* // Multiple services were detected.
* // Uncomment one of the lines below to set a different default
* //"zosmf": "ibmzosmf"
* "zosmf": "zosmf"
* }
* }
* @memberof Services
*/
static convertApimlProfileInfoToProfileConfig(profileInfoList) {
const configProfile = {
properties: {},
profiles: {}
};
let configDefaults = {};
const conflictingDefaults = {};
// const configPlugins: Set<string> = new Set<string>();
const _genCommentsHelper = (key, elements) => {
if (elements == null || elements.length === 0)
return "";
return elements.reduce((all, current, _index) => {
const escapeCurrent = JSON.stringify(current).slice(1, -1);
return all.concat(key.includes("base") ? `\n//"${key}": "${escapeCurrent}"` : `\n//"${key}": "${escapeCurrent}",`);
}, "");
};
profileInfoList === null || profileInfoList === void 0 ? void 0 : profileInfoList.forEach((profileInfo) => {
// TODO Add back in the future if we want plugins in team config
// profileInfo.pluginConfigs.forEach((pluginInfo: IApimlSvcAttrsLoaded) => {
// configPlugins.add(pluginInfo.pluginName);
// });
if (!configDefaults[profileInfo.profType]) {
configDefaults[profileInfo.profType] = profileInfo.profName;
}
else {
if (!conflictingDefaults[profileInfo.profType]) {
conflictingDefaults[profileInfo.profType] = [];
}
conflictingDefaults[profileInfo.profType].push(profileInfo.profName);
}
configProfile.profiles[profileInfo.profName] = {
type: profileInfo.profType,
properties: {}
};
const basePaths = JSON.parse(JSON.stringify(profileInfo.basePaths));
if (basePaths.length === 1) {
configProfile.profiles[profileInfo.profName].properties.basePath = basePaths[0];
}
else if (basePaths.length > 1) {
const defaultBasePath = basePaths.shift();
const basePathConflicts = Object.keys(profileInfo.gatewayUrlConflicts);
let conflictingPluginsList = "";
basePathConflicts.forEach((element) => {
// The new-line before the // "element" is required in order to properly format the comment-json object
conflictingPluginsList += `
// "${element}": "${profileInfo.gatewayUrlConflicts[element].join('", "')}"`;
});
// Typecasting because of this issue: https://github.com/kaelzhang/node-comment-json/issues/42
const basepathConflictMessage = `
// ---
// Warning: basePath conflict detected!
// Different plugins require different versions of the same API.
// List:${conflictingPluginsList}
// ---`;
const noConflictMessage = `
// Multiple base paths were detected for this service.
// Uncomment one of the lines below to use a different one.`;
configProfile.profiles[profileInfo.profName].properties = JSONC.parse(`
{
${basePathConflicts.length > 0 ? basepathConflictMessage : noConflictMessage}
${_genCommentsHelper("basePath", basePaths)}
"basePath": ${JSON.stringify(defaultBasePath)}
}`);
}
});
// Establish keys for object map for index check within loop
const defaultKeys = Object.keys(conflictingDefaults);
for (const defaultKey in conflictingDefaults) {
if (configDefaults[defaultKey] != null) {
const trueDefault = configDefaults[defaultKey];
delete configDefaults[defaultKey];
let jsonString = `
${JSONC.stringify(configDefaults, null, imperative_1.ConfigConstants.INDENT).slice(0, -1)}${Object.keys(configDefaults).length > 0 ? "," : ""}`;
const defaultKeyIndex = defaultKeys.indexOf(defaultKey);
// Logic to ensure that comment block is not duplicated
if (defaultKeyIndex === 0) {
jsonString += `
// Multiple services were detected.
// Uncomment one of the lines below to set a different default.`;
}
jsonString += `
${_genCommentsHelper(defaultKey, conflictingDefaults[defaultKey])}
"${defaultKey}": ${JSON.stringify(trueDefault)}`;
// Terminate the JSON string
jsonString += '\n}';
configDefaults = JSONC.parse(jsonString);
}
}
const configResult = {
profiles: configProfile.profiles,
defaults: configDefaults,
autoStore: true
};
return configResult;
}
}
exports.Services = Services;
//# sourceMappingURL=Services.js.map