Skip to content

Instantly share code, notes, and snippets.

@joona
Last active March 13, 2020 06:39
Show Gist options
  • Save joona/925920873419fe276fd8022ba2484946 to your computer and use it in GitHub Desktop.
Save joona/925920873419fe276fd8022ba2484946 to your computer and use it in GitHub Desktop.
Simplisting YAML syntax processor for CF
"use strict";
const config = global.dasBootConfig;
const path = require('path');
const fs = require('fs');
const jsonjs = require('jsonjs');
const handlebars = require('handlebars');
const bucket = config.basePath;
const BASE_PATH = config.basePath;
class YamlProcessorException extends Error {
constructor(message) {
super(message);
}
}
class YamlProcessorPartial {
constructor(templateLines) {
this.templateLines = templateLines;
}
render() {
return this.templateLines;
}
}
var argumentsToObject = function(string) {
var parts = string.split(' ');
return parts.reduce((obj, x) => {
var y = x.split('=');
obj[y[0]] = y[1];
return obj;
}, {});
};
const TAGS = {
dasPartial: {
tag: '!DasPartial',
minMatches: 1,
regexp: new RegExp("!DasPartial (\\S+)(\\W+(.*))?$"),
process(matches, ctx, tag) {
//console.log('!DasPartial matches:', matches);
var templatePath = matches[1];
var stringArguments = matches[3] || '';
var args = argumentsToObject(stringArguments);
var templateContext = Object.assign({ _: args, _args: stringArguments }, ctx.object());
var template = fs.readFileSync(path.join(BASE_PATH, templatePath));
var compiled = handlebars.compile(template.toString());
var processed = compiled(templateContext);
//console.log('Processed partial template:', processed);
var processor = new YamlProcessor(processed, templateContext);
processor.process();
var processedLines = processor.processedLines;
processedLines.unshift(`# ${matches[0]}`);
return new YamlProcessorPartial(processedLines);
}
},
dasEnv: {
tag: '!DasEnv',
minMatches: 1,
regexp: new RegExp("!DasEnv ([a-zA-Z0-9._]+)"),
process(matches, ctx, tag) {
var key = matches[1];
var keyParts = key.split('.');
keyParts.unshift('environment');
var value = ctx.get(keyParts);
return value;
}
},
dasContext: {
tag: '!DasContext',
minMatches: 1,
regexp: new RegExp("!DasContext ([a-zA-Z0-9._]+)(\\W+(.*))?$"),
process(matches, ctx, tag) {
var key = matches[1];
var keyParts = key.split('.');
keyParts.unshift('context');
var value = ctx.get(keyParts);
if(!value) return;
// string value modifies, like Limit
if(matches[3]) {
var args = argumentsToObject(matches[3]);
if(args.Limit) {
value = value.substr(0, args.Limit);
}
if(args.Lower) {
value = value.toLowerCase();
}
}
return value;
}
},
/*
dasExportNamespace: {
tag: '!InbotExportNamespace',
minMatches: 2,
regexp: new RegExp("!DasExportNamespace ([a-zA-Z0-9._]+) ([a-zA-Z0-9._]+)"),
process(matches, ctx, tag) {
var scope = matches[1];
var suffix = matches[2];
if(!scope || !suffix) {
throw new Error('Validation error, check parameters!');
}
switch(scope) {
case 'Environment':
var parts = ctx.get('context', 'environmentName').split('-');
var environmentBase = parts[1];
var environmentName = parts[0];
if(ctx.get('context', 'stackType') == 'environment') {
environmentBase = ctx.get('context', 'environmentName');
environmentName = ctx.get('context', 'stackName');
}
if(!environmentBase) {
throw new Error(`DasExportNamespace: Unable to figure out base environment from: ${parts}`);
}
if(!environmentName) {
throw new Error(`DasExportNamespace: Unable to figure out environment name from: ${parts}`);
}
return `${environmentBase}${environmentName}${suffix}`;
}
return;
}
},*/
dasImportValue: {
tag: '!DasImportValue',
minMatches: 1,
regexp: new RegExp("!DasImportValue ([a-zA-Z0-9._]+)"),
process(matches, ctx, tag) {
var key = matches[1];
if(!key) {
throw new Error('ValidationError, check parameters!');
}
var outputs = ctx.getOrCreateDecoratedObject('upstreamOutputs');
return outputs.get(key);
}
}
};
class YamlProcessor {
constructor(template, context) {
this.template = template;
this.context = jsonjs.decorate(context);
this.processedLines = [];
this.tagKeys = Object.keys(TAGS);
}
toString() {
return this.processedLines.join("\n");
}
process() {
var templateLines = this.getLines();
for(var i = 0; i < templateLines.length; i++) {
var lines = this.processLine(templateLines[i], i+1);
if(lines && lines.length > 0) {
for(var ii = 0; ii < lines.length; ii++) {
this.processedLines.push(lines[ii]);
}
} else {
this.processedLines.push(templateLines[i]);
}
}
return this.toString();
}
processLine(line, ln) {
var results = [];
for(var i = 0; i < this.tagKeys.length; i++) {
const tag = TAGS[this.tagKeys[i]];
if(line.indexOf(tag.tag) < 0) {
continue;
}
var matches = tag.regexp.exec(line);
if(matches && matches.length > 0) {
if((matches.length - 1) < tag.minMatches) {
throw new YamlProcessorException(`Found tag ${tag.tag} on line ${ln} expecting ${tag.minMatches} values, but found only ${matches.length-1}`);
}
var value;
try {
value = tag.process(matches, this.context, tag);
} catch(err) {
throw new YamlProcessorException(`Problem while processing tag ${tag.tag} on line ${ln}. Processor error: ${err.message}`);
}
var pre = line.substr(0, matches.index);
var whitespace = '';
for(var wi = 0; line.charAt(wi) == ' '; wi++) {
whitespace += ' ';
}
if(Array.isArray(value)) {
results.push(`${pre} # (${matches[0]})`);
for(var ii = 0; ii < value.length; ii++) {
results.push(`${whitespace} - ${value[ii]}`);
}
}
else if(value instanceof YamlProcessorPartial) {
var arr = value.render();
for(var yi = 0; yi < arr.length; yi++) {
results.push(`${whitespace}${arr[yi]}`);
}
}
else if(typeof value === "string") {
if(value.charAt(0) === '!') {
results.push(`${pre}${value} # (${matches[0]})`);
} else {
results.push(`${pre}"${value}" # (${matches[0]})`);
}
}
else {
console.error('Return value:', value);
throw new Error(`Found tag ${tag.tag} on line ${ln}, but got invalid return value from processor "${typeof value}"`);
}
} else {
throw new Error(`Found tag ${tag.tag} on line ${ln}, but line ${line} it's not matching regexp provided [${tag.regexp.toString()}]`)
}
}
return results;
}
getLines() {
return this.template.split("\n");
}
}
module.exports.create = function(template, context) {
return new YamlProcessor(template, context);
};
// smoketest
if(!module.parent) {
var defaultUpstream = {
EnvironmentSecurityGroupId: 'environmentSG',
BaseEnvironmentSecurityGroupId: 'baseSG',
VpnBaseSecurityGroupId: 'vpnSG',
VpcId: 'foo-vpc'
};
var template = require('fs').readFileSync('../../templates/das-api.yml');
var processor = new YamlProcessor(template.toString(), {
environment: {
VPCZoneIdentifiers: ['a', 'b', 'c'],
AvailabilityZones: ['a', 'b', 'c']
},
upstreamOutputs: defaultUpstream,
context: {
stackName: 'Service-das-api-FooBar-FooEuWest'
}
});
var stack = processor.process();
template = require('fs').readFileSync('../../templates/new-testing-environment.yml');
processor = new YamlProcessor(template.toString(), {
environment: {
VPCZoneIdentifiers: ['a', 'b', 'c'],
AvailabilityZones: ['a', 'b', 'c']
},
context: {
stackName: 'Environment-FooBar-FooEuWest'
}
});
var environment = processor.process();
console.log('====> Environment:');
console.log(environment);
console.log('---');
console.log('====> Stack:');
console.log(stack);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment