UNPKG

4.55 kBJavaScriptView Raw
1"use strict";
2const AWS = require("aws-sdk");
3const BbPromise = require("bluebird");
4const _ = require("lodash");
5const path = require("path");
6const fs = require("fs");
7
8// DynamoDB has a 25 item limit in batch requests
9// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html
10const MAX_MIGRATION_CHUNK = 25;
11
12// TODO: let this be configurable
13const MIGRATION_SEED_CONCURRENCY = 5;
14
15/**
16 * Writes a batch chunk of migration seeds to DynamoDB. DynamoDB has a limit on the number of
17 * items that may be written in a batch operation.
18 * @param {function} dynamodbWriteFunction The DynamoDB DocumentClient.batchWrite or DynamoDB.batchWriteItem function
19 * @param {string} tableName The table name being written to
20 * @param {any[]} seeds The migration seeds being written to the table
21 */
22function writeSeedBatch(dynamodbWriteFunction, tableName, seeds) {
23 const params = {
24 RequestItems: {
25 [tableName]: seeds.map((seed) => ({
26 PutRequest: {
27 Item: seed,
28 },
29 })),
30 },
31 };
32 return new BbPromise((resolve, reject) => {
33 // interval lets us know how much time we have burnt so far. This lets us have a backoff mechanism to try
34 // again a few times in case the Database resources are in the middle of provisioning.
35 let interval = 0;
36 function execute(interval) {
37 setTimeout(() => dynamodbWriteFunction(params, (err) => {
38 if (err) {
39 if (err.code === "ResourceNotFoundException" && interval <= 5000) {
40 execute(interval + 1000);
41 } else {
42 reject(err);
43 }
44 } else {
45 resolve();
46 }
47 }), interval);
48 }
49 execute(interval);
50 });
51}
52
53/**
54 * Writes a seed corpus to the given database table
55 * @param {function} dynamodbWriteFunction The DynamoDB DocumentClient.batchWrite or DynamoDB.batchWriteItem function
56 * @param {string} tableName The table name
57 * @param {any[]} seeds The seed values
58 */
59function writeSeeds(dynamodbWriteFunction, tableName, seeds) {
60 if (!dynamodbWriteFunction) {
61 throw new Error("dynamodbWriteFunction argument must be provided");
62 }
63 if (!tableName) {
64 throw new Error("table name argument must be provided");
65 }
66 if (!seeds) {
67 throw new Error("seeds argument must be provided");
68 }
69
70 if (seeds.length > 0) {
71 const seedChunks = _.chunk(seeds, MAX_MIGRATION_CHUNK);
72 return BbPromise.map(
73 seedChunks,
74 (chunk) => writeSeedBatch(dynamodbWriteFunction, tableName, chunk),
75 { concurrency: MIGRATION_SEED_CONCURRENCY }
76 )
77 .then(() => console.log("Seed running complete for table: " + tableName));
78 }
79}
80
81/**
82 * A promise-based function that determines if a file exists
83 * @param {string} fileName The path to the file
84 */
85function fileExists(fileName) {
86 return new BbPromise((resolve) => {
87 fs.exists(fileName, (exists) => resolve(exists));
88 });
89}
90
91/**
92 * Transform all selerialized Buffer value in a Buffer value inside a json object
93 *
94 * @param {json} json with serialized Buffer value.
95 * @return {json} json with Buffer object.
96 */
97function unmarshalBuffer(json) {
98 _.forEach(json, function(value, key) {
99 // Null check to prevent creation of Buffer when value is null
100 if (value !== null && value.type==="Buffer") {
101 json[key]= new Buffer(value.data);
102 }
103 });
104 return json;
105}
106
107/**
108 * Scrapes seed files out of a given location. This file may contain
109 * either a simple json object, or an array of simple json objects. An array
110 * of json objects is returned.
111 *
112 * @param {any} location the filename to read seeds from.
113 */
114function getSeedsAtLocation(location) {
115 // load the file as JSON
116 const result = require(location);
117
118 // Ensure the output is an array
119 if (Array.isArray(result)) {
120 return _.forEach(result, unmarshalBuffer);
121 } else {
122 return [ unmarshalBuffer(result) ];
123 }
124}
125
126/**
127 * Locates seeds given a set of files to scrape
128 * @param {string[]} sources The filenames to scrape for seeds
129 */
130function locateSeeds(sources, cwd) {
131 sources = sources || [];
132 cwd = cwd || process.cwd();
133
134 const locations = sources.map((source) => path.join(cwd, source));
135 return BbPromise.map(locations, (location) => {
136 return fileExists(location).then((exists) => {
137 if(!exists) {
138 throw new Error("source file " + location + " does not exist");
139 }
140 return getSeedsAtLocation(location);
141 });
142 // Smash the arrays together
143 }).then((seedArrays) => [].concat.apply([], seedArrays));
144}
145
146module.exports = { writeSeeds, locateSeeds };