UNPKG

karavan-core

Version:
413 lines (385 loc) 19.1 kB
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as yaml from 'js-yaml'; import { Beans, CamelElement, Integration } from '../model/IntegrationDefinition'; import { BeanFactoryDefinition, RouteConfigurationDefinition, RouteDefinition } from '../model/CamelDefinition'; import { CamelUtil } from './CamelUtil'; import { CamelDefinitionYamlStep } from './CamelDefinitionYamlStep'; export class CamelDefinitionYaml { private constructor() { } static integrationToYaml = (integration: Integration): string => { const clone: any = CamelUtil.cloneIntegration(integration); const flows = integration.spec.flows; clone.spec.flows = flows ?.map((f: any) => CamelDefinitionYaml.cleanupElement(f)) .filter(x => Object.keys(x).length !== 0); if (integration.type === 'crd') { delete clone.type; const i = JSON.parse(JSON.stringify(clone, (key, value) => CamelDefinitionYaml.replacer(key, value), 3)); // fix undefined in string attributes return CamelDefinitionYaml.yamlDump(i); } else if (integration.type === 'kamelet') { delete clone.type; // turn array of flows to object properties in template for Kamelet const template: any = {route: {}} const route: RouteDefinition = clone.spec.flows.filter((f: any) => f.dslName === 'RouteDefinition')?.[0]; if (route) { template.route = Object.assign(template.route, route); } else if (clone.spec.template?.route) { template.route = clone.spec.template.route; } else if (clone.spec.template?.from) { template.route = {from: clone.spec.template?.from}; } const from: RouteDefinition = clone.spec.flows.filter((f: any) => f.dslName === 'FromDefinition')?.[0]; if (from) { template.from = {from: from}; } const beans = clone.spec.flows.filter((f: any) => f.dslName === 'Beans')?.at(0)?.beans; if (beans) { template.beans = beans; } else if (clone.spec.template?.beans){ template.beans = clone.spec.template.beans; } clone.spec.template = template; delete clone.spec.flows; const i = JSON.parse(JSON.stringify(clone, (key, value) => CamelDefinitionYaml.replacer(key, value, true), 3)); // fix undefined in string attributes return CamelDefinitionYaml.yamlDump(i); } else { const f = JSON.parse( JSON.stringify(clone.spec.flows, (key, value) => CamelDefinitionYaml.replacer(key, value), 3), ); return CamelDefinitionYaml.yamlDump(f); } }; static isEmpty = (value?: string): boolean => { return value === undefined || (value.trim && value.trim().length === 0); }; static isEmptyObject(obj: any): boolean { // Check if it's an object and not null if (obj && typeof obj === 'object') { // Get all enumerable property names const keys = Object.keys(obj); // Get all non-enumerable property names const nonEnumProps = Object.getOwnPropertyNames(obj); // Check if there are no properties return keys.length === 0 && nonEnumProps.length === 0; } return false; } static cleanupElement = (element: CamelElement, inArray?: boolean, inSteps?: boolean): CamelElement => { const result: any = {}; const object: any = { ...element }; if (inArray) { object.inArray = inArray; object.inSteps = !!inSteps; } if (object.dslName.endsWith('Expression')) { delete object.language; delete object.expressionName; } else if (object.dslName.endsWith('DataFormat')) { delete object.dataFormatName; } else if (object.dslName === 'BeanFactoryDefinition') { if (object.properties && Object.keys(object.properties).length === 0) { delete object.properties; } if (object.constructors && CamelDefinitionYaml.isEmptyObject(object.constructors)) { delete object.constructors; } } else if (object.dslName === 'CatchDefinition' && object?.onWhen?.stepName !== undefined) { object.onWhen.stepName = 'onWhen'; // https://github.com/apache/camel-karavan/issues/1420 } delete object.uuid; delete object.showChildren; for (const [key, value] of Object.entries(object) as [string, any][]) { if (value instanceof CamelElement || (typeof value === 'object' && value?.dslName)) { result[key] = CamelDefinitionYaml.cleanupElement(value); } else if (Array.isArray(value)) { if (value.length > 0) { result[key] = CamelDefinitionYaml.cleanupElements(value, key === 'steps'); } } else if (key === 'parameters' && typeof value === 'object') { const parameters = Object.entries(value || {}) .filter(([_, v]: [string, any]) => !CamelDefinitionYaml.isEmpty(v)) .reduce((x: any, [k, v]) => ({ ...x, [k]: v }), {}); if (Object.keys(parameters).length > 0) { result[key] = parameters; } } else { if (!CamelDefinitionYaml.isEmpty(value)) { result[key] = value; } } } return result as CamelElement; }; static cleanupElements = (elements: CamelElement[], inSteps?: boolean): CamelElement[] => { const result: any[] = []; for (const element of elements) { if (typeof element === 'object') { result.push(CamelDefinitionYaml.cleanupElement(element, true, inSteps)); } else { result.push(element); } } return result; }; static yamlDump = (integration: any): string => { return yaml.dump(integration, { noRefs: false, noArrayIndent: false, // forceQuotes: true, quotingType: '"', sortKeys: function(a: any, b: any) { if (a === 'steps') return 1; else if (b === 'steps') return -1; else return 0; }, }); }; static replacer = (key: string, value: any, isKamelet: boolean = false): any => { if ( typeof value === 'object' && (value.hasOwnProperty('stepName') || value.hasOwnProperty('inArray') || value.hasOwnProperty('inSteps')) ) { const stepNameField = value.hasOwnProperty('stepName') ? 'stepName' : 'step-name'; const stepName = value[stepNameField]; const dslName = value.dslName; let newValue: any = JSON.parse(JSON.stringify(value)); delete newValue.dslName; delete newValue[stepNameField]; if ( value.inArray && !value.inSteps && ['intercept', 'interceptFrom', 'interceptSendToEndpoint', 'onCompletion', 'onException'].includes( stepName, ) ) { delete newValue.inArray; delete newValue.inSteps; const xValue: any = {}; xValue[stepName] = newValue; return xValue; } else if ( (value.inArray && !value.inSteps) || dslName === 'ExpressionSubElementDefinition' || dslName === 'ExpressionDefinition' || dslName?.endsWith('Expression') || stepName === 'otherwise' || stepName === 'doFinally' || stepName === 'resilience4jConfiguration' || stepName === 'faultToleranceConfiguration' || stepName === 'errorHandler' || stepName === 'onWhen' || // https://github.com/apache/camel-karavan/issues/1420 stepName === 'deadLetterChannel' || stepName === 'defaultErrorHandler' || stepName === 'jtaTransactionErrorHandler' || stepName === 'noErrorHandler' || stepName === 'springTransactionErrorHandler' || stepName === 'redeliveryPolicy' || stepName === 'securityDefinitions' || stepName === 'apiKey' || stepName === 'basicAuth' || stepName === 'bearer' || stepName === 'mutualTls' || stepName === 'oauth2' || stepName === 'openIdConnect' || stepName === 'openApi' || key === 'from' ) { delete newValue.inArray; delete newValue.inSteps; return newValue; } else if (isKamelet && dslName === 'RouteDefinition') { delete value?.dslName; delete value?.stepName; return value; } else { delete newValue.inArray; delete newValue.inSteps; const xValue: any = {}; xValue[stepName] = newValue; return xValue; } } else { delete value?.dslName; return value; } }; static yamlToIntegration = (filename: string, text: string): Integration => { const integration: Integration = Integration.createNew(filename); const fromYaml: any = yaml.load(text); const camelized: any = CamelUtil.camelizeObject(fromYaml); if (camelized?.apiVersion && camelized.apiVersion.startsWith('camel.apache.org') && camelized.kind) { if (camelized?.metadata) { integration.metadata = camelized?.metadata; } if (camelized?.spec) { integration.spec.definition = camelized?.spec.definition; integration.spec.dependencies = camelized?.spec.dependencies; integration.spec.types = camelized?.spec.types; } const int: Integration = new Integration({ ...camelized }); if (camelized.kind === 'Integration') { integration.type = 'crd'; integration.spec.flows?.push(...CamelDefinitionYaml.flowsToCamelElements(int.spec.flows || [])); } else if (camelized.kind === 'Kamelet') { integration.type = 'kamelet'; integration.kind = 'Kamelet'; const flows: any[] = []; // turn kamelet template object properties to array of flows const beans = int.spec.template?.beans; if (beans) { flows.push(new Beans({beans: beans})) } const from = int.spec.template?.from; if (from) { flows.push(new RouteDefinition({from: from})) } else { const route = int.spec.template?.route; flows.push(route); } integration.spec.flows?.push(...CamelDefinitionYaml.flowsToCamelElements(flows || [])); } } else if (Array.isArray(camelized)) { integration.type = 'plain'; const flows: any[] = camelized; integration.spec.flows?.push(...CamelDefinitionYaml.flowsToCamelElements(flows)); } return integration; }; static yamlIsIntegration = (text: string): 'crd' | 'plain' | 'kamelet' | 'none' => { try { const fromYaml: any = yaml.load(text); const camelized: any = CamelUtil.camelizeObject(fromYaml); if (camelized?.apiVersion && camelized.apiVersion.startsWith('camel.apache.org') && camelized.kind) { if (camelized.kind === 'Integration') { return 'crd'; } else if (camelized.kind === 'Kamelet') { return 'kamelet'; } } else if (Array.isArray(camelized)) { return 'plain'; } else { return 'none'; } } catch (e) { } return 'none'; }; static flowsToCamelElements = (flows: any[]): any[] => { const result: any[] = []; flows.filter((e: any) => e.hasOwnProperty('restConfiguration')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRestConfigurationDefinition(f.restConfiguration))); flows.filter((e: any) => e.hasOwnProperty('rest')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRestDefinition(f.rest))); flows.filter((e: any) => e.hasOwnProperty('route')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRouteDefinition(f.route))); flows.filter((e: any) => e.hasOwnProperty('from')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRouteDefinition(new RouteDefinition({from: f.from})))); flows.filter((e: any) => e.hasOwnProperty('beans')) .forEach((b: any) => result.push(CamelDefinitionYaml.readBeanDefinition(b))); flows.filter((e: any) => e.hasOwnProperty('routeConfiguration')) .forEach((e: any) => result.push(CamelDefinitionYamlStep.readRouteConfigurationDefinition(e.routeConfiguration))); flows.filter((e: any) => e.hasOwnProperty('errorHandler')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRouteConfigurationDefinition(new RouteConfigurationDefinition({errorHandler: f.errorHandler})))); flows.filter((e: any) => e.hasOwnProperty('onException')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRouteConfigurationDefinition(new RouteConfigurationDefinition({onException: f.onException})))); flows.filter((e: any) => e.hasOwnProperty('intercept')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRouteConfigurationDefinition(new RouteConfigurationDefinition({intercept: f.intercept})))); flows.filter((e: any) => e.hasOwnProperty('interceptFrom')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRouteConfigurationDefinition(new RouteConfigurationDefinition({interceptFrom: f.interceptFrom})))); flows.filter((e: any) => e.hasOwnProperty('interceptSendToEndpoint')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRouteConfigurationDefinition(new RouteConfigurationDefinition({interceptSendToEndpoint: f.interceptSendToEndpoint})))); flows.filter((e: any) => e.hasOwnProperty('onCompletion')) .forEach((f: any) => result.push(CamelDefinitionYamlStep.readRouteConfigurationDefinition(new RouteConfigurationDefinition({onCompletion: f.onCompletion})))); return result; }; static readBeanDefinition = (beans: any): Beans => { const result: Beans = new Beans(); for (const bean of beans.beans) { const props: any = {}; if (bean && bean.properties) { // convert map style to properties if requires for (const [key, value] of Object.entries(bean.properties)) { CamelDefinitionYaml.flatMapProperty(key, value, new Map<string, any>()).forEach( (v, k) => (props[k] = v), ); } } if (bean && bean.property && Array.isArray(bean.property)) { // convert map style to properties if requires Array.from(bean.property).forEach((val: any) => { props[val.key] = val.value; }) delete bean.property; } bean.properties = props; result.beans.push(new BeanFactoryDefinition(bean)); } return result; }; // convert map style to properties if requires static flatMapProperty = (key: string, value: any, properties: Map<string, any>): Map<string, any> => { if (value === undefined) { return properties; } if (typeof value === 'object') { for (const k in value) { const key2 = key + '.' + k; const value2: any = value[k]; CamelDefinitionYaml.flatMapProperty(key2, value2, new Map<string, any>()).forEach((value1, key1) => properties.set(key1, value1), ); } } else { properties.set(key, value); } return properties; }; // add generated Integration YAML into existing Integration YAML static addYamlToIntegrationYaml = ( filename: string, camelYaml: string | undefined, restYaml: string, addREST: boolean, addRoutes: boolean, ): string => { const existing = camelYaml !== undefined ? CamelDefinitionYaml.yamlToIntegration(filename, camelYaml) : Integration.createNew(filename); const generated = CamelDefinitionYaml.yamlToIntegration(filename, restYaml); const flows: CamelElement[] = existing.spec.flows?.filter(f => !['RouteDefinition', 'RestDefinition'].includes(f.dslName)) || []; const restE: CamelElement[] = existing.spec.flows?.filter(f => f.dslName === 'RestDefinition') || []; const restG: CamelElement[] = generated.spec.flows?.filter(f => f.dslName === 'RestDefinition') || []; if (addREST) { flows.push(...restG); } else { flows.push(...restE); } const routeE: CamelElement[] = existing.spec.flows?.filter(f => f.dslName === 'RouteDefinition') || []; const routeG: CamelElement[] = generated.spec.flows?.filter(f => f.dslName === 'RouteDefinition') || []; if (addRoutes) { flows.push(...routeG); } else { flows.push(...routeE); } existing.spec.flows = flows; return CamelDefinitionYaml.integrationToYaml(existing); }; }