UNPKG

3.23 kBJavaScriptView Raw
1'use strict'
2
3const debug = require('./debug')
4const tunnel = require('tunnel-ssh')
5const cli = require('heroku-cli-util')
6const host = require('./host')
7
8const getBastion = function (config, baseName) {
9 const { sample } = require('lodash')
10 // If there are bastions, extract a host and a key
11 // otherwise, return an empty Object
12
13 // If there are bastions:
14 // * there should be one *_BASTION_KEY
15 // * pick one host from the comma-separated list in *_BASTIONS
16 // We assert that _BASTIONS and _BASTION_KEY always exist together
17 // If either is falsy, pretend neither exist
18
19 const bastionKey = config[`${baseName}_BASTION_KEY`]
20 const bastionHost = sample((config[`${baseName}_BASTIONS`] || '').split(','))
21 return (!(bastionKey && bastionHost))
22 ? {}
23 : { bastionHost, bastionKey }
24}
25
26exports.getBastion = getBastion
27
28const env = function (db) {
29 let baseEnv = Object.assign({
30 PGAPPNAME: 'psql non-interactive',
31 PGSSLMODE: (!db.hostname || db.hostname === 'localhost') ? 'prefer' : 'require'
32 }, process.env)
33 let mapping = {
34 PGUSER: 'user',
35 PGPASSWORD: 'password',
36 PGDATABASE: 'database',
37 PGPORT: 'port',
38 PGHOST: 'host'
39 }
40 Object.keys(mapping).forEach((envVar) => {
41 let val = db[mapping[envVar]]
42 if (val) {
43 baseEnv[envVar] = val
44 }
45 })
46 return baseEnv
47}
48
49exports.env = env
50
51function tunnelConfig (db) {
52 const localHost = '127.0.0.1'
53 const localPort = Math.floor(Math.random() * (65535 - 49152) + 49152)
54 return {
55 username: 'bastion',
56 host: db.bastionHost,
57 privateKey: db.bastionKey,
58 dstHost: db.host,
59 dstPort: db.port,
60 localHost: localHost,
61 localPort: localPort
62 }
63}
64
65exports.tunnelConfig = tunnelConfig
66
67function getConfigs (db) {
68 let dbEnv = env(db)
69 const dbTunnelConfig = tunnelConfig(db)
70 if (db.bastionKey) {
71 dbEnv = Object.assign(dbEnv, {
72 PGPORT: dbTunnelConfig.localPort,
73 PGHOST: dbTunnelConfig.localHost
74 })
75 }
76 return {
77 dbEnv: dbEnv,
78 dbTunnelConfig: dbTunnelConfig
79 }
80}
81
82exports.getConfigs = getConfigs
83
84function sshTunnel (db, dbTunnelConfig, timeout) {
85 return new Promise((resolve, reject) => {
86 // if necessary to tunnel, setup a tunnel
87 // see also https://github.com/heroku/heroku/blob/master/lib/heroku/helpers/heroku_postgresql.rb#L53-L80
88 let timer = setTimeout(() => reject(new Error('Establishing a secure tunnel timed out')), timeout)
89 if (db.bastionKey) {
90 let tun = tunnel(dbTunnelConfig, (err, tnl) => {
91 if (err) {
92 debug(err)
93 reject(new Error('Unable to establish a secure tunnel to your database.'))
94 }
95 debug('Tunnel created')
96 clearTimeout(timer)
97 resolve(tnl)
98 })
99 tun.on('error', (err) => {
100 // we can't reject the promise here because we may already have resolved it
101 debug(err)
102 cli.exit(1, 'Secure tunnel to your database failed')
103 })
104 } else {
105 clearTimeout(timer)
106 resolve()
107 }
108 })
109}
110
111exports.sshTunnel = sshTunnel
112
113function * fetchConfig (heroku, db) {
114 return yield heroku.get(
115 `/client/v11/databases/${encodeURIComponent(db.id)}/bastion`,
116 {
117 host: host(db)
118 }
119 )
120}
121
122exports.fetchConfig = fetchConfig