Skip to content

Instantly share code, notes, and snippets.

@aackerman
Last active April 15, 2021 07:45
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save aackerman/93d86b780ef7e951b59351dcc99af1b1 to your computer and use it in GitHub Desktop.
Save aackerman/93d86b780ef7e951b59351dcc99af1b1 to your computer and use it in GitHub Desktop.
Parameter Store Config
'use strict'
const EventEmitter = require('events')
// default expiry 3 mins
const DEFAULT_EXPIRY = 3 * 60 * 1000
const create = ({ keys, ssm, keyPrefix = '', expiryMs = DEFAULT_EXPIRY }) => {
let isRefreshing = false
if (!keys || !Array.isArray(keys) || keys.length === 0) {
throw new Error('Provide a non-empty array of config keys')
}
if (expiryMs <= 0) {
throw new Error('Specify an expiry (ms) greater than 0')
}
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 SSM Parameter Store keys: ${missing}`)
}
}
let fetchParameters = async () => {
if (process.env.NODE_ENV === 'production') {
let req = {
Names: keys.map(k => `${keyPrefix}${k}`),
WithDecryption: true
}
let {
Parameters
} = await ssm.getParameters(req).promise()
let params = {}
for (let p of Parameters) {
let name = p.Name.replace(keyPrefix, '')
params[name] = p.Value
}
return params
} else {
let params = {}
for (let key of keys) {
params[key] = process.env[key]
}
return params
}
}
let refresh = async () => {
if (isRefreshing) { return }
isRefreshing = true
console.log(`Refreshing SSM Parameter Store keys: ${keys}`)
try {
let params = await fetchParameters()
validate(keys, params)
console.log(`Successfully refreshed SSM Parameter Store keys: ${keys}`)
let now = new Date()
cache.expiration = new Date(now.getTime() + expiryMs)
cache.items = params
eventEmitter.emit('refresh')
} finally {
isRefreshing = false
}
}
let getValue = (key) => {
let now = new Date()
if (now >= cache.expiration) {
refresh()
}
return cache.items[key]
}
let config = {
refresh: refresh,
onRefresh: listener => eventEmitter.addListener('refresh', listener),
onRefreshError: listener => eventEmitter.addListener('refreshError', listener)
}
let defaultKeys = Object.keys(config)
for (let key of keys) {
key = key.replace(keyPrefix, '')
Object.defineProperty(config, key, {
get() { return getValue(key) },
enumerable: true,
configurable: false
})
}
// catch usage of undefined keys
if (process.env.NODE_ENV !== 'production') {
return new Proxy(config, {
get(target, key) {
if (keys.includes(key) === false && defaultKeys.includes(key) === false) {
throw new Error(`Key '${key}' is not defined in config keys`)
}
return target[key]
}
})
}
return config
}
module.exports = {
create
}
@aarkerio
Copy link

aarkerio commented Apr 3, 2019

Thanks a lot!, your code works great.

@robertsamarji
Copy link

Are you okay to explain how to utilise this config? I'm using the following but it doesn't seem to work:

const AWS = require('aws-sdk');
const configClient = require('./lib/configClient.js'
const ssm = new AWS.SSM();

module.exports.handler = async (event) => {
  const keys = ['foo'];
  const config = await configClient.create({keys, ssm);
  const keySecret = config.foo;
}

@sappusaketh
Copy link

sappusaketh commented Oct 21, 2019

can you please explain how will you resolve the promise inside getValue https://gist.github.com/aackerman/93d86b780ef7e951b59351dcc99af1b1#file-gistfile1-js-L78

@sappusaketh
Copy link

Thanks a lot!, your code works great.

does this really worked for you on production?

@igorkosta
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment