1 | 'use strict'
|
2 |
|
3 | const co = require('co')
|
4 | const bastion = require('./bastion')
|
5 | const debug = require('./debug')
|
6 |
|
7 | function handlePsqlError (reject, psql) {
|
8 | psql.on('error', (err) => {
|
9 | if (err.code === 'ENOENT') {
|
10 | reject(`The local psql command could not be located. For help installing psql, see https://devcenter.heroku.com/articles/heroku-postgresql#local-setup`)
|
11 | } else {
|
12 | reject(err)
|
13 | }
|
14 | })
|
15 | }
|
16 |
|
17 | function execPsql (query, dbEnv) {
|
18 | const {spawn} = require('child_process')
|
19 | return new Promise((resolve, reject) => {
|
20 | let result = ''
|
21 | debug('Running query: %s', query.trim())
|
22 | let psql = spawn('psql', ['-c', query], {env: dbEnv, encoding: 'utf8', stdio: [ 'ignore', 'pipe', 'inherit' ]})
|
23 | psql.stdout.on('data', function (data) {
|
24 | result += data.toString()
|
25 | })
|
26 | psql.on('close', function (code) {
|
27 | resolve(result)
|
28 | })
|
29 | handlePsqlError(reject, psql)
|
30 | })
|
31 | }
|
32 |
|
33 | function psqlInteractive (dbEnv, prompt) {
|
34 | const {spawn} = require('child_process')
|
35 | return new Promise((resolve, reject) => {
|
36 | let psql = spawn('psql',
|
37 | ['--set', `PROMPT1=${prompt}`, '--set', `PROMPT2=${prompt}`],
|
38 | {env: dbEnv, stdio: 'inherit'})
|
39 | handlePsqlError(reject, psql)
|
40 | psql.on('close', (data) => {
|
41 | resolve()
|
42 | })
|
43 | })
|
44 | }
|
45 |
|
46 | function handleSignals () {
|
47 | process.removeAllListeners('SIGINT')
|
48 | process.on('SIGINT', () => {})
|
49 | }
|
50 |
|
51 | function * exec (db, query) {
|
52 | handleSignals()
|
53 | let configs = bastion.getConfigs(db)
|
54 |
|
55 | yield bastion.sshTunnel(db, configs.dbTunnelConfig)
|
56 | return yield execPsql(query, configs.dbEnv)
|
57 | }
|
58 |
|
59 | function * interactive (db) {
|
60 | const pgUtil = require('./util')
|
61 | let name = pgUtil.getUrl(db.attachment.config_vars).replace(/^HEROKU_POSTGRESQL_/, '').replace(/_URL$/, '')
|
62 | let prompt = `${db.attachment.app.name}::${name}%R%# `
|
63 | handleSignals()
|
64 | let configs = bastion.getConfigs(db)
|
65 |
|
66 | yield bastion.sshTunnel(db, configs.dbTunnelConfig)
|
67 | return yield psqlInteractive(configs.dbEnv, prompt)
|
68 | }
|
69 |
|
70 | module.exports = {
|
71 | exec: co.wrap(exec),
|
72 | interactive: co.wrap(interactive)
|
73 | }
|