├── .gitignore ├── .npmignore ├── renovate.json ├── .github └── workflows │ ├── xcop.yml │ ├── reuse.yml │ ├── typos.yml │ ├── pdd.yml │ ├── yamllint.yml │ ├── copyrights.yml │ ├── markdown-lint.yml │ ├── npm.yml │ └── actionlint.yml ├── test ├── fixture.json └── archive.test.js ├── LICENSE.txt ├── LICENSES └── MIT.txt ├── .rultor.yml ├── REUSE.toml ├── package.json ├── bin ├── dynamo-restore.js └── dynamo-archive.js ├── lib └── utils.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | node_modules/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .rultor.yml 2 | .github/ 3 | articles.json 4 | test/ 5 | renovate.json 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/xcop.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: xcop 6 | 'on': 7 | push: 8 | pull_request: 9 | jobs: 10 | xcop: 11 | timeout-minutes: 15 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: g4s8/xcop-action@master 16 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: reuse 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | reuse: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: fsfe/reuse-action@v5 20 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: typos 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | typos: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: crate-ci/typos@v1.32.0 20 | -------------------------------------------------------------------------------- /.github/workflows/pdd.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: pdd 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | pdd: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: volodya-lombrozo/pdd-action@master 20 | -------------------------------------------------------------------------------- /.github/workflows/yamllint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: yamllint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | yamllint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: ibiqlik/action-yamllint@v3 20 | -------------------------------------------------------------------------------- /test/fixture.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": { 4 | "S": "foo" 5 | }, 6 | "Value": { 7 | "N": "5" 8 | } 9 | }, 10 | { 11 | "Name": { 12 | "S": "foo" 13 | }, 14 | "Value": { 15 | "N": "24" 16 | } 17 | }, 18 | { 19 | "Name": { 20 | "S": "bar" 21 | }, 22 | "Value": { 23 | "N": "9" 24 | } 25 | }, 26 | { 27 | "Name": { 28 | "S": "baz" 29 | }, 30 | "Value": { 31 | "N": "300" 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /.github/workflows/copyrights.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: copyrights 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | copyrights: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: yegor256/copyrights-action@0.0.8 20 | -------------------------------------------------------------------------------- /.github/workflows/markdown-lint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: markdown-lint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | markdown-lint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: DavidAnson/markdownlint-cli2-action@v20.0.0 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2025 Yegor Bugayenko 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2025 Yegor Bugayenko 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | -------------------------------------------------------------------------------- /.rultor.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | docker: 6 | image: yegor256/rultor-image:1.24.0 7 | assets: 8 | npmrc: yegor256/home#assets/npmrc 9 | install: |- 10 | npm install --no-color 11 | release: 12 | pre: false 13 | script: |- 14 | node --version 15 | npm -version 16 | npm test --no-color 17 | sed -i "s/0.0.0/${tag}/g" package.json 18 | chmod 600 ../npmrc 19 | npm publish --no-color --userconfig=../npmrc 20 | merge: 21 | script: |- 22 | npm test --no-color 23 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: npm 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | test: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-24.04, macos-15] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: '12.x' 24 | - run: npm install --no-color 25 | - run: npm test --no-color 26 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | 4 | version = 1 5 | [[annotations]] 6 | path = [ 7 | ".DS_Store", 8 | ".gitattributes", 9 | ".gitignore", 10 | ".npmignore", 11 | "**.json", 12 | "**.md", 13 | "**.txt", 14 | "**/.DS_Store", 15 | "**/.gitignore", 16 | "**/*.csv", 17 | "**/*.jpg", 18 | "**/*.json", 19 | "**/*.md", 20 | "**/*.pdf", 21 | "**/*.png", 22 | "**/*.svg", 23 | "**/*.txt", 24 | "**/*.vm", 25 | "**/CNAME", 26 | "README.md", 27 | "renovate.json", 28 | ] 29 | precedence = "override" 30 | SPDX-FileCopyrightText = "Copyright (c) 2025 Yegor Bugayenko" 31 | SPDX-License-Identifier = "MIT" 32 | -------------------------------------------------------------------------------- /.github/workflows/actionlint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: actionlint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | actionlint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Download actionlint 20 | id: get_actionlint 21 | run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 22 | shell: bash 23 | - name: Check workflow files 24 | run: ${{ steps.get_actionlint.outputs.executable }} -color 25 | shell: bash 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Yegor Bugayenko", 3 | "bin": { 4 | "dynamo-archive": "bin/dynamo-archive.js", 5 | "dynamo-restore": "bin/dynamo-restore.js" 6 | }, 7 | "bugs": { 8 | "url": "https://github.com/yegor256/dynamo-archive/issues" 9 | }, 10 | "dependencies": { 11 | "aws-sdk": "2.814.x", 12 | "dotenv": "0.5.x", 13 | "minimist": "1.2.x", 14 | "proxy-agent": "^2.0.0", 15 | "sleep": "6.3.x" 16 | }, 17 | "description": "Backup DynamoDB tables to JSON files", 18 | "devDependencies": { 19 | "dynalite": "3.2.x", 20 | "faucet": "0.0.x", 21 | "tape": "~2.14.0" 22 | }, 23 | "homepage": "https://github.com/yegor256/dynamo-archive", 24 | "keywords": [ 25 | "AWS", 26 | "DynamoDB", 27 | "export", 28 | "import", 29 | "achive" 30 | ], 31 | "license": "Apache", 32 | "name": "dynamo-archive", 33 | "repository": { 34 | "type": "git", 35 | "url": "git://github.com/yegor256/dynamo-archive.git" 36 | }, 37 | "scripts": { 38 | "test": "tape test/archive.test.js | faucet" 39 | }, 40 | "version": "0.0.0" 41 | } 42 | -------------------------------------------------------------------------------- /bin/dynamo-restore.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | var utils = require('../lib/utils'); 8 | var readline = require('readline'); 9 | var sleep = require('sleep'); 10 | 11 | var argv = utils.config({ 12 | demand: ['table'], 13 | optional: ['rate', 'key', 'secret', 'region'], 14 | usage: 'Restores Dynamo DB table from JSON file\n' + 15 | 'Usage: dynamo-archive --table my-table [--rate 100] [--region us-east-1] [--key AK...AA] [--secret 7a...IG]' 16 | }); 17 | 18 | var dynamo = utils.dynamo(argv); 19 | dynamo.describeTable( 20 | { 21 | TableName: argv.table 22 | }, 23 | function (err, data) { 24 | if (err != null) { 25 | throw err; 26 | } 27 | if (data == null) { 28 | throw 'Table ' + argv.table + ' not found in DynamoDB'; 29 | } 30 | var quota = data.Table.ProvisionedThroughput.WriteCapacityUnits; 31 | var start = Date.now(); 32 | var msecPerItem = Math.round(1000 / quota / ((argv.rate || 100) / 100)); 33 | var done = 0; 34 | readline.createInterface(process.stdin, process.stdout).on( 35 | 'line', 36 | function(line) { 37 | dynamo.putItem( 38 | { 39 | TableName: argv.table, 40 | Item: JSON.parse(line) 41 | }, 42 | function (err, data) { 43 | if (err) { 44 | console.log(err, err.stack); 45 | throw err; 46 | } 47 | } 48 | ); 49 | ++done; 50 | var expected = start + msecPerItem * done; 51 | if (expected > Date.now()) { 52 | sleep.usleep((expected - Date.now()) * 1000); 53 | } 54 | } 55 | ); 56 | } 57 | ); 58 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | // SPDX-License-Identifier: MIT 3 | 4 | /** 5 | * Copyright 2013-2025 Yegor Bugayenko 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | require('dotenv').load(); 20 | var minimist = require('minimist'); 21 | var aws = require('aws-sdk'); 22 | var proxy = require('proxy-agent'); 23 | 24 | module.exports = {}; 25 | 26 | module.exports.dynamo = function(opts) { 27 | aws.config.update( 28 | { 29 | accessKeyId: opts.key || process.env.AWS_ACCESS_KEY_ID, 30 | secretAccessKey: opts.secret || process.env.AWS_SECRET_ACCESS_KEY, 31 | sessionToken: opts.session || process.env.AWS_SESSION_TOKEN, 32 | region: opts.region || process.env.AWS_DEFAULT_REGION || 'us-east-1' 33 | } 34 | ); 35 | if (process.env.https_proxy) { 36 | aws.config.update( 37 | { 38 | httpOptions: { agent: proxy(process.env.https_proxy) } 39 | } 40 | ); 41 | } 42 | var opts = {}; 43 | if (process.env.AWS_DYNAMODB_ENDPOINT){ 44 | opts.endpoint = new aws.Endpoint(process.env.AWS_DYNAMODB_ENDPOINT); 45 | } 46 | return new aws.DynamoDB(opts); 47 | }; 48 | 49 | // `opts` should be an object with keys: demand, optional, usage 50 | module.exports.config = function(opts) { 51 | var args = minimist(process.argv.slice(2), opts); 52 | var filtered = {}; 53 | opts.demand.concat(opts.optional).forEach( 54 | function(k) { 55 | if (args[k] !== undefined) { 56 | filtered[k] = args[k]; 57 | } 58 | } 59 | ); 60 | // Ensure required params are present 61 | if (opts.demand.reduce(function(m, k) { 62 | return m || filtered[k] == undefined; 63 | }, false)) { 64 | console.error('> Missing required argument') 65 | console.error(opts.usage); 66 | process.exit(1); 67 | } 68 | return filtered; 69 | }; 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![DevOps By Rultor.com](https://www.rultor.com/b/yegor256/dynamo-archive)](https://www.rultor.com/p/yegor256/dynamo-archive) 2 | 3 | [![npm](https://github.com/yegor256/dynamo-archive/actions/workflows/npm.yml/badge.svg)](https://github.com/yegor256/dynamo-archive/actions/workflows/npm.yml) 4 | [![NPM version](https://badge.fury.io/js/dynamo-archive.svg)](https://badge.fury.io/js/dynamo-archive) 5 | 6 | There are two simple Node.js scripts that archive and restore an entire 7 | [AWS Dynamo DB](http://aws.amazon.com/dynamodb/) 8 | table in JSON format. 9 | 10 | Install it first (I assume you have 11 | [Node.js](http://nodejs.org/) and 12 | [Npm](https://npmjs.org/doc/install.html) installed already): 13 | 14 | ```bash 15 | $ npm install -g dynamo-archive 16 | ``` 17 | 18 | Create a user in [Amazon IAM](http://aws.amazon.com/iam/) 19 | and assign a policy to it ([how?](http://docs.aws.amazon.com/IAM/latest/UserGuide/ManagingPolicies.html)): 20 | 21 | ```json 22 | { 23 | "Statement": [ 24 | { 25 | "Effect": "Allow", 26 | "Action": ["dynamodb:Scan", "dynamodb:DescribeTable"], 27 | "Resource": "arn:aws:dynamodb:us-east-1:019644334823:table/test" 28 | } 29 | ] 30 | } 31 | ``` 32 | 33 | Where `019644334823` if your AWS account number, `us-east-1` is AWS region, 34 | and `test` is the name of your Dynamo DB table (can be a `*`, if you grant 35 | access to all tables). 36 | 37 | Run it first without arguments and read the output: 38 | 39 | ```bash 40 | $ dynamo-archive.js 41 | ``` 42 | 43 | To restore a table from a JSON file run: 44 | 45 | ```bash 46 | $ dynamo-restore.js 47 | ``` 48 | 49 | ## Crontab automation 50 | 51 | I'd recommend to use this simple bash script to automate backups 52 | of your Dynamo DB tables and save them to S3 (I'm using [s3cmd](http://s3tools.org/s3cmd)): 53 | 54 | ```bash 55 | #/bin/bash 56 | 57 | AWS_ACCESS_KEY_ID=AKIAJK.......XWGA5AA 58 | AWS_SECRET_ACCESS_KEY=7aDUFa68GN....................IGcH0zTf3k 59 | #optional endpoint for DynamoDB local 60 | AWS_DYNAMODB_ENDPOINT=http://localhost:8000/ 61 | declare -a TABLES=(first second third) 62 | for t in ${TABLES[@]} 63 | do 64 | dynamo-archive/bin/dynamo-archive.js --table=$t > $t.json 65 | s3cmd --no-progress put $t.json s3://backup.example.com/dynamo/$t.json 66 | rm $t.json 67 | done 68 | ``` 69 | 70 | ## How to contribute 71 | 72 | Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html). 73 | Make sure your build is green before you contribute 74 | your pull request. You will need to have NodeJS and installed. Then: 75 | 76 | ``` 77 | $ npm install 78 | $ npm test 79 | ``` 80 | 81 | If it's clean and you don't see any error messages, submit your pull request. 82 | -------------------------------------------------------------------------------- /bin/dynamo-archive.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | var utils = require('../lib/utils'); 8 | var sleep = require('sleep'); 9 | 10 | var argv = utils.config({ 11 | demand: ['table'], 12 | optional: ['rate', 'query', 'key', 'secret', 'region', 'index'], 13 | usage: 'Archives Dynamo DB table to standard output in JSON\n' + 14 | 'Usage: dynamo-archive --table my-table [--rate 100] [--query "{}"] [--region us-east-1] [--key AK...AA] [--secret 7a...IG] [--index index-name]' 15 | }); 16 | 17 | var dynamo = utils.dynamo(argv); 18 | function search(params) { 19 | var msecPerItem = Math.round(1000 / params.Limit / ((argv.rate || 100) / 100)); 20 | var method = params.KeyConditions ? dynamo.query : dynamo.scan; 21 | var read = function(start, done, params) { 22 | method.call( 23 | dynamo, 24 | params, 25 | function (err, data) { 26 | if (err != null) { 27 | throw err; 28 | } 29 | if (data == null) { 30 | throw 'dynamo returned NULL instead of data'; 31 | } 32 | for (var idx = 0; idx < data.Items.length; idx++) { 33 | process.stdout.write(JSON.stringify(data.Items[idx])); 34 | process.stdout.write("\n"); 35 | } 36 | var expected = start + msecPerItem * (done + data.Items.length); 37 | if (expected > Date.now()) { 38 | sleep.usleep((expected - Date.now()) * 1000); 39 | } 40 | if (data.LastEvaluatedKey) { 41 | params.ExclusiveStartKey = data.LastEvaluatedKey; 42 | read(start, done + data.Items.length, params); 43 | } 44 | } 45 | ); 46 | }; 47 | read(Date.now(), 0, params); 48 | }; 49 | 50 | dynamo.describeTable( 51 | { 52 | TableName: argv.table 53 | }, 54 | function (err, data) { 55 | if (err != null) { 56 | throw err; 57 | } 58 | if (data == null) { 59 | throw 'Table ' + argv.table + ' not found in DynamoDB'; 60 | } 61 | const rcu = data.Table.ProvisionedThroughput.ReadCapacityUnits; 62 | var params = { 63 | TableName: argv.table, 64 | ReturnConsumedCapacity: 'NONE', 65 | Limit: rcu > 0 ? rcu : 1000 66 | }; 67 | if (argv.index) { 68 | params.IndexName = argv.index 69 | } 70 | 71 | if (argv.query) { 72 | params.KeyConditions = JSON.parse(argv.query); 73 | } 74 | search(params); 75 | } 76 | ); 77 | -------------------------------------------------------------------------------- /test/archive.test.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko 2 | // SPDX-License-Identifier: MIT 3 | 4 | var path = require('path'); 5 | var test = require('tape').test 6 | var dynalite = require('dynalite'); 7 | var exec = require('child_process').exec; 8 | var aws = require('aws-sdk'); 9 | 10 | var tests = []; 11 | 12 | tests.push(['Load archive', function (t, opts, done) { 13 | t.plan(3); 14 | var fixture = path.join(__dirname, 'fixture.json'); 15 | var cmd = './dynamo-restore.js --table testing-table < '+fixture; 16 | exec(cmd, opts, function(err, stdout, stderr) { 17 | t.notOk(err, 'Exit cleanly'); 18 | t.notOk(stdout, 'Clean stdout'); 19 | t.notOk(stderr, 'Clean stderr'); 20 | done(); 21 | }); 22 | }]); 23 | 24 | tests.push(['Export archive', function (t, opts, done) { 25 | t.plan(4); 26 | var cmd = './dynamo-archive.js --table testing-table'; 27 | exec(cmd, opts, function(err, stdout, stderr) { 28 | t.notOk(err, 'Exit cleanly'); 29 | t.notOk(stderr, 'Clean stderr'); 30 | t.ok(stdout.length > 0, 'Got results'); 31 | var records = stdout.split('\n').filter(function(v) { return v.length > 0; }); 32 | t.ok(records.length == 4, 'Found items'); 33 | done(); 34 | }); 35 | }]); 36 | 37 | tests.push(['Export archive with query', function (t, opts, done) { 38 | t.plan(4); 39 | var query = JSON.stringify({ 40 | Name: { 41 | ComparisonOperator: 'EQ', 42 | AttributeValueList: [{ 43 | S: 'foo' 44 | }] 45 | } 46 | }).replace(/"/g,'\\"'); 47 | var cmd = './dynamo-archive.js --table testing-table --query "'+query+'"'; 48 | exec(cmd, opts, function(err, stdout, stderr) { 49 | t.notOk(err, 'Exit cleanly'); 50 | t.notOk(stderr, 'Clean stderr'); 51 | t.ok(stdout.length > 0, 'Got results'); 52 | var records = stdout.split('\n').filter(function(v) { return v.length > 0; }); 53 | t.ok(records.length == 2, 'Found items'); 54 | done(); 55 | }); 56 | }]); 57 | 58 | (function() { 59 | var opts = { 60 | env: { 61 | AWS_ACCESS_KEY_ID: 'fake', 62 | AWS_SECRET_ACCESS_KEY: 'fake', 63 | AWS_DYNAMODB_ENDPOINT: 'http://localhost:', 64 | PATH: process.env.PATH 65 | }, 66 | cwd: path.join(__dirname,'../bin/'), 67 | timeout: 10000 68 | }; 69 | 70 | function runner() { 71 | var current = tests.shift(); 72 | if (!current) { 73 | return dbServer.close(); 74 | } 75 | 76 | test(current[0], function(t) { 77 | current[1].call(this, t, opts, runner); 78 | }); 79 | } 80 | 81 | aws.config.update({ 82 | accessKeyId: opts.env.AWS_ACCESS_KEY_ID, 83 | secretAccessKey: opts.env.AWS_SECRET_ACCESS_KEY, 84 | region: opts.env.AWS_DEFAULT_REGION || 'us-east-1' 85 | }); 86 | 87 | var dbServer = dynalite({createTableMs: 5}); 88 | dbServer.listen(0, function(err) { 89 | if (err != null) { 90 | throw err; 91 | } 92 | opts.env.AWS_DYNAMODB_ENDPOINT += dbServer.address().port; 93 | var dynamo = new aws.DynamoDB({ 94 | endpoint: new aws.Endpoint(opts.env.AWS_DYNAMODB_ENDPOINT) 95 | }); 96 | var params = { 97 | AttributeDefinitions: [ 98 | { 99 | AttributeName: 'Name', 100 | AttributeType: 'S', 101 | }, 102 | { 103 | AttributeName: 'Value', 104 | AttributeType: 'N', 105 | } 106 | ], 107 | KeySchema: [ 108 | { 109 | AttributeName: 'Name', 110 | KeyType: 'HASH', 111 | }, 112 | { 113 | AttributeName: 'Value', 114 | KeyType: 'RANGE', 115 | } 116 | ], 117 | ProvisionedThroughput: { 118 | ReadCapacityUnits: 10, 119 | WriteCapacityUnits: 10 120 | }, 121 | TableName: 'testing-table' 122 | }; 123 | dynamo.createTable(params, function(err, data) { 124 | if (err != null) { 125 | throw err; 126 | } 127 | runner(); 128 | }); 129 | }); 130 | })(); 131 | --------------------------------------------------------------------------------