Skip to content

Commit

Permalink
v0.2.0 - support for ssm param store
Browse files Browse the repository at this point in the history
  • Loading branch information
shivpatel committed Sep 20, 2019
1 parent 88f337f commit 065457b
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
node_modules
cstore.yml
cstore*.yml
test.js
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[![NPM version](https://img.shields.io/npm/v/cstore-pull.svg)](https://www.npmjs.org/package/cstore-pull)

cstore-pull is a lightweight JavaScript module for pulling cStore configs in Node. It currently **only supports the cStore S3 storage method**; SSM Parameter Store and Secrets Manager coming soon. cstore-pull can automatically inject your configs into Node's `process.env`.
cstore-pull is a lightweight JavaScript module for pulling cStore configs in Node. It currently **only supports the cStore S3 and SSM Parameter Store storage methods**; values encrypted with Secrets Manager are not supported. cstore-pull can automatically inject your configs into Node's `process.env`.

This module is only intended for use alongside an existing [cStore](https://github.com/turnerlabs/cstore) setup.

Tested against cStore `2.5.1`.
Tested against cStore `2.6.2`.

## Installation

Expand Down Expand Up @@ -43,7 +43,7 @@ init();

### pull (ymlPath: String, tag: String, injectIntoProcess: Boolean)

Locates the given `tag` in the `cstore.yml` file located at `ymlPath`. Fetches the corresponding object from S3 if the tag and file information is found. Parses file contents and returns an object with env vars as key/value pairs.
Locates the given `tag` in the `cstore.yml` file located at `ymlPath`. Fetches the specified configs via AWS SDK and returns an object with env vars as key/value pairs.

Params:
- `ymlPath` *(required)* - absolute path to your `cstore.yml` file
Expand Down
25 changes: 12 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
const yaml = require('js-yaml');
const fs = require('fs');
const s3 = require('./s3.js');
const parser = require('./lib/parser');
const s3 = require('./lib/stores/s3');
const ssm = require('./lib/stores/ssm');

const provide = {};

/**
* Given full file path, return contents of file in UTF8
* @param {String} path Absolute path to cstore.yml file
*/
const parseYaml = (path) => {
const doc = yaml.safeLoad(fs.readFileSync(path, 'utf8'));
return doc;
const stores = {
'aws-s3': s3.getConfigs,
'aws-parameter': ssm.getConfigs
}

/**
Expand All @@ -22,10 +18,13 @@ const parseYaml = (path) => {
*/
provide.pull = async (ymlPath, tag, injectIntoProcess = true) => {
console.info(`Loading configuration for ${tag}`);
const doc = parseYaml(ymlPath);
const doc = parser.parseYaml(ymlPath);
const context = doc.context;
const fileinfo = s3.locateTag(doc, tag);
const envVars = await s3.getConfig(context, fileinfo);
const fileinfo = parser.locateTag(doc, tag); // todo: could get multiple files back
if (!stores[fileinfo.store]) {
throw new Error(`cstore-pull - unsupported store type ${fileinfo.store}`);
}
const envVars = await stores[fileinfo.store](context, fileinfo);
console.info(`Loaded configuration for ${tag}`);
if (injectIntoProcess) {
for (let envKey in envVars) {
Expand Down
36 changes: 36 additions & 0 deletions lib/parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const yaml = require('js-yaml');
const fs = require('fs');

const provide = {};

/**
* Given full file path, return contents of file in UTF8
* @param {String} path Absolute path to cstore.yml file
*/
provide.parseYaml = (path) => {
const doc = yaml.safeLoad(fs.readFileSync(path, 'utf8'));
return doc;
}

/**
* Locate and return file info from parsed YAML given tag name
* @param {Object} parsedYaml Processed YAML file in JSON format
* @param {String} tag Tag name
*/
provide.locateTag = (parsedYaml, tag) => {
const files = Object.keys(parsedYaml.files);
let desiredFile = null;
for (let file of files) {
const fileinfo = parsedYaml.files[file];
if (fileinfo.tags && fileinfo.tags.includes(tag)) {
desiredFile = fileinfo;
break;
}
}
if (desiredFile === null) {
throw new Error('cstore-pull - tag not found');
}
return desiredFile;
}

module.exports = provide;
29 changes: 7 additions & 22 deletions s3.js → lib/stores/s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,15 @@ const get = async (Bucket, Key) => {
};

/**
* Locate and return file info from parsed YAML given tag name
* @param {Object} parsedYaml Processed YAML file in JSON format
* @param {String} tag Tag name
* Returns `true` if sufficient param store info
* is available in file.
* @param {Object} fileinfo parsed fileinfo JSON from cstore.yml
*/
provide.locateTag = (parsedYaml, tag) => {
const files = Object.keys(parsedYaml.files);
let desiredFile = null;
for (let file of files) {
const fileinfo = parsedYaml.files[file];
if (fileinfo.tags && fileinfo.tags.includes(tag)) {
desiredFile = fileinfo;
break;
}
}
if (desiredFile === null) {
throw new Error('cstore-pull - tag not found');
}
if (desiredFile.store !== 'aws-s3') {
throw new Error(`cstore-pull - unsupported store ${desiredFile.store}`);
}
if (!desiredFile.data.AWS_S3_BUCKET) {
provide.isValidFileInfo = (fileinfo) => {
if (!fileinfo.data.AWS_S3_BUCKET) {
throw new Error(`cstore-pull - missing AWS S3 bucket name`);
}
return desiredFile;
return true;
}

/**
Expand All @@ -58,7 +43,7 @@ provide.locateTag = (parsedYaml, tag) => {
* @param {String} context Context from cstore.yml
* @param {Object} fileinfo Parse fileinfo JSON from cstore.yml
*/
provide.getConfig = async (context, fileinfo) => {
provide.getConfigs = async (context, fileinfo) => {
const key = `${context}/${fileinfo.path}`;
const bucket = fileinfo.data.AWS_S3_BUCKET;
const envVars = {};
Expand Down
92 changes: 92 additions & 0 deletions lib/stores/ssm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';

const AWS = require('aws-sdk');
const ssm = new AWS.SSM();

const provide = {};

let count = 0;

/**
* Calls AWS API to get up to 10 param payloads starting
* with `path` after the provided `nextToken`
* @param {String} path pattern to match against
* @param {String} nextToken starting point page of results.
* If null or undefined, will start with page 0 of results.
*/
const get = async (path, nextToken) => {
count++;
return new Promise((resolve, reject) => {
const params = {
Path: path,
MaxResults: 10,
Recursive: true,
WithDecryption: true
};
if (nextToken) params.NextToken = nextToken;
ssm.getParametersByPath(params, function(err, data) {
if (err) reject(err);
else resolve(data);
});
});
}

/**
* Returns array of all param payloads where param
* name starts with the provided `path`
* @param {String} path pattern to match against
*/
const getAll = async (path) => {
let results = [];
let lastKnownToken = null;
while (true) {
const { Parameters, NextToken } = await get(path, lastKnownToken);
if (Parameters && Parameters.length) {
results.push(...Parameters);
}
if (NextToken) {
lastKnownToken = NextToken;
} else {
return results;
}
}
}

/**
* Parses raw AWS SSM parameter data into key/values.
* @param {String} path path of parameter name not desired
* in the final env var key
* @param {Object} params raw list of params from AWS API
*/
const parseRawParams = (path, params) => {
const results = {};
for (let param of params) {
const key = param.Name.split(path)[1];
results[key] = param.Value;
}
return results;
}

/**
* Returns `true` if sufficient param store info
* is available in file.
* @param {Object} fileinfo parsed fileinfo JSON from cstore.yml
*/
provide.isValidFileInfo = (fileinfo) => {
return true;
}

/**
* Return configs in key/value format given cStore context
* and parsed JSON file info
* @param {String} context context value from cstore.yml
* @param {Object} fileinfo parsed fileinfo JSON from cstore.yml
*/
provide.getConfigs = async (context, fileinfo) => {
const path = `/${context}/${fileinfo.path}/`;
const params = await getAll(path);
const envVars = parseRawParams(path, params);
return envVars;
}

module.exports = provide;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cstore-pull",
"version": "0.1.0",
"version": "0.2.0",
"description": "lightweight js client for cstore pull",
"main": "index.js",
"scripts": {
Expand Down

0 comments on commit 065457b

Please sign in to comment.