Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@f5devcentral/f5-fast-core

Package Overview
Dependencies
Maintainers
14
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@f5devcentral/f5-fast-core - npm Package Compare versions

Comparing version 0.7.0 to 0.8.0

schema/template.json

18

CHANGELOG.md

@@ -0,1 +1,19 @@

# v0.8.0
## Added
* template: Support extended user types (e.g., "var:lib:type") for sections and partials
* template: Add post-processing strategies and add one for 'application/json' to cleanup dangling commas
* cli: Add guiSchema sub command that runs the parameters schema through guiUtils.modSchemaForJSONEditor() before displaying it
## Fixed
* template: Fix using full Mustache variable names (e.g., "var:lib:type") for dependencies and requires
* template: Fix sections with a dot item overriding user definitions
* template: Add missing doc strings for HTTP fetching and forwarding functions
* template: Fix missing defaults from merged templates
* guiUtils: Additional fixes for allOf schema in modSchemaForJSONEditor()
* guiUtils: Do not error if a dependency is missing from the properties
## Changed
* cli: Run fetchHttp() as part of render command
* guiUtils: modSchemaForJSONEditor() no longer modifies in place
# v0.7.0

@@ -2,0 +20,0 @@ ## Added

29

cli.js

@@ -13,3 +13,3 @@ #!/usr/bin/env node

const FsTemplateProvider = require('./lib/template_provider').FsTemplateProvider;
const generateHtmlPreview = require('./lib/gui_utils').generateHtmlPreview;
const guiUtils = require('./lib/gui_utils');

@@ -54,3 +54,4 @@ const loadTemplate = (templatePath) => {

.then((tmpl) => {
console.log(JSON.stringify(tmpl.getParametersSchema(), null, 2));
const schema = tmpl.getParametersSchema();
console.log(JSON.stringify(schema, null, 2));
})

@@ -62,2 +63,13 @@ .catch((e) => {

const templateToParametersSchemaGui = templatePath => loadTemplate(templatePath)
.then((tmpl) => {
let schema = tmpl.getParametersSchema();
schema = guiUtils.modSchemaForJSONEditor(schema);
console.log(JSON.stringify(schema, null, 2));
})
.catch((e) => {
console.error(`Failed to generate schema:\n${e.stack}`);
process.exit(1);
});
const validateParamData = (tmpl, parameters) => {

@@ -82,2 +94,7 @@ try {

const renderTemplate = (templatePath, parametersPath) => loadTemplateAndParameters(templatePath, parametersPath)
.then(([tmpl, parameters]) => Promise.all([
Promise.resolve(tmpl),
tmpl.fetchHttp()
.then(httpParams => Object.assign({}, parameters, httpParams))
]))
.then(([tmpl, parameters]) => {

@@ -105,3 +122,3 @@ validateParamData(tmpl, parameters);

const htmlPreview = (templatePath, parametersPath) => loadTemplateAndParameters(templatePath, parametersPath)
.then(([tmpl, parameters]) => generateHtmlPreview(
.then(([tmpl, parameters]) => guiUtils.generateHtmlPreview(
tmpl.getParametersSchema(),

@@ -141,2 +158,8 @@ tmpl.getCombinedParameters(parameters)

}, argv => templateToParametersSchema(argv.file))
.command('guiSchema <file>', 'get template parameter schema (modified for use with JSON Editor) for given template source file', (yargs) => {
yargs
.positional('file', {
describe: 'template source file to parse'
});
}, argv => templateToParametersSchemaGui(argv.file))
.command('validateParameters <tmplFile> <parameterFile>', 'validate supplied template parameters with given template', (yargs) => {

@@ -143,0 +166,0 @@ yargs

3

index.js

@@ -7,3 +7,3 @@ 'use strict';

const { FsTemplateProvider, DataStoreTemplateProvider } = require('./lib/template_provider');
const { Template, mergeStrategies } = require('./lib/template');
const { Template, mergeStrategies, postProcessStrategies } = require('./lib/template');
const httpUtils = require('./lib/http_utils');

@@ -19,2 +19,3 @@ const guiUtils = require('./lib/gui_utils');

mergeStrategies,
postProcessStrategies,
httpUtils,

@@ -21,0 +22,0 @@ guiUtils,

@@ -38,2 +38,5 @@ 'use strict';

Object.keys(schema.dependencies).forEach((key) => {
if (!schema.properties[key]) {
return;
}
const depsOpt = schema.dependencies[key].reduce((acc, curr) => {

@@ -74,23 +77,35 @@ acc[curr] = !(

const fixAllOfOrder = (schema) => {
const fixAllOfOrder = (schema, orderID) => {
orderID = orderID || 0;
if (schema.allOf) {
schema.allOf.forEach((subSchema) => {
orderID = fixAllOfOrder(subSchema, orderID);
});
}
if (schema.properties) {
Object.keys(schema.properties).forEach((key) => {
const prop = schema.properties[key];
if (!prop.propertyOrder && !keyInXOf(key, schema)) {
prop.propertyOrder = 1100;
prop.propertyOrder = orderID;
orderID += 1;
}
});
}
return orderID;
};
const collapseAllOf = (schema) => {
Object.assign(schema, mergeAllOf(schema));
fixAllOfOrder(schema);
return mergeAllOf(schema);
};
const modSchemaForJSONEditor = (schema) => {
schema = JSON.parse(JSON.stringify(schema)); // Do not modify original schema
schema.title = schema.title || 'Template';
schema = collapseAllOf(schema);
injectFormatsIntoSchema(schema);
addDepsToSchema(schema);
fixAllOfOrder(schema);
collapseAllOf(schema);

@@ -112,4 +127,5 @@ return schema;

const generateHtmlPreview = (schema, view) => {
schema = modSchemaForJSONEditor(schema);
const htmlView = {
schema_data: JSON.stringify(modSchemaForJSONEditor(schema)),
schema_data: JSON.stringify(schema),
default_view: JSON.stringify(filterExtraProperties(view, schema)),

@@ -116,0 +132,0 @@ jsoneditor: fs.readFileSync(`${__dirname}/jsoneditor.js`, 'utf8')

@@ -16,6 +16,5 @@ 'use strict';

const _templateSchemaData = require('./template_schema').schema;
const tmplSchema = require('../schema/template.json');
// Setup validator
const tmplSchema = yaml.safeLoad(_templateSchemaData);
const validator = new Ajv();

@@ -52,2 +51,9 @@

if (schema.type === 'object') {
if (!value) {
return JSON.stringify({});
}
return JSON.stringify(value);
}
if (schema.format === 'text' && value) {

@@ -86,2 +92,18 @@ return JSON.stringify(value);

/**
* PostProcessStrategy for targeting `application/json` Content-Type
*/
function JsonPostProcessStrategy(rendered) {
return JSON.stringify(yaml.safeLoad(rendered), null, 2);
}
/**
* Object containing available post-processing strategy functions.
* The property is a Content-Type MIME (e.g., `test/plain`).
* The value is a PostProcessStrategy function that accepts rendered output.
*/
const postProcessStrategies = {
'application/json': JsonPostProcessStrategy
};
// Disable HTML escaping

@@ -196,9 +218,22 @@ Mustache.escape = function escape(text) {

const [mstType, mstName] = [curr[0], curr[1]];
const [defName, schemaName, type] = mstName.split(':');
if (['name', '#', '>', '^'].includes(mstType)) {
if (schemaName && typeof typeSchemas[schemaName] === 'undefined') {
throw new Error(`Failed to find the specified schema: ${schemaName} (${mstType}, ${mstName})`);
}
if (schemaName) {
const schemaDef = typeSchemas[schemaName].definitions[type];
if (!schemaDef) {
throw new Error(`No definition for ${type} in ${schemaName} schema`);
}
this.definitions[type] = Object.assign({}, schemaDef, this.definitions[type]);
Object.assign(this.typeDefinitions, typeSchemas[schemaName].definitions);
}
}
switch (mstType) {
case 'name': {
const [defName, schemaName, type] = mstName.split(':');
const defType = type || 'string';
if (schemaName && typeof typeSchemas[schemaName] === 'undefined') {
throw new Error(`Failed to find the specified schema: ${schemaName}`);
}
if (!schemaName && typeof primitives[defType] === 'undefined') {

@@ -211,7 +246,2 @@ throw new Error(`No schema definition for ${schemaName}/${defType}`);

acc.properties[defName] = Object.assign({}, schemaDef);
if (!acc.properties[defName]) {
throw new Error(`No definition for ${defType} in ${schemaName} schema`);
}
this.definitions[defType] = Object.assign({}, schemaDef, this.definitions[defType]);
Object.assign(this.typeDefinitions, typeSchemas[schemaName].definitions);
} else {

@@ -256,6 +286,6 @@ if (defType === 'text') {

case '>': {
if (!knownPartials[mstName]) {
throw new Error(`${mstName} does not reference a known partial`);
if (!knownPartials[defName]) {
throw new Error(`${defName} does not reference a known partial`);
}
const partial = this.typeDefinitions[mstName];
const partial = this.typeDefinitions[defName];
this._mergeSchemaInto(acc, partial, dependencies);

@@ -274,12 +304,16 @@ if (partial.required) {

const items = this._handleParsed(curr[4], typeSchemas);
const defType = (this.definitions[mstName] && this.definitions[mstName].type) || 'array';
const newDef = Object.assign({ type: defType }, this.definitions[mstName]);
const schemaDef = Object.assign(
this.typeDefinitions[type] || {},
this.definitions[defName] || {}
);
const defType = schemaDef.type || 'array';
const newDef = Object.assign({ type: defType }, schemaDef);
const asBool = defType === 'boolean' || defType === 'string';
if (defType === 'array') {
newDef.skip_xform = true;
newDef.items = Object.assign({}, items);
newDef.items = Object.assign({}, items, newDef.items);
} else if (defType === 'object') {
Object.assign(newDef, items);
} else if (!asBool) {
throw new Error(`unsupported type for section "${mstName}": ${defType}`);
throw new Error(`unsupported type for section "${defName}": ${defType}`);
}

@@ -292,3 +326,3 @@

}
dependencies[item].push(mstName);
dependencies[item].push(defName);
});

@@ -305,4 +339,4 @@ }

acc.properties[mstName] = Object.assign({}, newDef);
required.add(mstName);
acc.properties[defName] = Object.assign({}, newDef);
required.add(defName);

@@ -313,8 +347,13 @@ break;

const items = this._handleParsed(curr[4], typeSchemas);
const schemaDef = Object.assign(
this.typeDefinitions[type] || {},
this.definitions[defName] || {}
);
if (!acc.properties[mstName]) {
acc.properties[mstName] = {
if (!acc.properties[defName]) {
acc.properties[defName] = Object.assign({
type: 'boolean',
default: primitives.boolean
};
},
schemaDef);
}

@@ -326,7 +365,7 @@ if (items.properties) {

}
dependencies[item].push(mstName);
dependencies[item].push(defName);
if (!items.properties[item].invertDependency) {
items.properties[item].invertDependency = [];
}
items.properties[item].invertDependency.push(mstName);
items.properties[item].invertDependency.push(defName);
});

@@ -336,6 +375,6 @@ }

// If an inverted section is present, the section variable is not required
required.delete(mstName);
required.delete(defName);
if (this.definitions[mstName]) {
Object.assign(acc.properties[mstName], this.definitions[mstName]);
if (this.definitions[defName]) {
Object.assign(acc.properties[defName], this.definitions[defName]);
}

@@ -350,3 +389,3 @@ this._mergeSchemaInto(acc, items, dependencies);

default:
// console.log(`skipping ${mstName} with type of ${mstType}`);
// console.log(`skipping ${defName} with type of ${mstType}`);
}

@@ -360,4 +399,3 @@ return acc;

return {
type: 'string',
default: primitives.string
type: 'string'
};

@@ -485,2 +523,4 @@ }

* @param {SchemaProvider} [schemaProvider] - SchemaProvider to use to fetch schema referenced by the template
*
* @returns {Promise} Promise resolves to `Template`
*/

@@ -508,2 +548,4 @@ static loadMst(msttext, schemaProvider) {

* @param {string} [rootDir]
*
* @returns {Promise} Promise resolves to `Template`
*/

@@ -588,2 +630,4 @@ static loadYaml(yamltext, schemaProvider, filePath, rootDir) {

* @param {object|string} obj - The JSON data to create a `Template` from
*
* @returns {Promise} Promise resolves to `Template`
*/

@@ -681,3 +725,3 @@ static fromJson(obj) {

mergedDefaults,
tmpl.defaultParameters
tmpl.getCombinedParameters(parameters)
);

@@ -739,3 +783,3 @@ });

_cleanTemplateText(text) {
return text.replace(/{{([_a-zA-Z0-9]+):.*}}/g, '{{$1}}');
return text.replace(/{{([_a-zA-Z0-9#^>/]+):.*?}}/g, '{{$1}}');
}

@@ -747,2 +791,4 @@

* @param {object} parameters
*
* @returns {string} rendered result
*/

@@ -781,3 +827,3 @@ render(parameters) {

return templateTexts.reduce((acc, curr) => {
const rendered = templateTexts.reduce((acc, curr) => {
if (curr.length === 0) {

@@ -794,4 +840,16 @@ return acc;

});
const postProcessStrategy = postProcessStrategies[this.contentType];
if (postProcessStrategy) {
return postProcessStrategy(rendered);
}
return rendered;
}
/**
* Fetch data using an HTTP request for properties that specify a URL
*
* @returns {object} parameters
*/
fetchHttp() {

@@ -836,2 +894,9 @@ const promises = [];

/**
* Run fetchHttp() and combine the results with the supplied parameters object to pass to render()
*
* @param {object} parameters
*
* @returns {string} rendered result
*/
fetchAndRender(parameters) {

@@ -844,2 +909,11 @@ return Promise.resolve()

/**
* Render the template using the supplied parameters object and forward the results based on `httpForward` property
*
* Also run fetchHttp().
*
* @param {object} parameters
*
* @returns {Promise} Promise resolves to HTTP response results
*/
forwardHttp(parameters) {

@@ -846,0 +920,0 @@ if (!this.httpForward) {

{
"name": "@f5devcentral/f5-fast-core",
"version": "0.7.0",
"version": "0.8.0",
"author": "F5 Networks",

@@ -27,5 +27,5 @@ "license": "Apache-2.0",

"chai-as-promised": "^7.1.1",
"eslint": "^6.8.0",
"mocha": "^7.2.0",
"nock": "^12.0.3",
"eslint": "^7.10.0",
"mocha": "^8.1.3",
"nock": "^13.0.4",
"nyc": "^15.1.0",

@@ -55,3 +55,3 @@ "pkg": "^4.4.9"

"@f5devcentral/atg-storage": "^0.1.0",
"ajv": "^6.12.4",
"ajv": "^6.12.5",
"archiver": "^4.0.2",

@@ -58,0 +58,0 @@ "deepmerge": "^4.2.2",

@@ -26,6 +26,4 @@ ![Pipeline](https://github.com/f5devcentral/f5-fast-core/workflows/Pipeline/badge.svg)

## Usage
## CLI
### CLI
A command line interface is provided via a `fast` binary.

@@ -41,2 +39,3 @@ The help text is provided below and also accessed via `fast --help`:

fast schema <file> get template parameter schema for given template source file
fast guiSchema <file> get template parameter schema (modified for use with JSON Editor) for given template source file
fast validateParameters <tmplFile> <parameterFile> validate supplied template parameters with given template

@@ -66,4 +65,6 @@ fast render <tmplFile> [parameterFile] render given template file with supplied parameters

### Module
## Module API
### Simple Loading
Below is a basic example for loading a template without any additional type schema:

@@ -74,4 +75,4 @@

const ymldata = `
view:
const yamldata = `
parameters:
message: Hello!

@@ -90,3 +91,3 @@ definitions:

fast.Template.loadYaml(ymldata)
fast.Template.loadYaml(yamldata)
.then((template) => {

@@ -98,3 +99,3 @@ console.log(template.getParametersSchema());

In addition to `Template.loadYaml()`, a `Template` can be created from Mustache data using `Template.loadMst()`:
If a `Template` has been serialized to JSON (e.g., to send in an HTTP request), it can be deserialized with `Template.fromJson()`:

@@ -104,5 +105,10 @@ ```javascript

const mstdata = '{{message}}';
const yamldata = `
template: |
{{message}}
`;
fast.Template.loadMst(ymldata)
fast.Template.loadYaml(yamldata)
.then(template => JSON.stringify(template))
.then(jsonData => template.fromJson(jsonData))
.then((template) => {

@@ -114,2 +120,19 @@ console.log(template.getParametersSchema());

`Template` does not provide a mechanism for loading a template from a file and, instead, needs to be paired with something like Node's `fs` module:
```javascript
const fs = require('fs');
const fast = require('@f5devcentral/f5-fast-core');
const yamldata = fs.readFileSync('path/to/file', 'utf8');
fast.Template.loadYaml(yamldata)
.then((template) => {
console.log(template.getParametersSchema());
console.log(template.render({message: "Hello world!"}));
});
```
### Loading with Type Schema
To support user-defined types, a `SchemaProvider` must be used.

@@ -123,5 +146,8 @@ The `FsSchemaProvider` can be used to load schema from disk:

const schemaProvider = new fast.FsSchemaProvider(templatesPath);
const mstdata = '{{virtual_port:types:port}}';
const yamldata = `
template: |
{{virtual_port:types:port}}
`;
fast.Template.loadMst(mstdata, schemaProvider)
fast.Template.loadYaml(yamldata, schemaProvider)
.then((template) => {

@@ -133,5 +159,7 @@ console.log(template.getParametersSchema());

### Using a TemplateProvider
A higher-level API is available for loading templates via `TemplateProvider` classes.
These classes will handle calling the correct load function (`Template.loadYaml()` vs `Template.loadMst()`) and can also handle schemas.
For example, to load "templates sets" (a collection of template source files) from a given directory, the `FsTemplateProvider` class can be used:
These classes will handle calling the correct load function (`Template.loadYaml()` vs `Template.loadMst()`) and can also automatically handle additional schema files.
For example, to load "templates sets" (a directory containing template files) from a given directory, the `FsTemplateProvider` class can be used:

@@ -141,4 +169,4 @@ ```javascript

const templatesPath = '/path/to/templatesdir';
const templateProvider = new fast.FsTemplateProvider(templatesPath);
const templateSetsPath = '/path/to/templateSetsDir';
const templateProvider = new fast.FsTemplateProvider(templateSetsPath);

@@ -155,5 +183,79 @@ templateProvider.fetch('templateSetName/templateName')

### HTTP Fetch
To resolve external URLs in templates, a `Template.fetchHttp()` is available.
This will take any definition with a `url` property, resolve it, and return an object of the results.
```javascript
const fast = require('@f5devcentral/f5-fast-core');
const yamldata = `
definitions:
var:
url: http://example.com/resource
pathQuery: $.foo
template: |
{{var}}
`;
fast.Template.loadYaml(yamldata)
.then(template => Promise.all[(
Promise.resolve(template),
() => template.fetchHttp()
)])
.then(([template, httpParams]) => {
console.log(template.render(httpParams));
});
```
A `Template.fetchAndRender()` convenience function is also available to do fetchHttp() and render() in a single function call.
```javascript
const fast = require('@f5devcentral/f5-fast-core');
const yamldata = `
definitions:
var:
url: http://example.com/resource
pathQuery: $.foo
template: |
{{var}}
`;
fast.Template.loadYaml(yamldata)
.then(template => template.fetchAndRender())
.then((rendered) => {
console.log(rendered);
});
```
### HTTP Forward
It is common to want to submit the rendered template result to an HTTP endpoint.
`f5-fast-core` makes this simpler with `Template.forwardHttp()`.
This function will:
* Resolve external URLs with `Template.fetchHttp()`
* Render the template result
* Forward the rendered result as a `POST` (by default) to the endpoint defined by the template's `httpForward` property
```javascript
const fast = require('@f5devcentral/f5-fast-core');
const yamldata = `
httpForward:
url: http://example.com/resource
definitions:
var:
default: foo
template: |
{{var}}
`;
fast.Template..loadYaml(yamldata)
.then(template => template.forwardHttp()); // POST "foo" to http://example.com/resource
```
## Development
`npm` commands should be run in the core subdirectory, not at the top-level.
* To check for lint errors run `npm run lint`

@@ -164,6 +266,2 @@ * To run unit tests use `npm test`

## Documentation
For more information about FAST, see [FAST Documentation](https://clouddocs.f5.com/products/extensions/f5-appsvcs-templates/latest/)
## License

@@ -170,0 +268,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc