-
-
Save theburningmonk/d5e7e0a4cb0558fc9ae74c327726c821 to your computer and use it in GitHub Desktop.
'use strict'; | |
const co = require('co'); | |
const EventEmitter = require('events'); | |
const Promise = require('bluebird'); | |
const AWS = require('aws-sdk'); | |
const ssm = Promise.promisifyAll(new AWS.SSM()); | |
const DEFAULT_EXPIRY = 3 * 60 * 1000; // default expiry is 3 mins | |
function loadConfigs (keys, expiryMs) { | |
expiryMs = expiryMs || DEFAULT_EXPIRY; // defaults to 3 mins | |
if (!keys || !Array.isArray(keys) || keys.length === 0) { | |
throw new Error('you need to provide a non-empty array of config keys'); | |
} | |
if (expiryMs <= 0) { | |
throw new Error('you need to specify an expiry (ms) greater than 0, or leave it undefined'); | |
} | |
// the below uses the captured closure to return an object with a gettable | |
// property per config key that on invoke: | |
// * fetch the config values and cache them the first time | |
// * thereafter, use cached values until they expire | |
// * otherwise, try fetching from SSM parameter store again and cache them | |
let cache = { | |
expiration : new Date(0), | |
items : {} | |
}; | |
let eventEmitter = new EventEmitter(); | |
let validate = (keys, params) => { | |
let missing = keys.filter(k => params[k] === undefined); | |
if (missing.length > 0) { | |
throw new Error(`missing keys: ${missing}`); | |
} | |
}; | |
let reload = co.wrap(function* () { | |
console.log(`loading cache keys: ${keys}`); | |
let req = { | |
Names: keys, | |
WithDecryption: true | |
}; | |
let resp = yield ssm.getParametersAsync(req); | |
let params = {}; | |
for (let p of resp.Parameters) { | |
params[p.Name] = p.Value; | |
} | |
validate(keys, params); | |
console.log(`successfully loaded cache keys: ${keys}`); | |
let now = new Date(); | |
cache.expiration = new Date(now.getTime() + expiryMs); | |
cache.items = params; | |
eventEmitter.emit('refresh'); | |
}); | |
let getValue = co.wrap(function* (key) { | |
let now = new Date(); | |
if (now <= cache.expiration) { | |
return cache.items[key]; | |
} | |
try { | |
yield reload(); | |
return cache.items[key]; | |
} catch (err) { | |
if (cache.items && cache.items.length > 0) { | |
// swallow exception if cache is stale, as we'll just try again next time | |
console.log('[WARN] swallowing error from SSM Parameter Store:\n', err); | |
eventEmitter.emit('refreshError', err); | |
return cache.items[key]; | |
} | |
console.log(`[ERROR] couldn't fetch the initial configs : ${keys}`); | |
console.error(err); | |
throw err; | |
} | |
}); | |
let config = { | |
onRefresh : listener => eventEmitter.addListener('refresh', listener), | |
onRefreshError : listener => eventEmitter.addListener('refreshError', listener) | |
}; | |
for (let key of keys) { | |
Object.defineProperty(config, key, { | |
get: function() { return getValue(key); }, | |
enumerable: true, | |
configurable: false | |
}); | |
} | |
return config; | |
} | |
module.exports = { | |
loadConfigs | |
}; |
theburningmonk
commented
Oct 23, 2019
via email
Thankyou @theburningmonk for the reply as the getvalue
function is returning a promise how do we resolve it in https://gist.github.com/theburningmonk/d5e7e0a4cb0558fc9ae74c327726c821#file-cacheclient-js-L98. And also I did not find any api with name getParametersAsync
on aws-sdk so how does this work can you please explainhttps://gist.github.com/theburningmonk/d5e7e0a4cb0558fc9ae74c327726c821#file-cacheclient-js-L48
Hey I am using serverless framework and wanted to load it at the begining of my application so before I use it I wanted to test so I created a route and I made few requests to the /get
route. While I was debugging the code using vs code debugger for each and every new request I made it never got values from https://gist.github.com/theburningmonk/d5e7e0a4cb0558fc9ae74c327726c821#file-cacheclient-js-L69. Always it went to https://gist.github.com/theburningmonk/d5e7e0a4cb0558fc9ae74c327726c821#file-cacheclient-js-L73 and got values from here. So instead of having expiry time can we check cache and return value if exists. Can you please show me how to load these values in to one object and export it to other modules
const {loadConfigs} = require ("./cacheClient")
const names = [`/${process.env.SSM_PARAM_PREFIX}/foo`]
const load = loadConfigs(names);
router
.route("/get")
.get( async(req,res)=>{
const val = await load[`/${process.env.SSM_PARAM_PREFIX}/foo`];
return res.send(val)
})
//lambda.js
const awsServerlessExpress = require("aws-serverless-express");
const app = require("./app");
const server = awsServerlessExpress.createServer(app, null);
exports.handler = function(event, context) {
try {
awsServerlessExpress.proxy(server, event, context);
} catch (error) {
throw new Error(error.message);
}
};
You should use middy's ssm middleware instead of doing this yourself: https://github.com/middyjs/middy/blob/master/docs/middlewares.md#ssm
But if you do want to use this then you should replace the co.wrap
and yield
with async
and await
respectively, then you can use it like this:
const configs = loadConfigs(['key1', 'key2']);
const key1 = await configs.key1;
const key2 = await configs.key2;
Thank you for the response. I m able to convert it to async/await and make it work but my question is for each and every new request it is making a request to ssm. The cache is getting cleared after a request. I am able to mock the aws environment via local stack and serverless framework.
I would suggest using the middy middleware instead, or look at its implementation https://github.com/middyjs/middy/blob/master/src/middlewares/ssm.js
This was a simple demonstration of how you'd do such a cache client.
My async/await
method is working I got confused because I can mock the whole aws environment locally but I cant really check the caching locally. I forgot that caching will be enabled on aws lambda hosted containers so when I checked in my cloudwatch logs caching is working so once again thank you very much
No worries :-) glad it's working for you!