initial commit

This commit is contained in:
Nils Norman Haukås 2018-05-20 16:31:59 +02:00
commit ce5e2a852f
12 changed files with 347 additions and 0 deletions

16
.env-example Normal file
View File

@ -0,0 +1,16 @@
# It's possible to specify any number of targets
# Example configuration
# Run 'hamstr mysqldump prod' to target this
HAMSTR_PROD_DB=mysql://username:pw@127.0.0.1:3306/databasename
HAMSTR_PROD_SSH=username@someserver.com
HAMSTR_PROD_SSH_PW=randompassword
# Run 'hamstr mysqldump stage' to target this
HAMSTR_STAGE_DB=
HAMSTR_STAGE_SSH=
HAMSTR_STAGE_SSH_PW=
# If you don't need tunnelling you just need
# to provide a DB connection string
HAMSTR_QA_DB=

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.env
*.sql
/node_modules/
.env
.DS_Store
/dist

3
.travis.yml Normal file
View File

@ -0,0 +1,3 @@
language: node_js
node_js:
- 'lts/*'

5
CHANGELOG.txt Normal file
View File

@ -0,0 +1,5 @@
# Changelog
## 1.0.0
- Initial release.

21
LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Nils Norman Haukås
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
README.md Normal file
View File

@ -0,0 +1,30 @@
# HAMSTR
Move mountains of SQL data.
## Features
* Run mysqldump through a ssh tunnel.
## Installation
Global installation: `npm i -g hamstr`
Project installation: `npm i hamstr`
## Usage
1. Copy `.env-example` into `.env` and fill out credentials.
1. Run `hamstr mysqldump --tunnel prod` to download the database target `PROD` defined in the .env file.
1. Run `hamstr --help` to get a functionality overview.
## Development and test
Not many tests yet, but we have Typescript.
1. Run `npm test` to trigger typescript build.
* Run `npm build -- --watch` to start a auto-compiling process which builds on file save.
## License
Project code is licensed under the MIT license, [see license](LICENSE.txt).

96
package-lock.json generated Normal file
View File

@ -0,0 +1,96 @@
{
"name": "hamstr",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "8.10.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.17.tgz",
"integrity": "sha512-3N3FRd/rA1v5glXjb90YdYUa+sOB7WrkU2rAhKZnF4TKD86Cym9swtulGuH0p9nxo7fP5woRNa8b0oFTpCO1bg==",
"dev": true
},
"asn1": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"dotenv": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz",
"integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow=="
},
"lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mysql-parse": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/mysql-parse/-/mysql-parse-2.0.3.tgz",
"integrity": "sha512-P+iGsXIZFbfU7Im1WRQWG2/OEnBxTMotRgrXV8V1zRNdbUeBHaW4ojtKRf8P1JQ5QfJ3z4bfZFfZXZhxir89/A=="
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"ssh2": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.5.4.tgz",
"integrity": "sha1-G/a2soyW6u8mf01sRqWiUXpZnic=",
"requires": {
"ssh2-streams": "~0.1.15"
}
},
"ssh2-streams": {
"version": "0.1.20",
"resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.1.20.tgz",
"integrity": "sha1-URGNFUVV31Rp7h9n4M8efoosDjo=",
"requires": {
"asn1": "~0.2.0",
"semver": "^5.1.0",
"streamsearch": "~0.1.2"
}
},
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"tunnel-ssh": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tunnel-ssh/-/tunnel-ssh-4.1.4.tgz",
"integrity": "sha512-CjBqboGvAbM7iXSX2F95kzoI+c2J81YkrHbyyo4SWNKCzU6w5LfEvXBCHu6PPriYaNvfhMKzD8bFf5Vl14YTtg==",
"requires": {
"debug": "2.6.9",
"lodash.defaults": "^4.1.0",
"ssh2": "0.5.4"
}
},
"typescript": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz",
"integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==",
"dev": true
}
}
}

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "hamstr",
"version": "1.0.0",
"description": "Let the hamster move mountains of MySQL data.",
"main": "./dist/index.js",
"scripts": {
"build": "tsc",
"test": "tsc"
},
"keywords": [
"mysql",
"ssh-tunnel",
"mysqldump"
],
"author": "Nils Norman Haukås",
"license": "MIT",
"dependencies": {
"commander": "^2.15.1",
"dotenv": "^5.0.1",
"mysql-parse": "^2.0.3",
"tunnel-ssh": "^4.1.4"
},
"bin": {
"hamstr": "./dist/index.js"
},
"devDependencies": {
"@types/node": "^8.10.17",
"typescript": "^2.8.3"
},
"engines": {
"node": ">=8"
}
}

2
src/definitions.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
declare module 'tunnel-ssh'
declare module 'mysql-parse'

29
src/index.ts Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env node
import program from 'commander'
require('dotenv').config()
const pjson = require('../package.json')
program.version(pjson.version).description(pjson.description)
program
.command('mysqldump <target>')
.description('Use mysqldump to download target defined in .env file.')
.option('-t --tunnel', 'open an ssh tunnel before running mysqldump')
.option(
'-p --path <path>',
'Path to save mysql dump, default is saving into a file named target.sql'
)
.action((target, options) => {
try {
require('./lib').mysqldump({ target, options })
} catch (e) {
console.error(e.message)
process.exit(1)
}
})
program.parse(process.argv)
if (process.argv.slice(2).length === 0) {
console.error('Heyo! Run "hamstr --help" for the manual.')
}

77
src/lib.ts Normal file
View File

@ -0,0 +1,77 @@
import { buildMysqlParams, parseUri } from 'mysql-parse'
import { promisify } from 'util'
const exec = promisify(require('child_process').exec)
const tunnel = promisify(require('tunnel-ssh'))
interface ProgramInput {
target: string
options: {
tunnel?: Boolean
path?: string
}
}
interface MysqldumpConfig {
uri: string
path: string
}
interface TunnelConfig {
host: string
dstPort: string
username: string
password: string
}
function buildMysqldumpConfig({
target,
options
}: ProgramInput): MysqldumpConfig {
const uri = getOrExit(`HAMSTR_${target}_DB`)
const { path = `${target.toLowerCase()}.sql` } = options
return { uri, path }
}
function buildTunnelConfig({ target, options }: ProgramInput): TunnelConfig {
const [originalstring, username, host] = <Array<string>>/(\w*)@(.*)/g.exec(
getOrExit(`HAMSTR_${target}_SSH`)
)
const password = getOrExit(`HAMSTR_${target}_SSH_PW`)
const { port: dstPort } = parseUri(getOrExit(`HAMSTR_${target}_DB`))
return { host, dstPort, username, password }
}
async function mysqldump({ target, options }: ProgramInput) {
target = target.toUpperCase()
if (options.tunnel) {
await openTunnel(buildTunnelConfig({ target, options }))
}
runMysqldump(buildMysqldumpConfig({ target, options }))
}
async function openTunnel(tunnelConfig: TunnelConfig) {
await tunnel(tunnelConfig)
const { username, host } = tunnelConfig
console.log(`Opened tunnel to ${username}@${host}`)
}
async function runMysqldump({ uri, path }: MysqldumpConfig) {
const params = buildMysqlParams(uri)
console.log(`Running mysqldump, saving data in ${path}`)
const child = await exec(`mysqldump ${params} > ${path}`, null)
// if we kill node we should kill mysql as well
process.on('exit', () => child.kill())
}
function getOrExit(variable: string): string {
if (!process.env[variable]) {
throw new Error(
`Tried looking up ${variable} but could not find it. \n\nDid you define an entry for this in your .env file?`
)
}
return process.env[variable] || ''
}
module.exports = {
mysqldump
}

29
tsconfig.json Normal file
View File

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist" /* Redirect output structure to the directory. */,
"rootDir":
"./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution":
"node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
}
}