@apistudio/apim-cli
Version:
CLI for API Management Products
258 lines • 11.8 kB
JavaScript
import { GatewayLabels } from '@apic/studio-client-model';
import JSZip from 'jszip';
import yaml from 'js-yaml';
import { checkFileExtension } from '../utils.js';
import { isValidAsset, SchemaHandler, Logger, toError } from '@apic/studio-shared';
import { AssetValidator } from '../service/validation-service.js';
export class AssetSchemaValidator {
constructor() {
this.commonAssetKinds = [
'API',
'CORS',
'Properties',
'Scope',
'URISchemes',
'Product',
'Plan',
'Quota',
'DataPowerAssembly',
];
}
async validateApiFile(buffer, gatewayTypes) {
const errors = [];
try {
const { lwgwReferences, wmgwReferences, dpgwReferences, dpgwv5References } = await this.categorizePolicySequences(buffer);
if (gatewayTypes.includes(GatewayLabels.WMGW) && wmgwReferences.size === 0) {
const msg = 'WebMethods gateway is selected but no staged policy sequences are referenced';
Logger.error(msg);
errors.push(msg);
}
if (gatewayTypes.includes(GatewayLabels.LWGW) && lwgwReferences.size === 0) {
const msg = 'DataPower Nano gateway is selected but no free flow policy sequences are referenced';
Logger.error(msg);
errors.push(msg);
}
if (gatewayTypes.includes(GatewayLabels.DPGW) && dpgwReferences.size === 0) {
const msg = 'DataPower API gateway is selected but no DataPowerAssembly is referenced';
Logger.error(msg);
errors.push(msg);
}
if (gatewayTypes.includes(GatewayLabels.DPv5GW) && dpgwv5References.size === 0) {
const msg = 'Publishing failed. The policy sequence is not compatible with the selected gateway.';
Logger.error(msg);
errors.push(msg);
}
return {
valid: errors.length === 0,
errors,
};
}
catch (err) {
const msg = `Error validating API file: ${err instanceof Error ? err.message : String(err)}`;
Logger.error(msg);
return { valid: false, errors: [msg] };
}
}
async readAndConsolidateYamlFiles(buffer) {
try {
const zip = new JSZip();
const zipContent = await zip.loadAsync(buffer);
const validYamlContents = [];
await Promise.all(Object.keys(zipContent.files).map(async (fileName) => {
const entry = zipContent.files[fileName];
if (!entry || entry.dir || !checkFileExtension(entry.name)) {
return;
}
const content = await entry.async('string');
try {
const yamlContents = yaml.loadAll(content);
for (const yamlContent of yamlContents) {
if (isValidAsset(yamlContent, true)) {
validYamlContents.push(yamlContent);
}
else {
Logger.warn(`Invalid asset found in ${entry.name}`);
}
}
}
catch (yamlError) {
Logger.error(`Error parsing YAML in ${entry.name}:`, toError(yamlError));
}
}));
return validYamlContents;
}
catch (error) {
Logger.error('Error processing buffer:', toError(error));
return [];
}
}
async categorizePolicySequences(buffer) {
const lwgwReferences = new Set();
const wmgwReferences = new Set();
const dpgwReferences = new Set();
const dpgwv5References = new Set();
const consolidatedYaml = await this.readAndConsolidateYamlFiles(buffer);
const apiAssets = consolidatedYaml.filter((asset) => (asset.kind.toUpperCase() === 'API' || asset.kind.toUpperCase() === 'GLOBALPOLICY') &&
asset.spec &&
asset.spec['policy-sequence'] &&
Array.isArray(asset.spec['policy-sequence']) &&
asset.spec['policy-sequence'].length > 0);
for (const apiAsset of apiAssets) {
const firstPolicyRef = apiAsset.spec['policy-sequence'][0].$ref;
if (typeof firstPolicyRef === 'string') {
const parts = firstPolicyRef.split(':');
const namespace = parts[0];
const name = parts[1];
const version = parts[2];
if (!namespace || !name || !version)
continue;
const matchingAsset = consolidatedYaml.find((asset) => asset.metadata &&
asset.metadata.namespace === namespace &&
asset.metadata.name === name &&
asset.metadata.version === version);
if (matchingAsset) {
if (matchingAsset.kind.toLowerCase() === 'freeflowpolicysequence') {
lwgwReferences.add(firstPolicyRef);
this.findAllNestedReferences(matchingAsset, consolidatedYaml, lwgwReferences);
}
else if (matchingAsset.kind.toLowerCase() === 'stagedpolicysequence') {
wmgwReferences.add(firstPolicyRef);
this.findAllNestedReferences(matchingAsset, consolidatedYaml, wmgwReferences);
}
else if (matchingAsset?.spec?.["x-ibm-configuration"]?.gateway === 'datapower-api-gateway') {
dpgwReferences.add(firstPolicyRef);
this.findAllNestedReferences(matchingAsset, consolidatedYaml, dpgwReferences);
}
else if (matchingAsset?.spec?.["x-ibm-configuration"]?.gateway === 'datapower-gateway') {
dpgwv5References.add(firstPolicyRef);
}
}
}
}
return { lwgwReferences, wmgwReferences, dpgwReferences, dpgwv5References };
}
findAllNestedReferences(asset, allAssets, referenceSet) {
if (!asset || !asset.spec)
return;
const currentRefs = new Set();
this.findAllRefsRecursive(asset.spec, currentRefs);
for (const ref of currentRefs) {
if (!referenceSet.has(ref)) {
referenceSet.add(ref);
const parts = ref.split(':');
const namespace = parts[0];
const name = parts[1];
const version = parts[1];
if (!namespace || !name || !version)
continue;
const referencedAsset = allAssets.find((a) => a.metadata &&
a.metadata.namespace === namespace &&
a.metadata.name === name &&
a.metadata.version === version);
if (referencedAsset) {
this.findAllNestedReferences(referencedAsset, allAssets, referenceSet);
}
}
}
}
findAllRefsRecursive(data, referenceSet) {
if (data == null)
return;
if (Array.isArray(data)) {
for (const item of data) {
this.findAllRefsRecursive(item, referenceSet);
}
}
else if (typeof data === 'object') {
for (const key of Object.keys(data)) {
const value = data[key];
if (key === '$ref' && typeof value === 'string') {
referenceSet.add(value);
}
else {
this.findAllRefsRecursive(value, referenceSet);
}
}
}
}
async validateSchema(buffer, gatewayTypes) {
const apiValidationResult = await this.validateApiFile(buffer, gatewayTypes);
if (!apiValidationResult.valid) {
return apiValidationResult;
}
const errors = [];
try {
const allAssets = await this.readAndConsolidateYamlFiles(buffer);
const commonSchemaHandler = new SchemaHandler();
const commonValidator = new AssetValidator(commonSchemaHandler);
for (const asset of allAssets) {
if (asset.kind && this.commonAssetKinds.includes(asset.kind)) {
const validationResult = commonValidator.validateAssets(asset);
if (!validationResult.valid) {
errors.push(...validationResult.errors);
}
}
}
if (gatewayTypes.includes(GatewayLabels.WMGW) &&
gatewayTypes.includes(GatewayLabels.LWGW) &&
gatewayTypes.includes(GatewayLabels.DPGW)) {
await this.validateGatewaySpecificAssets(allAssets, GatewayLabels.WMGW, errors, buffer);
await this.validateGatewaySpecificAssets(allAssets, GatewayLabels.LWGW, errors, buffer);
await this.validateGatewaySpecificAssets(allAssets, GatewayLabels.DPGW, errors, buffer);
}
else if (gatewayTypes.includes(GatewayLabels.WMGW)) {
await this.validateGatewaySpecificAssets(allAssets, GatewayLabels.WMGW, errors, buffer);
}
else if (gatewayTypes.includes(GatewayLabels.LWGW)) {
await this.validateGatewaySpecificAssets(allAssets, GatewayLabels.LWGW, errors, buffer);
}
else if (gatewayTypes.includes(GatewayLabels.DPGW)) {
await this.validateGatewaySpecificAssets(allAssets, GatewayLabels.DPGW, errors, buffer);
}
return {
valid: errors.length === 0,
errors,
};
}
catch (err) {
const msg = `Error validating schema: ${err instanceof Error ? err.message : String(err)}`;
Logger.error(msg);
return { valid: false, errors: [msg] };
}
}
async validateGatewaySpecificAssets(assets, gatewayType, errors, buffer) {
const gatewayLabel = gatewayType === GatewayLabels.WMGW
? 'webMethods'
: gatewayType === GatewayLabels.LWGW
? 'nano'
: gatewayType === GatewayLabels.DPGW
? 'datapower'
: undefined;
if (!gatewayLabel)
return;
const { lwgwReferences, wmgwReferences, dpgwReferences } = await this.categorizePolicySequences(buffer);
const referenceSet = gatewayType === GatewayLabels.WMGW
? wmgwReferences
: gatewayType === GatewayLabels.LWGW
? lwgwReferences
: dpgwReferences;
const schemaHandler = new SchemaHandler(gatewayLabel);
const validator = new AssetValidator(schemaHandler);
const gatewaySpecificAssets = assets.filter((asset) => {
return !this.commonAssetKinds.includes(asset.kind);
});
for (const asset of gatewaySpecificAssets) {
if (!asset.kind || !asset.metadata)
continue;
const assetRef = `${asset.metadata.namespace}:${asset.metadata.name}:${asset.metadata.version}`;
if (referenceSet.has(assetRef)) {
const validationResult = validator.validateAssets(asset);
if (!validationResult.valid) {
const gatewayErrors = validationResult.errors.map((err) => `${err}`);
errors.push(...gatewayErrors);
}
}
}
}
}
//# sourceMappingURL=schema-validator.impl.js.map