UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

318 lines (317 loc) 12.9 kB
/** * Copyright Super iPaaS Integration LLC, an IBM Company 2024 */ import yaml from 'js-yaml'; import { AppConstants } from './constants/app.constants.js'; import { addErrorToResponse, convertNumberToString, createAssetReferenceMap, createPathReferenceMap, isValidAsset, updatePathRefMap, validateMinAssets, validateYamlFiles } from './utils.js'; import JSZip from 'jszip'; // import { // } from './service/log-wrapper.js'; export class ZipProcessor { constructor(buffer) { this.buffer = buffer; } async validate() { //.logInfo('0003', 'Starting validation process for ZIP buffer'); // Starting validation process if (!await this.validateReferences()) { //.logDebug('0360', 'references'); return false; } if (!await this.validatePaths()) { //.logDebug('0360', 'paths'); return false; } if (!await this.validateZip()) { //.logDebug('0360', 'zip'); return false; } if (!await this.validateApiFiles()) { //.logDebug('0360', 'API files'); return false; } if (!await this.validateYamlStructure()) { //.logDebug('0360', AppConstants.YAMLStructure); return false; } return this.validateYamlFiles(); } async validateYamlStructure() { //.logDebug('0361', AppConstants.YAMLStructure); if (!await validateYamlFiles(this.buffer)) { //.logError('0255', 'Invalid YAML files detected in buffer'); return false; } //.logDebug('0362', AppConstants.YAMLStructure); return true; } async validateApiFiles() { //.logDebug('0361', 'API files in zip'); const zip = new JSZip(); try { const zipContent = await zip.loadAsync(this.buffer); //.logDebug('0362', 'API files in zip'); return await this.validateZipContent(zipContent); } catch (err) { //.logError('0013', 'loading ZIP', `${err}`); return false; } } async validateZipContent(zipContent) { //.logDebug('0361', 'ZIP content'); for (const fileName in zipContent.files) { const entry = zipContent.files[fileName]; if (this.isYamlFile(entry, fileName)) { //.logDebug('0357', AppConstants.YAMLContent, fileName); const content = await entry.async('string'); const isValid = await this.checkYamlContent(content, fileName); if (!isValid) { //.logError('0359', AppConstants.YAMLContent, fileName); return false; } } } //.logDebug('0362', 'ZIP content'); return true; } async checkYamlContent(content, fileName) { try { const yamlContents = yaml.loadAll(content); for (const yamlContent of yamlContents) { if (yamlContent.kind?.toLowerCase() === 'api') { const specObj = JSON.stringify(yamlContent.spec); const apiSpec = yaml.load(specObj); if (this.isInvalidApiSpec(apiSpec)) { addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, fileName, `Validation failed for api-spec field in file ${fileName}`); //.logError('0359', 'API spec', fileName); return false; } } } } catch (err) { //.logError('0013', `parsing YAML content in ${fileName}`, `${err}`); return false; } //.logDebug('0358', AppConstants.YAMLContent, fileName); return true; } isInvalidApiSpec(apiSpec) { //.logDebug('0361', 'API Specification'); const apiSpecField = AppConstants.apiSpec; const apiSpecPathLength = apiSpec[apiSpecField]?.$path?.length ?? 0; return !apiSpec || !apiSpec[apiSpecField] || !apiSpec[apiSpecField].$path || (apiSpecPathLength <= 0); } async validateReferences() { //.logDebug('0361', 'references'); const refMap = await createAssetReferenceMap(this.buffer); const allRefsValid = Array.from(refMap.entries()).every(([key, value]) => { if (!value) { addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, key, `Validation failed for Reference ${key}`); //.logError('0359', 'reference', key); } return value; }); if (!allRefsValid) { //.logError('0255', 'Some references are not valid'); return false; } return true; } async validatePaths() { //.logDebug('0361', 'paths'); const pathMap = await createPathReferenceMap(this.buffer); await updatePathRefMap(this.buffer, pathMap); const allPathsValid = Array.from(pathMap.entries()).every(([key, value]) => { if (!value) { addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, key, `Validation failed for Reference ${key}`); } return value; }); if (!allPathsValid) { //.logError('0255', 'Some references are not valid'); return false; } //.logDebug('0362', 'paths'); return true; } async validateZip() { //.logDebug('0361', 'ZIP'); if (!await validateMinAssets(this.buffer)) { //.logError('0255', 'Zip does not contain minimum assets'); return false; } //.logDebug('0362', 'ZIP'); return true; } async validateYamlFiles() { //.logDebug('0361', 'YAML files in zip'); const zip = new JSZip(); const zipContent = await zip.loadAsync(this.buffer); for (const fileName in zipContent.files) { const entry = zipContent.files[fileName]; if (this.isYamlFile(entry, fileName)) { if (!(await this.validateYamlFileContent(entry, fileName))) { return false; } } } //.logDebug('0362', 'YAML files in zip'); return true; } async validateYamlFileContent(entry, fileName) { //.logDebug('0357', AppConstants.YAMLContent, fileName); try { const content = await entry.async('string'); const yamlContents = yaml.loadAll(content); for (const yamlContent of yamlContents) { if (!isValidAsset(yamlContent)) { //.logError('0359', 'asset', fileName); addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, 'YAML_FILE', `Invalid YAML in file ${fileName}`); return false; } } } catch (err) { //.logError('0013', `parsing YAML in ${fileName}`, `${err}`); return false; } //.logDebug('0358', AppConstants.YAMLContent, fileName); return true; } async processZip() { //.logInfo('0003', 'Creating build ZIP from buffer'); if (!await this.validate()) { //.logDebug('0003', 'ZIP validation failed during build process'); return null; } //.logDebug('0003', 'Validation passed, extracting kind groups'); const kindGroups = await this.extractKindGroups(this.buffer); //.logDebug('0003', 'Extracted kind groups, creating build ZIP'); const buildZip = await this.createBuildZip(this.buffer, kindGroups); //.logInfo('0003', 'Build ZIP creation completed'); return { buildZip }; } async extractGatewaysJson(buffer) { //.logDebug('0003', 'Extracting gateways.json from ZIP'); const zip = new JSZip(); let gatewaysJsonContent = {}; try { const zipContent = await zip.loadAsync(buffer); for (const fileName in zipContent.files) { const entry = zipContent.files[fileName]; if (!entry.dir && fileName.includes('gateways.json')) { try { const content = await entry.async('string'); gatewaysJsonContent = JSON.parse(content); } catch (err) { //.logError('0013', 'parsing gateways.json', `${err}`); } } } } catch (err) { //.logError('0013', 'loading ZIP', `${err}`); } return gatewaysJsonContent; } async extractKindGroups(buffer) { const zip = new JSZip(); const kindGroups = {}; const kindGroupsSet = new Set(); try { const zipContent = await zip.loadAsync(buffer); await this.processZipContent(zipContent, kindGroups, kindGroupsSet); } catch (err) { //.logError('0013', 'extracting kind groups', `${err}`); } return kindGroups; } async processZipContent(zipContent, kindGroups, kindGroupsSet) { for (const fileName in zipContent.files) { const entry = zipContent.files[fileName]; if (this.isYamlFile(entry, fileName)) { //.logDebug('0353', fileName); await this.processYamlEntry(entry, fileName, kindGroups, kindGroupsSet); } } } isYamlFile(entry, fileName) { return !entry.dir && (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) && !fileName.startsWith('resources'); } async processYamlEntry(entry, fileName, kindGroups, kindGroupsSet) { try { const content = await entry.async('string'); const yamlContents = yaml.loadAll(content); for (const yamlContent of yamlContents) { if (yamlContent && yamlContent.kind) { await this.processYamlContent(yamlContent, kindGroups, kindGroupsSet); } } } catch (err) { //.logError('0013', 'parsing YAML', `${err}`); } } async processYamlContent(yamlContent, kindGroups, kindGroupsSet) { const kind = yamlContent.kind; if (!kindGroups[kind]) { kindGroups[kind] = []; } const metadata = yamlContent.metadata; const contentString = `${metadata.namespace ? metadata.namespace : ''}:${metadata.name}:${convertNumberToString(metadata.version)}`; if (!kindGroupsSet.has(contentString)) { yamlContent.metadata.version = convertNumberToString(metadata.version); const yamlString = yaml.dump(yamlContent); const contentWithYaml = await this.zipYamlContent(yamlString); kindGroups[kind].push(contentWithYaml); kindGroupsSet.add(contentString); } } async zipYamlContent(yamlString) { //.logDebug('0003', 'Zipping YAML content'); const tempZip = new JSZip(); tempZip.file('file.yaml', yamlString); const tempZipBuffer = await tempZip.generateAsync({ type: 'nodebuffer' }); const zipWithYaml = new JSZip(); const yamlZipContent = await zipWithYaml.loadAsync(tempZipBuffer); if (yamlZipContent.files) { return yamlZipContent.files['file.yaml'].async('string'); } //.logError('0004', 'Failed to zip YAML content'); throw new Error('Failed to zip YAML content'); } async createBuildZip(buffer, kindGroups) { const jszip = new JSZip(); const zip = await JSZip.loadAsync(buffer); const resourcesFolder = jszip.folder('resources'); for (const fileName in zip.files) { const entry = zip.files[fileName]; if (!entry.dir) { const content = await entry.async('nodebuffer'); if (fileName.includes('resources/')) { //.logDebug('0003', `Adding file to resources folder: ${fileName}`); resourcesFolder?.file(fileName.replace('resources/', ''), content); } } } const allYamlContent = this.combineYamlContent(kindGroups); jszip.file('gw_build.yaml', Buffer.from(allYamlContent.join('\n---\n') + '\n', 'utf-8')); //.logDebug('0003', 'Build ZIP creation completed'); return jszip; } combineYamlContent(kindGroups) { //.logDebug('0003', 'Combining YAML content from kind groups'); const allYamlContent = []; for (const kind in kindGroups) { const combinedYaml = kindGroups[kind].join('\n---\n'); allYamlContent.push(combinedYaml); } //.logDebug('0003', 'YAML content combined'); return allYamlContent; } }