UNPKG

serverless-aws-alias-alb-patch

Version:

This package fixes the bug in serverless-aws-alias where the alb event doesnt attach to the alias

155 lines (128 loc) 7.02 kB
const crypto = require("crypto"); const isObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]'; const isArray = Array.isArray; const required = (param) => {throw (`Missing param: ${param}`)}; class AlbAlias { constructor(serverless, options) { this.serverless = serverless; this.options = options || {}; this.provider = this.serverless.getProvider('aws'); this.hooks = { "before:package:finalize": this.init.bind(this), "before:deploy:deploy": this.run.bind(this) }; } get alias() { return this.provider.naming.getNormalizedFunctionName(this.serverless.service.provider.alias); } get name() { return this.serverless.service.service; } generateMd5Hash(val) { return crypto.createHash('md5').update(val).digest("hex"); } findTargetGroups(stack = required`stack`) { const targetGroupKeys = Object.keys(stack.Resources) .filter(resourceKey => stack.Resources[resourceKey].Type === "AWS::ElasticLoadBalancingV2::TargetGroup"); return targetGroupKeys.reduce((result, resourceKey) => Object.assign(result, {[`${resourceKey}`]: stack.Resources[resourceKey]}), {}); } findRules(stack = required`stack`) { const ruleKeys = Object.keys(stack.Resources) .filter(resourceKey => stack.Resources[resourceKey].Type === "AWS::ElasticLoadBalancingV2::ListenerRule"); return ruleKeys.reduce((result, resourceKey) => Object.assign(result, {[`${resourceKey}`]: stack.Resources[resourceKey]}), {}); } findPermissions(stack = required`stack`) { const permissionResourceKeys = Object.keys(stack.Resources) .filter(resourceKey => stack.Resources[resourceKey].Type === "AWS::Lambda::Permission"); if (permissionResourceKeys.length === 0) return; const resourceKeys = permissionResourceKeys.filter(permissionResourceKey => stack.Resources[permissionResourceKey].Properties.Principal === "elasticloadbalancing.amazonaws.com"); return resourceKeys.reduce((result, resourceKey) => Object.assign(result, {[`${resourceKey}`]: stack.Resources[resourceKey]}), {}); } transform(functionName = required`functionName`, {permissionResource, ruleResource, targetGroup}) { const resourceObj = Object.assign(permissionResource, ruleResource, targetGroup); const replaceRefs = (target, value, obj) => { if (isArray(obj)) { obj.forEach((item, index) => { if (isArray(item) || isObject(item)) replaceRefs(target, value, item); else if (item === target) obj[index] = value; }); return; } for (const key in obj) { if (obj.hasOwnProperty(key) === false) continue; if (isObject(obj[key]) || isArray(obj[key])) replaceRefs(target, value, obj[key]); else if (obj[key] === target) obj[key] = value; if (key === target) { obj[value] = obj[key]; delete obj[key]; } } }; const reNameMapping = Object.keys(resourceObj).reduce((result, key) => Object.assign(result, {[`${key}`]: key.replace(`${functionName}`, `${functionName}${this.alias}`)}), {}); for (const key in reNameMapping) { if (reNameMapping.hasOwnProperty(key) === false) continue; replaceRefs(key, reNameMapping[key], resourceObj) } return resourceObj; } init() { const stageStack = this.serverless.service.provider.compiledCloudFormationTemplate; this.permissionResources = this.findPermissions(stageStack); this.ruleResources = this.findRules(stageStack); this.targetGroups = this.findTargetGroups(stageStack); const keys = Object.keys(this.permissionResources) .concat(Object.keys(this.ruleResources)) .concat(Object.keys(this.targetGroups)); for (let key of keys) { delete stageStack.Resources[key]; } } run() { const aliasStack = this.serverless.service.provider.compiledCloudFormationAliasTemplate; for (const functionName in this.serverless.service.functions) { if (this.serverless.service.functions.hasOwnProperty(functionName) === false) continue; const normalizedFunctionName = this.provider.naming.getNormalizedFunctionName(functionName); const targetGroups = Object.keys(this.targetGroups).reduce((result, key) => { if (key.substr(0, normalizedFunctionName.length) !== normalizedFunctionName) return result; return Object.assign(result, {[`${key}`]: this.targetGroups[key]}); }, {}); const ruleResources = Object.keys(this.ruleResources).reduce((result, key) => { if (key.substr(0, normalizedFunctionName.length) !== normalizedFunctionName) return result; return Object.assign(result, {[`${key}`]: this.ruleResources[key]}); }, {}); const permissionResources = Object.keys(this.permissionResources).reduce((result, key) => { if (key.substr(0, normalizedFunctionName.length) !== normalizedFunctionName) return result; return Object.assign(result, {[`${key}`]: this.permissionResources[key]}); }, {}); for (let key in targetGroups) { if (this.targetGroups.hasOwnProperty(key) === false) continue; if (key.substr(0, normalizedFunctionName.length) !== normalizedFunctionName) continue; this.targetGroups[key].Properties.Targets[0].Id = { "Ref": `${normalizedFunctionName}Alias` }; if (!targetGroups[key].DependsOn) targetGroups[key].DependsOn = []; this.targetGroups[key].DependsOn.push(`${normalizedFunctionName}Alias`); this.targetGroups[key].Properties.Name = this.generateMd5Hash(`${normalizedFunctionName}${this.alias}${key}${this.name}`); } for (let key in permissionResources) { if (this.permissionResources.hasOwnProperty(key) === false) continue; this.permissionResources[key].Properties.FunctionName = { "Ref": `${normalizedFunctionName}Alias` }; if (!permissionResources[key].DependsOn) permissionResources[key].DependsOn = []; this.permissionResources[key].DependsOn.push(`${normalizedFunctionName}Alias`) } Object.assign(aliasStack.Resources, this.transform(normalizedFunctionName, { permissionResource: permissionResources, ruleResource: ruleResources, targetGroup: targetGroups })); } } } module.exports = AlbAlias;