From e03fdd699212a0eb86c63f531a94ed15b02f3e33 Mon Sep 17 00:00:00 2001 From: Tyler Ayers Date: Tue, 28 Nov 2023 20:26:18 +0100 Subject: [PATCH] Updated with extensions support --- cli/bin/apigee-templater.js | 5 +- cli/package-lock.json | 12 +- cli/package.json | 4 +- cli/src/cli.ts | 63 ++++-- module/lib/interfaces.ts | 6 +- module/lib/plugins/auth.apikey.plugin.ts | 16 +- module/lib/plugins/auth.sf.plugin.ts | 8 +- ...{proxy.plugin.ts => final.proxy.plugin.ts} | 66 ++++--- ...w.plugin.ts => final.sharedflow.plugin.ts} | 0 module/lib/plugins/flow.callout.plugin.ts | 54 ++--- .../lib/plugins/mediation.assignm.plugin.ts | 184 ++++++++++++++++-- module/lib/plugins/mediation.exvars.plugin.ts | 29 ++- module/lib/plugins/targets.plugin.ts | 64 +++--- module/lib/plugins/traffic.quota.plugin.ts | 8 +- .../lib/plugins/traffic.spikearrest.plugin.ts | 8 +- module/lib/service.ts | 141 +++++++++----- module/package.json | 2 +- module/src/index.ts | 2 +- module/test/convert.test.ts | 6 +- module/test/data/input1.assignmsg.json | 56 ------ module/test/data/input1.extensions.json | 80 ++++++++ module/test/data/input1.exvars.json | 57 ------ module/test/data/input1.sharedflow.json | 23 ++- 23 files changed, 556 insertions(+), 338 deletions(-) rename module/lib/plugins/{proxy.plugin.ts => final.proxy.plugin.ts} (75%) rename module/lib/plugins/{flow.plugin.ts => final.sharedflow.plugin.ts} (100%) delete mode 100644 module/test/data/input1.assignmsg.json create mode 100644 module/test/data/input1.extensions.json delete mode 100644 module/test/data/input1.exvars.json diff --git a/cli/bin/apigee-templater.js b/cli/bin/apigee-templater.js index 4b2edd7..6d2ba1e 100755 --- a/cli/bin/apigee-templater.js +++ b/cli/bin/apigee-templater.js @@ -15,9 +15,12 @@ * limitations under the License. */ +import { ApigeeTemplater } from 'apigee-templater-module' import cli from "../dist/cli.js"; process.removeAllListeners('warning'); -const myCli = new cli(); +let apigeeTemplater = new ApigeeTemplater(); + +const myCli = new cli(apigeeTemplater); myCli.process(process.argv); \ No newline at end of file diff --git a/cli/package-lock.json b/cli/package-lock.json index 013c93d..5e9ee50 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,15 +1,15 @@ { "name": "apigee-templater", - "version": "1.0.1", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "apigee-templater", - "version": "1.0.1", + "version": "2.0.0", "license": "ISC", "dependencies": { - "apigee-templater-module": "^1.0.1", + "apigee-templater-module": "^2.0.2", "apigee-x-module": "^1.0.1", "arg": "^5.0.1", "axios": "^1.6.2", @@ -188,9 +188,9 @@ } }, "node_modules/apigee-templater-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/apigee-templater-module/-/apigee-templater-module-1.0.1.tgz", - "integrity": "sha512-+UL80p/uYgPwHTDui6MzEv3lVIsnNDCqmoRDyBTAzEIKN5g/d/fzpD1Bv6UD2vXuwvvm5jzYf92lXBiW3Cejhg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/apigee-templater-module/-/apigee-templater-module-2.0.2.tgz", + "integrity": "sha512-zGratwvLfZC/Bt7OQwtDbS+bZxWq6By4kRDLqYAee9wcQxxUvm+5zu4exJ9sSQzMF0d8mru7F0AQUaWiq82Pjg==", "dependencies": { "archiver": "^5.3.0", "fs": "0.0.1-security", diff --git a/cli/package.json b/cli/package.json index 36a7ab7..906e1e8 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "apigee-templater", - "version": "1.0.2", + "version": "2.0.0", "description": "A CLI to easily template and deploy Apigee proxies.", "homepage": "https://github.com/tyayers/apigee-templater", "bugs": "https://github.com/tyayers/apigee-templater/issues", @@ -28,7 +28,7 @@ "author": "tayers", "license": "ISC", "dependencies": { - "apigee-templater-module": "^1.0.1", + "apigee-templater-module": "^2.0.2", "apigee-x-module": "^1.0.1", "arg": "^5.0.1", "axios": "^1.6.2", diff --git a/cli/src/cli.ts b/cli/src/cli.ts index e9edf76..ffa3157 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -19,7 +19,7 @@ import fs from 'fs' import { performance } from 'perf_hooks' import inquirer from 'inquirer' import chalk from 'chalk' -import { ApigeeTemplateInput, ApigeeTemplateService, ApigeeGenerator, GenerateResult } from 'apigee-templater-module' +import { ApigeeTemplateInput, ApigeeTemplateService, ApigeeTemplater, GenerateResult } from 'apigee-templater-module' import { ApigeeService, ApiManagementInterface, EnvironmentGroup, EnvironmentGroupAttachment, ProxyDeployment, ProxyRevision } from 'apigee-x-module' import axios from 'axios'; @@ -51,7 +51,18 @@ export class cli { * * @type {ApigeeTemplateService} */ - apigeeGenerator: ApigeeTemplateService = new ApigeeGenerator(); + apigeeTemplater: ApigeeTemplateService; + + /** + * Constructor + * @param name + */ + constructor(templaterInstance?: ApigeeTemplateService) { + if (templaterInstance) + this.apigeeTemplater = templaterInstance; + else + this.apigeeTemplater = new ApigeeTemplater(); + } /** * Parses the user inputs @@ -216,12 +227,18 @@ export class cli { * @param {cliArgs} args The user input args to the process */ async process(args: string[]) { - let options: cliArgs = this.parseArgumentsIntoOptions(args) - if (options.keyPath) process.env.GOOGLE_APPLICATION_CREDENTIALS = options.keyPath - if (options.verbose) this.logVerbose(JSON.stringify(options), 'options:') + let options: cliArgs = this.parseArgumentsIntoOptions(args); + if (options.verbose && fs.existsSync("apigee-templater-log.txt")) { + // Delete old log file, if it exists + fs.unlinkSync("apigee-templater-log.txt") + } + + if (options.keyPath) process.env.GOOGLE_APPLICATION_CREDENTIALS = options.keyPath; console.log(`${chalk.bold(chalk.magentaBright('> Welcome to Apigee Templater'))}, ${chalk.green('use -h for more command line options.')} `) + if (options.verbose) this.logVerbose(JSON.stringify(options, null, 2), 'start:'); + if (options.help) { this.printHelp() return @@ -248,7 +265,7 @@ export class cli { try { options = await this.promptForMissingOptions(options) } catch (error) { - console.error(`${chalk.redBright('! Error:')} Error during prompt for inputs, that's all we know.`) + console.error(`${chalk.redBright('! Error:')} Error during prompt for inputs.`) if (options.verbose) this.logVerbose(JSON.stringify(error), 'prompt error:') } @@ -265,7 +282,9 @@ export class cli { target: { name: 'default', table: options.targetBigQueryTable - } + }, + parameters: {}, + extensionSteps: [] } ] }); @@ -280,7 +299,9 @@ export class cli { target: { name: 'default', url: options.targetUrl - } + }, + parameters: {}, + extensionSteps: [] } ] }); @@ -301,7 +322,7 @@ export class cli { process.env.PROJECT = (await this.apigeeService.getOrg()).toString(); } - if (options.verbose) this.logVerbose(`Set Project to ${process.env.PROJECT}`, 'env:') + if (options.verbose) this.logVerbose(`Project set to ${process.env.PROJECT}`, 'project:') if (options.filter) { // users can add their own preprocessing filter scripts here @@ -311,12 +332,13 @@ export class cli { if (options.verbose) this.logVerbose(options.input, 'template:') - this.apigeeGenerator.convertStringToProxyInput(options.input).then((inputTemplate: ApigeeTemplateInput) => { + this.apigeeTemplater.convertStringToProxyInput(options.input).then((inputTemplate: ApigeeTemplateInput) => { if (options.basePath) inputTemplate.endpoints[0].basePath = options.basePath; if (options.name) inputTemplate.name = options.name; - this.apigeeGenerator.generateProxy(inputTemplate, _proxyDir).then((generateResult: GenerateResult) => { + this.apigeeTemplater.generateProxy(inputTemplate, _proxyDir).then((generateResult: GenerateResult) => { + if (options.verbose) this.logVerbose(JSON.stringify(generateResult, null, 2), 'template result:'); if (inputTemplate.sharedFlow && generateResult && generateResult.template) console.log(`${chalk.green('>')} Flow ${chalk.bold(chalk.blue(generateResult.template.name))} generated to ${chalk.magentaBright(chalk.bold(generateResult.localPath))} in ${chalk.bold(chalk.green(Math.round(generateResult.duration) + ' milliseconds'))}.`); else if (generateResult && generateResult.template) @@ -335,7 +357,7 @@ export class cli { this.apigeeService.deployFlowRevision(options.environment, generateResult.template.name, updateResult.revision, options.deployServiceAccount).then((deployResult: ProxyDeployment) => { const endTime = performance.now() const duration = endTime - startTime - if (options.verbose) this.logVerbose(JSON.stringify(generateResult), 'deploy result:') + if (options.verbose) this.logVerbose(JSON.stringify(generateResult), 'deploy result:'); if (generateResult && generateResult.template) { console.log(`${chalk.green('>')} Flow ${chalk.bold(chalk.blue(generateResult.template.name + ' version ' + updateResult.revision))} deployed to environment ${chalk.bold(chalk.magentaBright(options.environment))} in ${chalk.bold(chalk.green(Math.round(duration) + ' milliseconds'))}.`); } @@ -352,9 +374,9 @@ export class cli { if (updateResult && updateResult.revision) { if (generateResult && generateResult.template) { this.apigeeService.deployProxyRevision(options.environment, generateResult.template.name, updateResult.revision, options.deployServiceAccount).then((deployResult: ProxyDeployment) => { - const endTime = performance.now() - const duration = endTime - startTime - if (options.verbose) this.logVerbose(JSON.stringify(generateResult), 'deploy result:') + const endTime = performance.now(); + const duration = endTime - startTime; + if (options.verbose) this.logVerbose(JSON.stringify(generateResult), 'deploy result:'); if (generateResult && generateResult.template) { console.log(`${chalk.green('>')} Proxy ${chalk.bold(chalk.blue(generateResult.template.name + ' version ' + updateResult.revision))} deployed to environment ${chalk.bold(chalk.magentaBright(options.environment))} in ${chalk.bold(chalk.green(Math.round(duration) + ' milliseconds'))}.`) @@ -409,10 +431,15 @@ export class cli { * @param {string} label An optional label as prefix label */ logVerbose(input: string, label?: string) { + let logString: string = '> ' + input + '\n'; if (label) - console.log(`${chalk.cyanBright('> ' + label + ' ' + input)}`); - else - console.log(`${chalk.cyanBright('> ' + input)} `) + logString = '> ' + label + ' ' + input + '\n'; + + console.log(`${chalk.cyanBright(logString)}`); + fs.appendFile('apigee-templater-log.txt', logString, function (err) { + if (err) + console.error(err); + }); } } diff --git a/module/lib/interfaces.ts b/module/lib/interfaces.ts index 7d8365e..199e041 100644 --- a/module/lib/interfaces.ts +++ b/module/lib/interfaces.ts @@ -22,8 +22,6 @@ export class proxyTarget { table?= ''; authScopes?= []; headers?: { [key: string]: string } = {}; - preFlows?: string[] = []; - postFlows?: string[] = []; } /** A proxy endpoint describes a basepath, targets and other proxy features */ @@ -138,8 +136,8 @@ export class PlugInFilePolicyConfig { /** Profile definition with plugins to be used for conversion */ export class ApigeeTemplateProfile { plugins: ApigeeTemplatePlugin[] = []; - flowPlugins: {[key: string]: ApigeeTemplatePlugin} = {}; - finalizePlugin?: ApigeeTemplatePlugin; + extensionPlugins: {[key: string]: ApigeeTemplatePlugin} = {}; + finalizePlugins: ApigeeTemplatePlugin[] = []; } export interface ApigeeTemplatePlugin { diff --git a/module/lib/plugins/auth.apikey.plugin.ts b/module/lib/plugins/auth.apikey.plugin.ts index 3a6ffe6..7f21dac 100644 --- a/module/lib/plugins/auth.apikey.plugin.ts +++ b/module/lib/plugins/auth.apikey.plugin.ts @@ -28,14 +28,14 @@ import { ApigeeTemplatePlugin, PlugInResult, proxyEndpoint, authTypes, policyIns */ export class AuthApiKeyPlugin implements ApigeeTemplatePlugin { apikey_snippet = ` - - Verify API Key + + VA-VerifyKey `; removekey_snippet = ` - - Remove Query Param apikey + + AM-RemoveKey @@ -64,18 +64,18 @@ export class AuthApiKeyPlugin implements ApigeeTemplatePlugin { fileResult.files = [ { policyConfig: { - name: 'VerifyApiKey', + name: 'VA-VerifyKey', triggers: [policyInsertPlaces.preRequest] }, - path: '/policies/VerifyApiKey.xml', + path: '/policies/VA-VerifyKey.xml', contents: this.apikey_template({}) }, { policyConfig: { - name: 'RemoveApiKey', + name: 'AM-RemoveApiKey', triggers: [policyInsertPlaces.preRequest] }, - path: '/policies/RemoveApiKey.xml', + path: '/policies/AM-RemoveApiKey.xml', contents: this.removekey_template({}) } ]; diff --git a/module/lib/plugins/auth.sf.plugin.ts b/module/lib/plugins/auth.sf.plugin.ts index a2d86c7..8660054 100644 --- a/module/lib/plugins/auth.sf.plugin.ts +++ b/module/lib/plugins/auth.sf.plugin.ts @@ -28,8 +28,8 @@ import { ApigeeTemplatePlugin, proxyEndpoint, authTypes, PlugInResult, policyIns */ export class AuthSfPlugin implements ApigeeTemplatePlugin { snippet = ` - - VerifyJWT + + FC-VerifyFlow @@ -69,10 +69,10 @@ export class AuthSfPlugin implements ApigeeTemplatePlugin { fileResult.files = [ { policyConfig: { - name: 'VerifyJWT', + name: 'FC-VerifyFlow', triggers: [policyInsertPlaces.preRequest] }, - path: '/policies/VerifyJWT.xml', + path: '/policies/FC-VerifyFlow.xml', contents: this.template({ audience: authConfig.parameters.audience, roles: authConfig.parameters.roles, diff --git a/module/lib/plugins/proxy.plugin.ts b/module/lib/plugins/final.proxy.plugin.ts similarity index 75% rename from module/lib/plugins/proxy.plugin.ts rename to module/lib/plugins/final.proxy.plugin.ts index 07dfb18..0859d40 100644 --- a/module/lib/plugins/proxy.plugin.ts +++ b/module/lib/plugins/final.proxy.plugin.ts @@ -28,31 +28,47 @@ import { ApigeeTemplatePlugin, PlugInResult, policyInsertPlaces, proxyEndpoint } */ export class ProxyPlugin implements ApigeeTemplatePlugin { snippet = ` - - - - {{#each preRequestPolicies}} - - {{this}} - - {{/each}} - - - - - - - - - - - - {{basePath}} - - - {{targetName}} - - `; + + + + {{#each preRequestPolicies}} + + {{this}} + + {{/each}} + + + {{#each postRequestPolicies}} + + {{this}} + + {{/each}} + + + + + + {{#each preResponsePolicies}} + + {{this}} + + {{/each}} + + + {{#each postResponsePolicies}} + + {{this}} + + {{/each}} + + + + {{basePath}} + + + {{targetName}} + +`; template = Handlebars.compile(this.snippet); diff --git a/module/lib/plugins/flow.plugin.ts b/module/lib/plugins/final.sharedflow.plugin.ts similarity index 100% rename from module/lib/plugins/flow.plugin.ts rename to module/lib/plugins/final.sharedflow.plugin.ts diff --git a/module/lib/plugins/flow.callout.plugin.ts b/module/lib/plugins/flow.callout.plugin.ts index 7897f7f..a2f368f 100644 --- a/module/lib/plugins/flow.callout.plugin.ts +++ b/module/lib/plugins/flow.callout.plugin.ts @@ -17,21 +17,26 @@ import Handlebars from 'handlebars' import { ApigeeTemplatePlugin, proxyEndpoint, PlugInResult, policyInsertPlaces } from '../interfaces.js' +export class FlowCalloutConfig { + flowName: string = ""; + continueOnError: boolean = true; + triggers: policyInsertPlaces[] = []; +} + /** - * Plugin for traffic quota templating + * Plugin for making shared flow callouts * @date 2/14/2022 - 8:17:36 AM * * @export - * @class QuotaPlugin - * @typedef {QuotaPlugin} - * @implements {ApigeeTemplatePlugin} + * @class FlowCalloutPlugin + * @typedef {FlowCalloutPlugin} + * @implements {FlowCalloutPlugin} */ export class FlowCalloutPlugin implements ApigeeTemplatePlugin { sharedFlowSnippet = ` - + FC-{{flowName}} - {{flowName}} `; @@ -47,37 +52,20 @@ export class FlowCalloutPlugin implements ApigeeTemplatePlugin { * @param {Map} processingVars * @return {Promise} */ - applyTemplate (inputConfig: proxyEndpoint): Promise { + applyTemplate (inputConfig: proxyEndpoint, additionalData?: any): Promise { return new Promise((resolve) => { const fileResult: PlugInResult = new PlugInResult(this.constructor.name); - // Now set pre target flow callouts - if (inputConfig.target.preFlows && inputConfig.target.preFlows.length > 0) { - for (const flow of inputConfig.target.preFlows) { - fileResult.files.push({ - policyConfig: { - name: "FC-" + flow, - triggers: [policyInsertPlaces.preTarget] - }, - path: "/policies/FC-" + flow + ".xml", - contents: this.template({flowName: flow}) - }); - } - } + let config: FlowCalloutConfig = additionalData; - // Now set post target flow callouts - if (inputConfig.target.postFlows && inputConfig.target.postFlows.length > 0) { - for (const flow of inputConfig.target.postFlows) { - fileResult.files.push({ - policyConfig: { - name: "FC-" + flow, - triggers: [policyInsertPlaces.postTarget] - }, - path: "/policies/FC-" + flow + ".xml", - contents: this.template({flowName: flow}) - }); - } - } + fileResult.files.push({ + policyConfig: { + name: "FC-" + config.flowName, + triggers: config.triggers + }, + path: '/policies/FC-' + config.flowName + '.xml', + contents: this.template(config) + }); resolve(fileResult) }) diff --git a/module/lib/plugins/mediation.assignm.plugin.ts b/module/lib/plugins/mediation.assignm.plugin.ts index f042734..38c5f19 100644 --- a/module/lib/plugins/mediation.assignm.plugin.ts +++ b/module/lib/plugins/mediation.assignm.plugin.ts @@ -25,10 +25,10 @@ export class AssignMessageConfig { ignoreUnresolvedVariables: boolean = false; assignTo: string = ""; assignVariables: AssignVariableConfig[] = []; - add: AssignBlockConfig[] = []; - copy: AssignBlockConfig[] = []; - remove: AssignBlockConfig[] = []; - set: AssignBlockConfig[] = []; + add?: AssignContentConfig; + copy?: AssignContentConfig; + remove?: AssignContentConfig; + set?: AssignSetContentConfig; } export class AssignVariableConfig { @@ -36,11 +36,12 @@ export class AssignVariableConfig { propertySetRef: string = ""; ref: string = ""; resourceURL: string = ""; - template: string = ""; + templateVariable: string = ""; + templateMessage: string = ""; value: string = ""; } -export class AssignBlockConfig { +export class AssignContentConfig { formParams: AssignElementConfig[] = []; headers: AssignElementConfig[] = []; queryParams: AssignElementConfig[] = []; @@ -51,6 +52,17 @@ export class AssignBlockConfig { version: boolean = false; } +export class AssignSetContentConfig { + formParams: AssignElementConfig[] = []; + headers: AssignElementConfig[] = []; + queryParams: AssignElementConfig[] = []; + path?: string; + payload?: {contentType: string, variablePrefix: string, variableSuffix: string, newPayload: string}; + statusCode?: string; + verb?: string; + version?: string; +} + export class AssignElementConfig { name: string = ""; value: string = ""; @@ -71,20 +83,163 @@ export class AssignMessagePlugin implements ApigeeTemplatePlugin { AM-{{name}} + {{#if ignoreUnresolvedVariables}} + {{ignoreUnresolvedVariables}} + {{/if}} + + {{#each assignVariables}} + + {{this.name}} + {{this.propertySetRef}} + {{this.ref}} + {{this.resourceURL}} + {{#if this.templateMessage}} + + {{/if}} + {{#if this.templateVariable}} + + {{/if}} + {{this.value}} + + {{/each}} + + {{#if add}} - {{#each add}} + {{#if add.formParams}} - {{#each this.formParmas}} + {{#each add.formParmas}} {{this.value}} {{/each}} - {{/each}} - + {{/if}} + {{#if add.headers}} + + {{#each add.headers}} +
{{this.value}}
+ {{/each}} +
+ {{/if}} + {{#if add.queryParams}} + + {{#each add.queryParams}} + {{this.value}} + {{/each}} + + {{/if}} + + {{/if}} - message - {{prefix}} - {{ignoreUnresolved}} -`; + {{#if copy}} + + {{#if copy.formParams}} + + {{#each copy.formParams}} + {{this.value}} + {{/each}} + + {{/if}} + {{#if copy.headers}} + + {{#each copy.headers}} +
{{this.value}}
+ {{/each}} +
+ {{/if}} + {{#if copy.queryParams}} + + {{#each copy.queryParams}} + {{this.value}} + {{/each}} + + {{/if}} + {{#if copy.path}} + {{copy.path}} + {{/if}} + {{#if copy.payload}} + {{copy.payload}} + {{/if}} + {{#if copy.statusCode}} + {{copy.statusCode}} + {{/if}} + {{#if copy.verb}} + {{copy.verb}} + {{/if}} + {{#if copy.version}} + {{copy.version}} + {{/if}} +
+ {{/if}} + + {{#if remove}} + + {{#if remove.formParams}} + + {{#each remove.formParams}} + {{this.value}} + {{/each}} + + {{/if}} + {{#if remove.headers}} + + {{#each remove.headers}} +
{{this.value}}
+ {{/each}} +
+ {{/if}} + {{#if remove.queryParams}} + + {{#each remove.queryParams}} + {{this.value}} + {{/each}} + + {{/if}} + {{#if remove.payload}} + {{remove.payload}} + {{/if}} +
+ {{/if}} + + {{#if set}} + + {{#if set.formParams}} + + {{#each set.formParmas}} + {{this.value}} + {{/each}} + + {{/if}} + {{#if set.headers}} + + {{#each set.headers}} +
{{this.value}}
+ {{/each}} +
+ {{/if}} + {{#if set.queryParams}} + + {{#each set.queryParams}} + {{this.value}} + {{/each}} + + {{/if}} + {{#if set.path}} + {{set.path}} + {{/if}} + {{#if set.payload}} + EV-{{name}} -{{#each URIPaths}} + {{#each URIPaths}} {{this.pattern}} -{{/each}} + {{/each}} -{{#each URIPaths}} + {{#each queryParams}} {{this.pattern}} -{{/each}} -{{#each headers}} + {{/each}} + {{#each headers}}
{{this.pattern}}
-{{/each}} -{{#each formParams}} + {{/each}} + {{#each formParams}} {{this.pattern}} -{{/each}} -{{#each variables}} + {{/each}} + {{#each variables}} {{this.pattern}} -{{/each}} -{{#each JSONPaths}} + {{/each}} + {{#each JSONPaths}} {{this.path}} -{{/each}} -{{#each XMLPaths}} + {{/each}} + {{#each XMLPaths}} {{this.path}} -{{/each}} + {{/each}} message {{prefix}} {{ignoreUnresolved}} @@ -117,7 +117,6 @@ export class ExtractVariablesPlugin implements ApigeeTemplatePlugin { const fileResult: PlugInResult = new PlugInResult(this.constructor.name) let config: ExtractVariablesConfig = additionalData; - console.log(JSON.stringify(config)); fileResult.files.push({ policyConfig: { diff --git a/module/lib/plugins/targets.plugin.ts b/module/lib/plugins/targets.plugin.ts index 2430ae5..3154ac1 100644 --- a/module/lib/plugins/targets.plugin.ts +++ b/module/lib/plugins/targets.plugin.ts @@ -15,7 +15,7 @@ */ import Handlebars from 'handlebars' -import { ApigeeTemplatePlugin, PlugInResult, proxyEndpoint } from '../interfaces.js' +import { ApigeeTemplatePlugin, PlugInResult, policyInsertPlaces, proxyEndpoint } from '../interfaces.js' /** * Plugin for generating targets @@ -32,28 +32,28 @@ export class TargetsPlugin implements ApigeeTemplatePlugin { {{#if preflow_request_assign}} - - Set-Target-Message - + + AM-SetTargetHeaders + {{/if}} + {{#each preTargetPolicies}} + + {{this}} + + {{/each}} - + - {{#each pre_flows}} - - FC-{{this}} - - {{/each}} - {{#each post_flows}} + {{#each postTargetPolicies}} - FC-{{this}} + {{this}} - {{/each}} + {{/each}} @@ -62,8 +62,8 @@ export class TargetsPlugin implements ApigeeTemplatePlugin { `; preFlowAssignMessageSnippet = ` - - Set-Target-Message + + AM-SetTargetHeaders @@ -77,17 +77,9 @@ export class TargetsPlugin implements ApigeeTemplatePlugin { `; - sharedFlowSnippet = ` - - FC-{{flowName}} - - {{flowName}} - - `; - template = Handlebars.compile(this.snippet); messageAssignTemplate = Handlebars.compile(this.preFlowAssignMessageSnippet); - sharedFlowTemplate = Handlebars.compile(this.sharedFlowSnippet); + /** * Templates the targets configurations * @date 2/14/2022 - 8:15:57 AM @@ -102,6 +94,24 @@ export class TargetsPlugin implements ApigeeTemplatePlugin { if (inputConfig.target) { + const preTargetPolicies: string[] = []; + const postTargetPolicies: string[] = []; + + // Now collect all of our policies that should be triggered + if (inputConfig.fileResults) + for (let plugResult of inputConfig.fileResults) { + for (let fileResult of plugResult.files) { + if (fileResult.policyConfig) { + for (let policyTrigger of fileResult.policyConfig.triggers) { + if (policyTrigger == policyInsertPlaces.preTarget) + preTargetPolicies.push(fileResult.policyConfig.name); + else if (policyTrigger == policyInsertPlaces.postTarget) + postTargetPolicies.push(fileResult.policyConfig.name); + } + } + } + } + // Make sure the target has the https prefix if (inputConfig.target.url && !inputConfig.target.url.startsWith("http")) { inputConfig.target.url = "https://" + inputConfig.target.url; @@ -111,8 +121,8 @@ export class TargetsPlugin implements ApigeeTemplatePlugin { targetName: inputConfig.target.name, targetUrl: inputConfig.target.url, preflow_request_assign: (inputConfig.target.headers && Object.keys(inputConfig.target.headers).length > 0), - pre_flows: inputConfig.target.preFlows, - post_flows: inputConfig.target.postFlows + preTargetPolicies: preTargetPolicies, + postTargetPolicies: postTargetPolicies }; fileResult.files = [ @@ -128,7 +138,7 @@ export class TargetsPlugin implements ApigeeTemplatePlugin { }; fileResult.files.push({ - path: "/policies/Set-Target-Message.xml", + path: "/policies/AM-SetTargetHeaders.xml", contents: this.messageAssignTemplate(assignContext) }); } diff --git a/module/lib/plugins/traffic.quota.plugin.ts b/module/lib/plugins/traffic.quota.plugin.ts index f81fbed..a6f5395 100644 --- a/module/lib/plugins/traffic.quota.plugin.ts +++ b/module/lib/plugins/traffic.quota.plugin.ts @@ -28,8 +28,8 @@ import { ApigeeTemplatePlugin, proxyEndpoint, PlugInResult, policyInsertPlaces } */ export class QuotaPlugin implements ApigeeTemplatePlugin { snippet = ` - - Quota-{{index}} + + Q-Quota{{index}} 1 @@ -63,10 +63,10 @@ export class QuotaPlugin implements ApigeeTemplatePlugin { if (inputConfig.quotas[i].count > 0) { fileResult.files.push({ policyConfig: { - name: 'Quota-' + (Number(i) + 1).toString(), + name: 'Q-Quota' + (Number(i) + 1).toString(), triggers: [policyInsertPlaces.preRequest] }, - path: '/policies/Quota-' + (Number(i) + 1).toString() + '.xml', + path: '/policies/Q-Quota' + (Number(i) + 1).toString() + '.xml', contents: this.template({ index: (Number(i) + 1), count: inputConfig.quotas[i].count, diff --git a/module/lib/plugins/traffic.spikearrest.plugin.ts b/module/lib/plugins/traffic.spikearrest.plugin.ts index 10fa559..ec021bb 100644 --- a/module/lib/plugins/traffic.spikearrest.plugin.ts +++ b/module/lib/plugins/traffic.spikearrest.plugin.ts @@ -28,8 +28,8 @@ import { ApigeeTemplatePlugin, proxyEndpoint, PlugInResult, policyInsertPlaces } */ export class SpikeArrestPlugin implements ApigeeTemplatePlugin { snippet = ` - - Spike Arrest-1 + + SA-SpikeArrest @@ -54,10 +54,10 @@ export class SpikeArrestPlugin implements ApigeeTemplatePlugin { fileResult.files = [ { policyConfig: { - name: 'Spike-Arrest-1', + name: 'SA-SpikeArrest', triggers: [policyInsertPlaces.preRequest] }, - path: '/policies/Spike-Arrest-1.xml', + path: '/policies/SA-SpikeArrest.xml', contents: this.template({ rate: inputConfig.spikeArrest.rate }) diff --git a/module/lib/service.ts b/module/lib/service.ts index 50e7f33..8488e53 100644 --- a/module/lib/service.ts +++ b/module/lib/service.ts @@ -19,8 +19,8 @@ import fs from 'fs' import path from 'path' import { performance } from 'perf_hooks' import { ApigeeTemplateService, ApigeeTemplateInput, ApigeeTemplateProfile, PlugInResult, PlugInFile, ApigeeConverterPlugin, GenerateResult, proxyEndpoint } from './interfaces.js' -import { ProxyPlugin } from './plugins/proxy.plugin.js' -import { FlowPlugin } from './plugins/flow.plugin.js' +import { ProxyPlugin } from './plugins/final.proxy.plugin.js' +import { FlowPlugin } from './plugins/final.sharedflow.plugin.js' import { FlowCalloutPlugin } from './plugins/flow.callout.plugin.js' import { TargetsPlugin } from './plugins/targets.plugin.js' import { TargetsBigQueryPlugin } from './plugins/targets.bigquery.plugin.js' @@ -32,17 +32,18 @@ import { Json1Converter } from './converters/json1.plugin.js' import { Json2Converter } from './converters/json2.plugin.js' import { OpenApiV3Converter } from './converters/openapiv3.yaml.plugin.js' import { ExtractVariablesPlugin } from './plugins/mediation.exvars.plugin.js' +import { AssignMessagePlugin } from './plugins/mediation.assignm.plugin.js' /** * ApigeeGenerator runs the complete templating operation with all injected plugins * @date 2/14/2022 - 8:22:47 AM * * @export - * @class ApigeeGenerator - * @typedef {ApigeeGenerator} + * @class ApigeeTemplater + * @typedef {ApigeeTemplater} * @implements {ApigeeTemplateService} */ -export class ApigeeGenerator implements ApigeeTemplateService { +export class ApigeeTemplater implements ApigeeTemplateService { converterPlugins: ApigeeConverterPlugin[] = [ new Json1Converter(), new Json2Converter(), @@ -55,37 +56,46 @@ export class ApigeeGenerator implements ApigeeTemplateService { new SpikeArrestPlugin(), new AuthApiKeyPlugin(), new AuthSfPlugin(), - new QuotaPlugin(), - new FlowCalloutPlugin(), - new TargetsPlugin(), - new ProxyPlugin() + new QuotaPlugin() ], - flowPlugins: { - "ExtractVariables": new ExtractVariablesPlugin() + extensionPlugins: { + "ExtractVariables": new ExtractVariablesPlugin(), + "AssignMessage": new AssignMessagePlugin(), + "FlowCallout": new FlowCalloutPlugin() }, - finalizePlugin: new ProxyPlugin() + finalizePlugins: [ + new TargetsPlugin(), + new ProxyPlugin() + ] }, sharedflow: { plugins: [ new SpikeArrestPlugin(), new AuthApiKeyPlugin(), new AuthSfPlugin(), - new QuotaPlugin(), - new TargetsPlugin() + new QuotaPlugin() ], - flowPlugins: {}, - finalizePlugin: new FlowPlugin() + extensionPlugins: { + "ExtractVariables": new ExtractVariablesPlugin(), + "AssignMessage": new AssignMessagePlugin() + }, + finalizePlugins: [ + new TargetsPlugin(), + new FlowPlugin() + ] }, bigquery: { plugins: [ new SpikeArrestPlugin(), new AuthApiKeyPlugin(), new AuthSfPlugin(), - new QuotaPlugin(), - new TargetsBigQueryPlugin() + new QuotaPlugin() ], - flowPlugins: {}, - finalizePlugin: new ProxyPlugin() + extensionPlugins: {}, + finalizePlugins: [ + new TargetsBigQueryPlugin(), + new ProxyPlugin() + ] } }; @@ -211,12 +221,13 @@ export class ApigeeGenerator implements ApigeeTemplateService { endpoint.fileResults = results; // Now call flow plugins - let extensionPromise = this.callFlowPlugins(genInput, endpoint, newOutputDir); + let extensionPromise = this.callExtensionPlugins(genInput, endpoint, newOutputDir); promises.push(extensionPromise); - extensionPromise.then((results) => { - endpoint.fileResults = endpoint.fileResults?.concat(results); + extensionPromise.then((extensionResults) => { + endpoint.fileResults = endpoint.fileResults?.concat(extensionResults); + // And finally call finalizer plugin - let finalizerPromise = this.callFinalizerPlugin(genInput, endpoint, newOutputDir); + let finalizerPromise = this.callFinalizerPlugins(genInput, endpoint, newOutputDir); promises.push(finalizerPromise); finalizerPromise.then((finalizerResults) => { endpoint.fileResults = endpoint.fileResults?.concat(finalizerResults); @@ -234,12 +245,12 @@ export class ApigeeGenerator implements ApigeeTemplateService { genInput.sharedFlow.fileResults = results; // Now call flow plugins - let extensionPromise = this.callFlowPlugins(genInput, genInput.sharedFlow, newOutputDir); + let extensionPromise = this.callExtensionPlugins(genInput, genInput.sharedFlow, newOutputDir); promises.push(extensionPromise); - extensionPromise.then((results) => { + extensionPromise.then((extensionResults) => { if (genInput.sharedFlow) { - genInput.sharedFlow.fileResults = genInput.sharedFlow.fileResults?.concat(results); - let finalizerPromise = this.callFinalizerPlugin(genInput, genInput.sharedFlow, newOutputDir); + genInput.sharedFlow.fileResults = genInput.sharedFlow.fileResults?.concat(extensionResults); + let finalizerPromise = this.callFinalizerPlugins(genInput, genInput.sharedFlow, newOutputDir); promises.push(finalizerPromise); finalizerPromise.then((finalizerResults) => { // Call finalizer plugin for flow @@ -311,23 +322,18 @@ export class ApigeeGenerator implements ApigeeTemplateService { }); } - callFlowPlugins(genInput: ApigeeTemplateInput, endpoint: proxyEndpoint, newOutputDir: string): Promise { + callFinalizerPlugins(genInput: ApigeeTemplateInput, endpoint: proxyEndpoint, newOutputDir: string): Promise { return new Promise((resolve, reject) => { if (process.env.PROJECT) { + if (!endpoint.parameters) endpoint.parameters = {}; endpoint.parameters.PROJECT = process.env.PROJECT; } if (Object.keys(this.profiles).includes(genInput.profile)) { let promises: Promise[] = []; - if (endpoint.extensionSteps) { - for (const step of endpoint.extensionSteps) { - let stepDefinition: {type: string} = step; - if (this.profiles[genInput.profile].flowPlugins[stepDefinition.type]) { - // We have a plugin - promises.push(this.profiles[genInput.profile].flowPlugins[stepDefinition.type].applyTemplate(endpoint, step)); - } - } + for (const plugin of this.profiles[genInput.profile].finalizePlugins) { + promises.push(plugin.applyTemplate(endpoint)); } Promise.all(promises).then((values) => { @@ -339,35 +345,70 @@ export class ApigeeGenerator implements ApigeeTemplateService { } resolve(values); }).catch((error) => { - console.error("Error calling extension plugins, aborting."); - reject("Error calling extension plugins, aborting."); + console.error("Error calling plugins, aborting."); + reject("Error calling plugins, aborting."); }); } }); } - callFinalizerPlugin(genInput: ApigeeTemplateInput, endpoint: proxyEndpoint, newOutputDir: string): Promise { + callExtensionPlugins(genInput: ApigeeTemplateInput, endpoint: proxyEndpoint, newOutputDir: string): Promise { return new Promise((resolve, reject) => { if (process.env.PROJECT) { - if (!endpoint.parameters) endpoint.parameters = {}; endpoint.parameters.PROJECT = process.env.PROJECT; } if (Object.keys(this.profiles).includes(genInput.profile)) { let promises: Promise[] = []; - this.profiles[genInput.profile].finalizePlugin?.applyTemplate(endpoint).then((result: PlugInResult) => { - result.files.forEach((file: PlugInFile) => { - fs.mkdirSync(path.dirname(newOutputDir + file.path), { recursive: true }) - fs.writeFileSync(newOutputDir + file.path, file.contents) - }); + if (endpoint.extensionSteps) { + for (const step of endpoint.extensionSteps) { + let stepDefinition: {type: string} = step; + if (this.profiles[genInput.profile].extensionPlugins[stepDefinition.type]) { + // We have a plugin + promises.push(this.profiles[genInput.profile].extensionPlugins[stepDefinition.type].applyTemplate(endpoint, step)); + } + } + } - resolve([result]); + Promise.all(promises).then((values) => { + for (let newResult of values) { + for (let file of newResult.files) { + fs.mkdirSync(path.dirname(newOutputDir + file.path), { recursive: true }); + fs.writeFileSync(newOutputDir + file.path, file.contents); + } + } + resolve(values); }).catch((error) => { - console.error("Error calling plugins, aborting."); - reject("Error calling plugins, aborting."); - }); + console.error("Error calling extension plugins, aborting."); + reject("Error calling extension plugins, aborting."); + }); } }); } + +// callFinalizerPlugins(genInput: ApigeeTemplateInput, endpoint: proxyEndpoint, newOutputDir: string): Promise { +// return new Promise((resolve, reject) => { +// if (process.env.PROJECT) { +// if (!endpoint.parameters) endpoint.parameters = {}; +// endpoint.parameters.PROJECT = process.env.PROJECT; +// } + +// if (Object.keys(this.profiles).includes(genInput.profile)) { +// let promises: Promise[] = []; + +// this.profiles[genInput.profile].finalizePlugin?.applyTemplate(endpoint).then((result: PlugInResult) => { +// result.files.forEach((file: PlugInFile) => { +// fs.mkdirSync(path.dirname(newOutputDir + file.path), { recursive: true }) +// fs.writeFileSync(newOutputDir + file.path, file.contents) +// }); + +// resolve([result]); +// }).catch(() => { +// console.error("Error calling plugins, aborting."); +// reject("Error calling plugins, aborting."); +// }); +// } +// }); +// } } diff --git a/module/package.json b/module/package.json index 3cde367..c3d32b8 100644 --- a/module/package.json +++ b/module/package.json @@ -1,6 +1,6 @@ { "name": "apigee-templater-module", - "version": "1.0.1", + "version": "2.0.2", "description": "This library provides templating services for Apigee X proxies.", "homepage": "https://github.com/tyayers/apigee-templater", "bugs": "https://github.com/tyayers/apigee-templater/issues", diff --git a/module/src/index.ts b/module/src/index.ts index 8a151da..15620e5 100644 --- a/module/src/index.ts +++ b/module/src/index.ts @@ -18,7 +18,7 @@ export * from '../lib/interfaces.js' export * from '../lib/service.js' export * from '../lib/plugins/auth.apikey.plugin.js' export * from '../lib/plugins/auth.sf.plugin.js' -export * from '../lib/plugins/proxy.plugin.js' +export * from '../lib/plugins/final.proxy.plugin.js' export * from '../lib/plugins/targets.plugin.js' export * from '../lib/plugins/targets.bigquery.plugin.js' export * from '../lib/plugins/traffic.quota.plugin.js' diff --git a/module/test/convert.test.ts b/module/test/convert.test.ts index ff3d7c1..5af0f0d 100644 --- a/module/test/convert.test.ts +++ b/module/test/convert.test.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { ApigeeTemplateService, ApigeeGenerator } from '../src' +import { ApigeeTemplateService, ApigeeTemplater } from '../src' import fs from 'fs' import { expect } from 'chai' import { describe } from 'mocha' console.log('starting') -const apigeeGenerator: ApigeeTemplateService = new ApigeeGenerator() +const apigeeGenerator: ApigeeTemplateService = new ApigeeTemplater() describe('Generate simple normal JSON 1 proxy', () => { return it('should produce a valid proxy bundle', () => { @@ -46,7 +46,7 @@ describe('Generate simple JSON 1 shared flow', () => { describe('Generate JSON proxy with extension steps', () => { return it('should produce a valid proxy bundle', () => { - const input = fs.readFileSync('./test/data/input1.exvars.json', 'utf-8') + const input = fs.readFileSync('./test/data/input1.extensions.json', 'utf-8') return apigeeGenerator.generateProxyFromString(input, 'test/proxies').then((response) => { expect(response.success).to.equal(true) expect(response.duration).to.greaterThan(0) diff --git a/module/test/data/input1.assignmsg.json b/module/test/data/input1.assignmsg.json deleted file mode 100644 index e661098..0000000 --- a/module/test/data/input1.assignmsg.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "test-exvars-proxy", - "profile": "default", - "endpoints": [ - { - "name": "default", - "basePath": "/httpbin", - "target": { - "name": "default", - "url": "https://httpbin.org", - "headers": { - "Accept-Encoding": "gzip,deflate" - } - }, - "quotas": [ - { - "count": "200", - "timeUnit": "day" - } - ], - "spikeArrest": { - "rate": "20s" - }, - "auth": [ - { - "type": "apikey" - } - ], - "extensionSteps": [ - { - "type": "AssignMessage", - "name": "SetHeaders", - "triggers": ["preRequest"], - "continueOnError": true, - "assignTo": "request", - "ignoreUnresolvedVariables": true, - "assignVariables": [ - - ], - "add": [ - - ], - "copy": [ - - ], - "remove": [ - - ], - "set": [ - - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/module/test/data/input1.extensions.json b/module/test/data/input1.extensions.json new file mode 100644 index 0000000..6eb8551 --- /dev/null +++ b/module/test/data/input1.extensions.json @@ -0,0 +1,80 @@ +{ + "name": "test-extensions-proxy", + "profile": "default", + "endpoints": [ + { + "name": "default", + "basePath": "/test-extensions", + "target": { + "name": "default", + "url": "https://httpbin.org", + "headers": { + "Accept-Encoding": "gzip,deflate" + } + }, + "quotas": [ + { + "count": "200", + "timeUnit": "day" + } + ], + "spikeArrest": { + "rate": "20s" + }, + "auth": [], + "extensionSteps": [ + { + "type": "ExtractVariables", + "name": "extractQueryParam1", + "triggers": ["preRequest"], + "ignoreUnresolvedVariables": true, + "prefix": "test", + "queryParams": [ + { + "name": "param1", + "ignoreCase": true, + "pattern": "{param1}" + } + ] + }, + { + "type": "AssignMessage", + "name": "setTargetHeader", + "triggers": ["preTarget"], + "continueOnError": true, + "ignoreUnresolvedVariables": true, + "set": { + "headers": [ + { + "name": "X-Test-Header", + "value": "{test.param1}" + } + ] + } + }, + { + "type": "AssignMessage", + "name": "SetResponseHeader", + "triggers": ["postResponse"], + "continueOnError": true, + "assignTo": "response", + "ignoreUnresolvedVariables": true, + "set": { + "headers": [ + { + "name": "testHeader2", + "value": "{test.param1}" + } + ] + } + }, + { + "type": "FlowCallout", + "name": "CallSetHeaderFlow", + "triggers": ["postResponse"], + "flowName": "AddTestHeaderFlow" + } + ] + } + ] +} \ No newline at end of file diff --git a/module/test/data/input1.exvars.json b/module/test/data/input1.exvars.json deleted file mode 100644 index 7e37049..0000000 --- a/module/test/data/input1.exvars.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "test-exvars-proxy", - "profile": "default", - "endpoints": [ - { - "name": "default", - "basePath": "/httpbin", - "target": { - "name": "default", - "url": "https://httpbin.org", - "headers": { - "Accept-Encoding": "gzip,deflate" - } - }, - "quotas": [ - { - "count": "200", - "timeUnit": "day" - } - ], - "spikeArrest": { - "rate": "20s" - }, - "auth": [ - { - "type": "apikey" - } - ], - "extensionSteps": [ - { - "type": "ExtractVariables", - "name": "extractHeaders", - "triggers": ["preRequest"], - "ignoreUnresolvedVariables": true, - "prefix": "test", - "URIPaths": [ - { - "ignoreCase": true, - "pattern": "/accounts/{id}" - } - ], - "queryParams": [ - { - "name": "id", - "ignoreCase": true, - "pattern": "{user_id}" - } - ], - "headers": [""], - "formParams": [""], - "JSONPaths": [""], - "XMLPaths": [""] - } - ] - } - ] -} \ No newline at end of file diff --git a/module/test/data/input1.sharedflow.json b/module/test/data/input1.sharedflow.json index b2e5889..1f3339a 100644 --- a/module/test/data/input1.sharedflow.json +++ b/module/test/data/input1.sharedflow.json @@ -1,10 +1,25 @@ { - "name": "testflow", + "name": "AddTestHeaderFlow", "profile": "sharedflow", "sharedFlow": { "name": "default", - "spikeArrest": { - "rate": "20s" - } + "extensionSteps": [ + { + "type": "AssignMessage", + "name": "setResponseHeader", + "triggers": ["postResponse"], + "continueOnError": true, + "assignTo": "response", + "ignoreUnresolvedVariables": true, + "set": { + "headers": [ + { + "name": "X-Flow-Test-Header", + "value": "42" + } + ] + } + } + ] } } \ No newline at end of file