diff --git a/src/actions/InstallationHandler.ts b/src/actions/InstallationHandler.ts index 439638c4..5d2bbfaf 100644 --- a/src/actions/InstallationHandler.ts +++ b/src/actions/InstallationHandler.ts @@ -19,16 +19,7 @@ import * as fs from 'fs'; import { ConfigurationStore } from '../storage/ConfigurationStore'; import { InstallationArgs } from '../types/stateInterfaces'; import { FALLBACK_SCHEMA, deepMerge } from '../renderer/components/common/Utils'; - - -//AJV did not like the regex in our current schema -const zoweDatasetMemberRegexFixed = { - "description": "PARMLIB member used by ZIS", - "type": "string", - "pattern": "^([A-Z$#@]){1}([A-Z0-9$#@]){0,7}$", - "minLength": 1, - "maxLength": 8 -} +import { updateSchemaReferences } from '../services/ResolveRef'; class Installation { @@ -152,19 +143,11 @@ class Installation { } //No reason not to always set schema to latest if user is re-running installation - if(readPaxYamlAndSchema.details.yamlSchema && readPaxYamlAndSchema.details.serverCommon){ + if(readPaxYamlAndSchema.details.schemas.yamlSchema){ try { - let yamlSchema = JSON.parse(readPaxYamlAndSchema.details.yamlSchema); - const serverCommon = JSON.parse(readPaxYamlAndSchema.details.serverCommon); - if(yamlSchema && serverCommon){ - yamlSchema.additionalProperties = true; - yamlSchema.properties.zowe.properties.setup.properties.dataset.properties.parmlibMembers.properties.zis = zoweDatasetMemberRegexFixed; - yamlSchema.properties.zowe.properties.setup.properties.certificate.properties.pkcs12.properties.directory = serverCommon.$defs.path; - if(yamlSchema.$defs?.networkSettings?.properties?.server?.properties?.listenAddresses?.items){ - delete yamlSchema.$defs?.networkSettings?.properties?.server?.properties?.listenAddresses?.items?.ref; - yamlSchema.$defs.networkSettings.properties.server.properties.listenAddresses.items = serverCommon.$defs.ipv4 - } - // console.log('Setting schema from runtime dir:', JSON.stringify(yamlSchema)); + let yamlSchema = JSON.parse(readPaxYamlAndSchema.details.schemas.yamlSchema); + updateSchemaReferences(readPaxYamlAndSchema.details.schemas, yamlSchema); + if(yamlSchema){ ConfigurationStore.setSchema(yamlSchema); parsedSchema = true; ProgressStore.set('downloadUnpax.getSchemas', true); @@ -630,10 +613,10 @@ export class FTPInstallation extends Installation { const yamlSchema = await new FileTransfer().download(connectionArgs, yamlSchemaPath, DataType.ASCII); const serverCommonPath = `${installDir}/schemas/server-common.json`; const serverCommon = await new FileTransfer().download(connectionArgs, serverCommonPath, DataType.ASCII); - return {status: true, details: {yaml, yamlSchema, serverCommon}}; + return {status: true, details: {yaml, schemas: {yamlSchema, serverCommon}}}; } catch (e) { console.log("Error downloading example-zowe.yaml and schemas:", e.message); - return {status: false, details: {yaml: '', yamlSchema: '', serverCommon: ''}} + return {status: false, details: {yaml: '', schemas: {yamlSchema: '', serverCommon: ''}}}; } } diff --git a/src/services/ResolveRef.ts b/src/services/ResolveRef.ts new file mode 100644 index 00000000..18df0e91 --- /dev/null +++ b/src/services/ResolveRef.ts @@ -0,0 +1,92 @@ +let mainSchema: any; +let schemaMap: { [key: string]: any } = {}; + +export const updateSchemaReferences = (schemas: { [key: string]: string }, schemaObject: any): void => { + schemaMap = parseSchemas(schemas); + + // Traverse and resolve references in schemas other than zowe-yaml schema + Object.values(schemaMap).forEach((schema: any) => { + if(schema?.$id !== schemaObject?.$id) { + mainSchema = schema; + traverseAndResolveReferences(schema); + } + }) + + // Traverse and resolve references for the zowe-yaml schema + mainSchema = schemaObject; + traverseAndResolveReferences(schemaObject); +} + +// Parses all schemas and populates the schemaMap +const parseSchemas = (schemas: { [key: string]: string }): { [key: string]: any } => { + const schemaMap: { [key: string]: any } = {}; + Object.entries(schemas).forEach(([key, value]) => { + try { + const schemaObject = JSON.parse(value); + const id = schemaObject?.$id; + if (id) { + schemaMap[id] = schemaObject; + } + } catch (error: any) { + console.error(`Error parsing schema for key ${key}:`, error.message); + } + }); + return schemaMap; +}; + +// Recursively traverse and resolve $ref references in the schema object +const traverseAndResolveReferences = (schemaObj: any) => { + if (schemaObj && typeof schemaObj === "object") { + Object.keys(schemaObj).forEach((key) => { + if (key === "$ref" && typeof schemaObj[key] === "string") { + try { + const refValue = resolveRef(schemaObj[key]); + Object.assign(schemaObj, refValue); + delete schemaObj['$ref']; + } catch(error){ + console.error("Error resolving reference:", error.message); + } + } else { + traverseAndResolveReferences(schemaObj[key]); + } + }) + } +} + +// Resolve a $ref string to its referenced schema object +const resolveRef = (ref: string) => { + let [refPath, anchorPart] = ref.split('#'); + const isRefPathEmpty = !refPath; + + let refSchema = isRefPathEmpty + ? mainSchema + : schemaMap[Object.keys(schemaMap).find((id) => id.endsWith(refPath))]; + + if (!refSchema) { + throw new Error(`Schema for reference path ${refPath} not found`); + } + + if (!refSchema.$defs) { + throw new Error(`No $defs found in schema ${refSchema.$id}`); + } + + const anchor = anchorPart?.split("/").pop(); + const refObject = isRefPathEmpty + ? refSchema.$defs[anchor] + : Object.values(refSchema.$defs).find((obj:any) => obj.$anchor === anchor); + + if (!refObject) { + throw new Error(`Reference ${ref} not found in schemaObject ${refSchema.$id}`); + } + + // Ensure pattern is a string and remove backslashes for ajv compatibility + if (refObject.pattern) { + const pattern = typeof refObject.pattern === 'string' ? refObject.pattern : String(refObject.pattern); + + if (pattern.includes("\\")) { + refObject.pattern = pattern.replace(/\\/g, ''); + } + } + + return refObject; +} \ No newline at end of file