Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Hardcoded Schema References #240

Merged
merged 15 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 7 additions & 24 deletions src/actions/InstallationHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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: ''}}};
}
}

Expand Down
92 changes: 92 additions & 0 deletions src/services/ResolveRef.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Loading