UNPKG

3.59 kBJavaScriptView Raw
1'use strict'
2
3const co = require('co')
4const bastion = require('./bastion')
5const debug = require('./debug')
6
7function 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
17function 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
33function execPsqlWithFile (file, dbEnv) {
34 const { spawn } = require('child_process')
35 return new Promise((resolve, reject) => {
36 let result = ''
37 debug('Running sql file: %s', file.trim())
38 let psql = spawn('psql', ['-f', file], { env: dbEnv, encoding: 'utf8', stdio: [ 'ignore', 'pipe', 'inherit' ] })
39 psql.stdout.on('data', function (data) {
40 result += data.toString()
41 })
42 psql.on('close', function (code) {
43 resolve(result)
44 })
45 handlePsqlError(reject, psql)
46 })
47}
48
49function psqlInteractive (dbEnv, prompt) {
50 const { spawn } = require('child_process')
51 return new Promise((resolve, reject) => {
52 let psqlArgs = ['--set', `PROMPT1=${prompt}`, '--set', `PROMPT2=${prompt}`]
53 let psqlHistoryPath = process.env.HEROKU_PSQL_HISTORY
54 if (psqlHistoryPath) {
55 const fs = require('fs')
56 const path = require('path')
57 if (fs.existsSync(psqlHistoryPath) && fs.statSync(psqlHistoryPath).isDirectory()) {
58 let appLogFile = `${psqlHistoryPath}/${prompt.split(':')[0]}`
59 debug('Logging psql history to %s', appLogFile)
60 psqlArgs = psqlArgs.concat(['--set', `HISTFILE=${appLogFile}`])
61 } else if (fs.existsSync(path.dirname(psqlHistoryPath))) {
62 debug('Logging psql history to %s', psqlHistoryPath)
63 psqlArgs = psqlArgs.concat(['--set', `HISTFILE=${psqlHistoryPath}`])
64 } else {
65 const cli = require('heroku-cli-util')
66 cli.warn(`HEROKU_PSQL_HISTORY is set but is not a valid path (${psqlHistoryPath})`)
67 }
68 }
69
70 let psql = spawn('psql', psqlArgs, { env: dbEnv, stdio: 'inherit' })
71 handlePsqlError(reject, psql)
72 psql.on('close', (data) => {
73 resolve()
74 })
75 })
76}
77
78function handleSignals () {
79 process.removeAllListeners('SIGINT')
80 process.on('SIGINT', () => {})
81}
82
83function * exec (db, query) {
84 handleSignals()
85 let configs = bastion.getConfigs(db)
86
87 yield bastion.sshTunnel(db, configs.dbTunnelConfig)
88 return yield execPsql(query, configs.dbEnv)
89}
90
91async function execFile (db, file) {
92 handleSignals()
93 let configs = bastion.getConfigs(db)
94
95 await bastion.sshTunnel(db, configs.dbTunnelConfig)
96 return execPsqlWithFile(file, configs.dbEnv)
97}
98
99function * interactive (db) {
100 let name = db.attachment.name
101 let prompt = `${db.attachment.app.name}::${name}%R%# `
102 handleSignals()
103 let configs = bastion.getConfigs(db)
104 configs.dbEnv.PGAPPNAME = 'psql interactive' // default was 'psql non-interactive`
105
106 yield bastion.sshTunnel(db, configs.dbTunnelConfig)
107 return yield psqlInteractive(configs.dbEnv, prompt)
108}
109
110module.exports = {
111 exec: co.wrap(exec),
112 execFile: execFile,
113 interactive: co.wrap(interactive)
114}