cfn-lambda
Version:
CloudFormation custom resource helper for Lambda Node.js runtime
170 lines (150 loc) • 6.07 kB
JavaScript
module.exports = options => (...args) => {
console.log('Using cfn-lambda SDKAlias to define an operation')
const { method } = options
switch (args.length) {
// Create
case 2:
console.log('Aliasing method %s as CREATE operation.', method)
return SimpleAlias({
options,
physicalId: null,
params: args[0],
reply: args[1]
})
// Delete
case 3:
console.log('Aliasing method %s as DELETE or NOOPUPDATE operation.', method)
return SimpleAlias({
options,
physicalId: args[0],
params: args[1],
reply: args[2]
})
// Update
case 4:
console.log('Aliasing method %s as UPDATE operation.', method)
return SimpleAlias({
options,
physicalId: args[0],
params: args[1],
reply: args[3]
})
default:
throw new Error('Could not determine cfn-lambda SDKAlias method signature at runtime.')
}
}
const SimpleAlias = ({
options,
options: {
returnPhysicalId,
api,
method,
ignoreErrorCodes
},
physicalId,
params: { ServiceToken, ...params }={},
reply
}) => {
var usedParams = usableParams({ params, options, physicalId })
var physicalIdFunction = 'function' === typeof returnPhysicalId
? returnPhysicalId
: 'string' === typeof returnPhysicalId
? accessFunction(returnPhysicalId)
: noop;
api[method](usedParams, function(err, data) {
if (!err || isIgnorable(ignoreErrorCodes, err)) {
console.log('Aliased method succeeded: %j', data);
return reply(null, physicalIdFunction(data, params),
attrsFrom({ options, data }));
}
console.log('Aliased method had error: %j', err);
reply(err.message);
});
}
const attrsFrom = ({ options: { returnAttrs }, data }) => ((Array.isArray(returnAttrs) && returnAttrs.every(isString)
? keyFilter.bind(null, returnAttrs)
: 'function' === typeof returnAttrs
? returnAttrs
: noop)(data))
const forcePaths = (params, pathSet, translator) => {
pathSet.forEach(path => {
const pathTokens = path.split('.')
const lastToken = pathTokens.pop()
const intermediate = pathTokens.reduce((obj, key, index) => {
if ('*' === key && obj != null) {
return forcePaths(obj, Object.keys(obj).map(indexOrElement => [indexOrElement].concat(pathTokens.slice(index + 1)).concat(lastToken).join('.')), translator)
}
return obj == null
? undefined
: obj[key]
}, params)
if (intermediate) {
if (lastToken === '*') {
if (Array.isArray(intermediate)) {
intermediate.forEach((value, index) => intermediate[index] = translator(value))
} else {
Object.keys(intermediate).forEach(key => intermediate[key] = translator(intermediate[key]))
}
} else if (intermediate[lastToken] !== undefined) {
intermediate[lastToken] = translator(intermediate[lastToken])
}
}
})
return params
}
const forceNum = (params, pathSet) => forcePaths(params, pathSet, value => +value)
const forceBoolean = (params, pathSet) => forcePaths(params, pathSet, value => ({
'0': false,
'false': false,
'': false,
'null': false,
'undefined': false,
'1': true,
'true': true
})[value])
const chain = functors => starting => functors.reduce((current, functor) => functor(current), starting)
const defaultToObject = params => params || {}
const forceBoolsWithin = ({ forceBools }) => params => Array.isArray(forceBools) && forceBools.every(isString) ? forceBoolean(params, forceBools) : params
const forceNumsWithin = ({ forceNums }) => params => Array.isArray(forceNums) && forceNums.every(isString) ? forceNum(params, forceNums) : params
const maybeAliasPhysicalId = ({ physicalId, physicalIdAs }) => params => isString(physicalIdAs) ? addAliasedPhysicalId(params, physicalIdAs, physicalId) : params
const filterToKeys = ({ keys }) => params => Array.isArray(keys) && keys.every(isString) ? keyFilter(keys, params) : params
const mapWithKeys = ({ mapKeys }) => params => Object(mapKeys) === mapKeys ? useKeyMap(params, mapKeys) : params
const maybeDowncase = ({ downcase }) => params => downcase ? downcaseKeys(params) : params
const logWithMethod = ({ method }) => params => (console.log('Calling aliased method %s with params: %j', method, params) || params)
const usableParams = ({
params,
options: { forceBools, forceNums, physicalIdAs, keys, mapKeys, downcase, method },
physicalId
}) => chain([
defaultToObject,
forceBoolsWithin({ forceBools }),
forceNumsWithin({ forceNums }),
maybeAliasPhysicalId({ physicalId, physicalIdAs }),
filterToKeys({ keys }),
mapWithKeys({ mapKeys }),
maybeDowncase({ downcase }),
logWithMethod({ method })
])(params)
const addAliasedPhysicalId = (params, physcialIdAlias, physicalId) => ({ ...params, [physcialIdAlias]: physicalId })
const downcaseKeys = hash => Object.keys(hash).reduce((dced, key) => ({ ...dced, [key[0].toLowerCase() + key.slice(1, key.length)]: hash[key] }), {})
const isString = obj => 'string' === typeof obj
const isIgnorable = (ignorableErrorCodes, errObject) => Array.isArray(ignorableErrorCodes) && !!~ignorableErrorCodes.indexOf(errObject.statusCode)
const accessFunction = key => {
let actualKey = key
const getDataSimple = data => data == null ? undefined : data[actualKey]
const getDataRecursive = data => {
if (actualKey.includes('.')) {
const pathTokens = actualKey.split('.')
const firstElem = pathTokens[0]
const childData = data[firstElem]
const childPath = pathTokens.slice(1).join('.')
actualKey = childPath
return getDataRecursive(childData)
}
return getDataSimple(data)
}
return getDataRecursive
}
const useKeyMap = (params, keyMap) => Object.keys(params).reduce((mapped, key) => ({ ...mapped, [keyMap[key] ? keyMap[key] : key]: params[key] }), {})
const noop = () => undefined
const keyFilter = (includedKeySet, hash) => includedKeySet.reduce((fHash, key) => ({ ...fHash, [key]: accessFunction(key)(hash) }), {})