Skip to content

Instantly share code, notes, and snippets.

@aksel
Created January 23, 2020 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aksel/d7a6adf1862b21a6945c0253fb8a2820 to your computer and use it in GitHub Desktop.
Save aksel/d7a6adf1862b21a6945c0253fb8a2820 to your computer and use it in GitHub Desktop.
A very barebones Webpack plugin, that builds Node functions, and recursively traverses nested stacks.
const fs = require('fs');
const { yamlParse } = require('yaml-cfn');
const merge = require('lodash/merge');
const resourceReducer = (acc, [key, value]) => {
switch (value.Type) {
case 'AWS::Serverless::Application':
return merge(acc, { applications: { [key]: value } });
case 'AWS::Serverless::Function':
return merge(acc, { functions: { [key]: value } });
default:
return acc;
}
};
/**
* Heavily inspired by https://github.com/SnappyTutorials/aws-sam-webpack-plugin
* Parses SAM templates, and spits out entries for each AWS::Serverless::Function
* Will recursively traverse nested stacks as well, for each AWS::Serverless::Application.
*
* Also copies yaml templates into .aws-sam/build folders, unmodified.
*
* Note, that nested stacks within nested stacks don't work;
* shit breaks after first recursion because of file paths.
* But we don't need that yet, so it will have to wait.
* It is doable though! *just* have to continually prepend a parent's stack file path to a child.
*
* TODO: Handle nested stacks within nested stacks (i.e. second level of recursion)
*/
class SamPlugin {
constructor() {
this.entryPoints = {};
this.stacks = [];
}
/**
* Get template at path
* @param {string} [path]="."
* @return {{}} Entry points
*/
entries(path = '.') {
const templatePath = `${path}/template.yaml`;
const yaml = fs.readFileSync(templatePath);
if (!yaml) {
throw new Error(`No template found at ${templatePath}`);
}
this.stacks.push(templatePath);
const template = yamlParse(yaml.toString());
this.entryForTemplate(template, path);
return this.entryPoints;
}
/**
* Finds applications and functions within a template's Resources.
* Adds functions to this.entries.
* Recursively traverses nested stacks (applications), via this.entry(template path)
*
* Path prefix is used to ensure that input and output file structures are the same.
* Nested stacks point to functions by path relative to their root.
*
* This lets us use an unmodified yaml file afterwards; the relative file paths are the same.
* @param template - Parsed YAML template.
* @param pathPrefix - Prefix to be added to entry point.
*/
entryForTemplate(template, pathPrefix) {
const { Resources } = template;
const initialValue = { applications: {}, functions: {} };
const {
applications,
functions,
} = Object.entries(Resources).reduce(resourceReducer, initialValue);
// Parse functions into entries
Object
.values(functions)
.forEach((f) => {
const path = [pathPrefix, f.Properties.CodeUri].join('/');
this.entryPoints[path] = path;
});
// Recursively traverse nested stacks
Object
.values(applications)
.forEach((value) => this.entries(`./${value.Properties.Location.replace('/template.yaml', '')}`));
}
// Add outputTemplates as afterEmit hook.
apply = (compiler) => compiler.hooks.afterEmit.tap('SamPlugin', this.outputTemplates);
// Loop through stacks, and copy them from source to .aws-sam/build
outputTemplates = () => this.stacks.forEach((stack) => {
fs.copyFileSync(stack, `./.aws-sam/build/${stack.replace('./', '')}`);
});
}
module.exports = SamPlugin;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment