yaml-cfn
Version:
Parser and schema for CloudFormation YAML template tags.
151 lines (130 loc) • 4.45 kB
JavaScript
/**
* Parser and schema for CloudFormation YAML template tags.
*
* There are some existing modules out there:
* https://github.com/yyolk/cloudformation-js-yaml-schema
* https://github.com/KoharaKazuya/js-yaml-schema-cfn
* But both are poorly documented, with insufficient tests, and don't fully work.
*
* This implementation is based on the official AWS python client:
* https://github.com/aws/aws-cli/blob/develop/awscli/customizations/cloudformation/yamlhelper.py
*/
;
const jsYaml = require('js-yaml');
/**
* Split a string on the given separator just once, returning an array of two parts, or null.
*/
function splitOne(str, sep) {
let index = str.indexOf(sep);
return index < 0 ? null : [str.slice(0, index), str.slice(index + sep.length)];
}
/**
* Returns true if obj is a representation of a CloudFormation intrinsic, i.e. an object with a
* single property at key keyName.
*/
function checkType(obj, keyName) {
return obj && typeof obj === 'object' && Object.keys(obj).length === 1 &&
obj.hasOwnProperty(keyName);
}
const overrides = {
// ShortHand notation for !GetAtt accepts Resource.Attribute format while the standard notation
// is to use an array [Resource, Attribute]. Convert shorthand to standard format.
GetAtt: {
parse: data => typeof data === 'string' ? splitOne(data, '.') : data,
dump: data => data.join('.')
}
};
function applyOverrides(data, tag, method) {
return overrides[tag] ? overrides[tag][method](data) : data;
}
/**
* Generic tag-creating helper. For the given name of the form 'Fn::Something' (or just
* 'Something'), creates a js-yaml Type object that can parse and dump this type. It creates it
* for all types of values, for simplicity and because that's how the official Python version
* works.
*/
function makeTagTypes(name) {
const parts = splitOne(name, '::');
const tag = parts ? parts[1] : name;
// Translate in the same way for all types, to match Python's generic translation.
return ['scalar', 'sequence', 'mapping'].map(kind => new jsYaml.Type('!' + tag, {
kind: kind,
construct: data => ({[name]: applyOverrides(data, tag, 'parse')}),
predicate: obj => checkType(obj, name),
represent: obj => applyOverrides(obj[name], tag, 'dump'),
}));
}
/**
* This list is from
* http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
* Note that the Python version handles ANY tag that starts with ! in the same way (translating it
* to Fn:: prefix, but js-yaml requires listing tags explicitly.
*/
const supportedFunctions = [
'Fn::Base64',
'Fn::Cidr',
'Fn::FindInMap',
'Fn::GetAtt',
'Fn::GetAZs',
'Fn::ImportValue',
'Fn::Join',
'Fn::Select',
'Fn::Split',
'Fn::Sub',
'Fn::Transform',
'Ref',
'Condition',
'Fn::And',
'Fn::Equals',
'Fn::If',
'Fn::Not',
'Fn::Or'
];
/**
* This list is from
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-rules.html
* This is not comprehensive, as it doesn't include
* - Supported Functions - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-rules.html#supported-rule-functions
* - Supported Attributes - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-rules.html#rules-parameter-attributes
* Note that the Python version handles ANY tag that starts with ! in the same way (translating it
* to Fn:: prefix, but js-yaml requires listing tags explicitly.
*/
const ruleFunctions = [
'Fn::And',
'Fn::Contains',
'Fn::EachMemberEquals',
'Fn::EachMemberIn',
'Fn::Equals',
'Fn::Not',
'Fn::Or',
'Fn::RefAll',
'Fn::ValueOf',
'Fn::ValueOfAll'
]
let allTagTypes = []
let allFunctions = [...supportedFunctions, ...ruleFunctions]
for (let name of allFunctions) {
allTagTypes.push(...makeTagTypes(name));
}
/**
* The actual js-yaml schema, extending the DEFAULT_SAFE_SCHEMA.
*/
const schema = jsYaml.CORE_SCHEMA.extend({
implicit: [],
explicit: allTagTypes,
});
exports.schema = schema;
/**
* Convenience function to parse the given yaml input.
*/
function yamlParse(input) {
return jsYaml.load(input, { schema: schema });
}
exports.yamlParse = yamlParse;
/**
* Convenience function to serialize the given object to Yaml.
*/
function yamlDump(input) {
return jsYaml.dump(input, { schema: schema });
}
exports.yamlDump = yamlDump;