dblink 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/dblink +189 -0
- data/config.ini.erb +13 -0
- data/dblink.gemspec +16 -0
- data/lib/dblink/color.rb +25 -0
- data/lib/dblink/network.rb +10 -0
- data/lib/dblink/pg_bouncer_runner.rb +124 -0
- data/lib/dblink/psql_runner.rb +48 -0
- data/lib/dblink/system.rb +24 -0
- data/lib/dblink/tonnel_runner.rb +90 -0
- data/lib/dblink/web_service.rb +34 -0
- data/pgbouncer +0 -0
- data/share.pem +27 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3b0e7af330863d700229caf232c3c2ae55931b42
|
4
|
+
data.tar.gz: bb6ab4186a21d60076e51bd484ffc3f2be908b9a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4316a3ab2414ee53970ef3a21c49996448f93744e2d9d720f332bfaa9202d872568b32b447ae2044a064b15453371cdc96226e453f83c8e618741b7eb79b7058
|
7
|
+
data.tar.gz: ecbb1c23a0b4aac78ebe3358cd4f5af84420e767392a7c0be7db375c5fc4dc60c38cff05e8cf1a8aab26880cc932d04fe36920d02d96b04d3dbe56430b4d120c
|
data/bin/dblink
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'open3'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'erb'
|
7
|
+
require 'securerandom'
|
8
|
+
require 'json'
|
9
|
+
require 'optparse'
|
10
|
+
require 'fileutils'
|
11
|
+
|
12
|
+
BASE_DIR = File.expand_path(File.dirname(__FILE__) + '/..')
|
13
|
+
|
14
|
+
require BASE_DIR + "/lib/dblink/network"
|
15
|
+
require BASE_DIR + "/lib/dblink/system"
|
16
|
+
require BASE_DIR + "/lib/dblink/pg_bouncer_runner"
|
17
|
+
require BASE_DIR + "/lib/dblink/tonnel_runner"
|
18
|
+
require BASE_DIR + "/lib/dblink/psql_runner"
|
19
|
+
require BASE_DIR + "/lib/dblink/web_service"
|
20
|
+
require BASE_DIR + "/lib/dblink/color"
|
21
|
+
|
22
|
+
PUBLIC_HOST = 'dblink.tk'
|
23
|
+
|
24
|
+
CLI_OPTS = {
|
25
|
+
verbose: false,
|
26
|
+
host: 'localhost',
|
27
|
+
port: 5432,
|
28
|
+
user: ENV['USER'],
|
29
|
+
database: ENV['USER'],
|
30
|
+
check_local: true,
|
31
|
+
check_tonnel: true
|
32
|
+
}
|
33
|
+
|
34
|
+
OptionParser.new do |opts|
|
35
|
+
opts.banner = "Usage: dblink [options]"
|
36
|
+
|
37
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
38
|
+
CLI_OPTS[:verbose] = v
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on("-H", "--host [HOST]", "PostgreSQL host", "Default: localhost") do |value|
|
42
|
+
CLI_OPTS[:host] = value
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on("-p", "--port [PORT]", "PostgreSQL port", "Default: 5432") do |value|
|
46
|
+
CLI_OPTS[:port] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("-d", "--database [DATABASE]", "PostgreSQL database", "Default: #{ENV['USER']}") do |value|
|
50
|
+
CLI_OPTS[:database] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("-u", "--user [USER]", "User to connect to local PostgreSQL server", "Default: #{ENV['USER']}") do |value|
|
54
|
+
CLI_OPTS[:user] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on("-P", "--password [PASSWORD]", "PostgreSQL password") do |value|
|
58
|
+
CLI_OPTS[:password] = value
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on("-cl", "--[no-]check-local", "Default true. Check connection to local PostgreSQL server") do |value|
|
62
|
+
CLI_OPTS[:check_local] = value
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on("-cr", "--[no-]check-remote", "Default true. Check connection via ssh tonnel") do |value|
|
66
|
+
CLI_OPTS[:check_tonnel] = value
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
70
|
+
puts opts
|
71
|
+
exit
|
72
|
+
end
|
73
|
+
|
74
|
+
end.parse!
|
75
|
+
|
76
|
+
#p BASE_DIR
|
77
|
+
|
78
|
+
if CLI_OPTS[:check_local]
|
79
|
+
print "Checking connection to "
|
80
|
+
print "#{CLI_OPTS[:host]}:#{CLI_OPTS[:port]}".underline
|
81
|
+
|
82
|
+
result = PsqlRunner.check_connection(CLI_OPTS)
|
83
|
+
|
84
|
+
print " - "
|
85
|
+
puts result === true ? "OK".green : "FAILED".red
|
86
|
+
|
87
|
+
if result != true
|
88
|
+
puts
|
89
|
+
puts result
|
90
|
+
puts
|
91
|
+
|
92
|
+
exit(1)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
pgb_runner = PgBouncerRunner.new(
|
97
|
+
pg_host: CLI_OPTS[:host],
|
98
|
+
pg_port: CLI_OPTS[:port],
|
99
|
+
pg_user: CLI_OPTS[:user],
|
100
|
+
pg_password: CLI_OPTS[:password],
|
101
|
+
pg_db: CLI_OPTS[:database],
|
102
|
+
verbose: CLI_OPTS[:verbose]
|
103
|
+
)
|
104
|
+
|
105
|
+
print "Creating configs in /tmp folder"
|
106
|
+
pgb_runner.create_config
|
107
|
+
puts " - " + "OK".green
|
108
|
+
|
109
|
+
if CLI_OPTS[:verbose]
|
110
|
+
puts "Config folder: #{pgb_runner.tmp_folder}"
|
111
|
+
end
|
112
|
+
|
113
|
+
print "Starting pgbouncer "
|
114
|
+
pgb_runner.run_async
|
115
|
+
pgb_pid = pgb_runner.wait_for_pid
|
116
|
+
puts "- " + "OK".green + " (pid: #{pgb_pid})"
|
117
|
+
|
118
|
+
print "Starting SSH tonnel "
|
119
|
+
tonnel = TonnelRunner.new(pgb_runner.pgb_port, tmp_dir: pgb_runner.tmp_folder, verbose: CLI_OPTS[:verbose])
|
120
|
+
tonnel.run_async
|
121
|
+
tonnel.wait_remote_port_allocated
|
122
|
+
|
123
|
+
puts "- " + "OK".green + " (pid: #{tonnel.runner_pid})"
|
124
|
+
puts "Remote port: #{tonnel.remote_port}"
|
125
|
+
|
126
|
+
psql_opt = {
|
127
|
+
user: pgb_runner.pgb_user,
|
128
|
+
password: pgb_runner.pgb_password,
|
129
|
+
database: pgb_runner.pg_db,
|
130
|
+
port: tonnel.remote_port,
|
131
|
+
host: PUBLIC_HOST,
|
132
|
+
verbose: CLI_OPTS[:verbose]
|
133
|
+
}
|
134
|
+
|
135
|
+
if CLI_OPTS[:check_tonnel]
|
136
|
+
print "Checking connection: "
|
137
|
+
result = PsqlRunner.check_connection(psql_opt)
|
138
|
+
puts result === true ? "OK".green : "FAILED".red
|
139
|
+
|
140
|
+
if result != true
|
141
|
+
puts
|
142
|
+
puts result
|
143
|
+
puts
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
db_url = pgb_runner.make_db_url(tonnel.remote_port)
|
148
|
+
|
149
|
+
puts
|
150
|
+
puts "DATABSE CONNECTION:"
|
151
|
+
puts " #{db_url}"
|
152
|
+
puts
|
153
|
+
|
154
|
+
psql_path = PsqlRunner.find_psql
|
155
|
+
if psql_path
|
156
|
+
puts "CONNECT WITH PSQL:"
|
157
|
+
passwd = pgb_runner.pgb_password == '' ? '' : "PGPASSWORD=#{pgb_runner.pgb_password}"
|
158
|
+
puts " #{passwd} #{psql_path} -h dblink.tk -p #{tonnel.remote_port} -U #{pgb_runner.pgb_user} #{pgb_runner.pg_db}"
|
159
|
+
puts
|
160
|
+
end
|
161
|
+
|
162
|
+
web_response = WebService.register_link(db_url, verbose: CLI_OPTS[:verbose])
|
163
|
+
|
164
|
+
if web_response['status'] == 'success'
|
165
|
+
puts "WEB URL:"
|
166
|
+
puts " #{web_response['web_url']}"
|
167
|
+
puts
|
168
|
+
end
|
169
|
+
|
170
|
+
at_exit do
|
171
|
+
print "Stoping pgbouncer ..."
|
172
|
+
pgb_runner.stop_async
|
173
|
+
puts
|
174
|
+
print "Stoping ssh ..."
|
175
|
+
tonnel.stop_async
|
176
|
+
puts
|
177
|
+
|
178
|
+
tonnel.runner_thread.kill
|
179
|
+
|
180
|
+
puts "Removing tmp dir ..."
|
181
|
+
FileUtils.rm_rf(pgb_runner.tmp_folder)
|
182
|
+
|
183
|
+
exit!
|
184
|
+
end
|
185
|
+
|
186
|
+
tonnel.runner_thread.value
|
187
|
+
|
188
|
+
#ssh_command = SSH_TONNEL_COMMAND.gsub('{{local_port}}', '6543')
|
189
|
+
#system ssh_command
|
data/config.ini.erb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
[databases]
|
2
|
+
<%= pg_db %> = host=<%= pg_host %> port=<%= pg_port %> dbname=<%= pg_db %> user=<%= pg_user %> <%= pg_password && pg_password != '' ? "password=#{pg_password}" : '' %>
|
3
|
+
|
4
|
+
[pgbouncer]
|
5
|
+
pool_mode = session
|
6
|
+
listen_port = <%= pgb_port %>
|
7
|
+
listen_addr = 127.0.0.1
|
8
|
+
auth_type = plain
|
9
|
+
auth_file = ./users.txt
|
10
|
+
logfile = pgbouncer.log
|
11
|
+
pidfile = pgbouncer.pid
|
12
|
+
admin_users = someuser
|
13
|
+
stats_users = stat_collector
|
data/dblink.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "dblink"
|
3
|
+
s.version = "0.2"
|
4
|
+
s.author = ["Pavel Evstigneev"]
|
5
|
+
s.description= "CLI utility to share access to your local PostgreSQL server"
|
6
|
+
s.email = ["pavel.evst@gmail.com"]
|
7
|
+
s.homepage = "http://dblink.r15.railsrumble.com"
|
8
|
+
s.summary = %q{Veritrans ruby library}
|
9
|
+
s.license = 'MIT'
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.test_files = []
|
13
|
+
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
s.executables = ["dblink"]
|
16
|
+
end
|
data/lib/dblink/color.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class String
|
2
|
+
def black; "\e[30m#{self}\e[0m" end
|
3
|
+
def red; "\e[31m#{self}\e[0m" end
|
4
|
+
def green; "\e[32m#{self}\e[0m" end
|
5
|
+
def brown; "\e[33m#{self}\e[0m" end
|
6
|
+
def blue; "\e[34m#{self}\e[0m" end
|
7
|
+
def magenta; "\e[35m#{self}\e[0m" end
|
8
|
+
def cyan; "\e[36m#{self}\e[0m" end
|
9
|
+
def gray; "\e[37m#{self}\e[0m" end
|
10
|
+
|
11
|
+
def bg_black; "\e[40m#{self}\e[0m" end
|
12
|
+
def bg_red; "\e[41m#{self}\e[0m" end
|
13
|
+
def bg_green; "\e[42m#{self}\e[0m" end
|
14
|
+
def bg_brown; "\e[43m#{self}\e[0m" end
|
15
|
+
def bg_blue; "\e[44m#{self}\e[0m" end
|
16
|
+
def bg_magenta; "\e[45m#{self}\e[0m" end
|
17
|
+
def bg_cyan; "\e[46m#{self}\e[0m" end
|
18
|
+
def bg_gray; "\e[47m#{self}\e[0m" end
|
19
|
+
|
20
|
+
def bold; "\e[1m#{self}\e[22m" end
|
21
|
+
def italic; "\e[3m#{self}\e[23m" end
|
22
|
+
def underline; "\e[4m#{self}\e[24m" end
|
23
|
+
def blink; "\e[5m#{self}\e[25m" end
|
24
|
+
def reverse_color; "\e[7m#{self}\e[27m" end
|
25
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
class PgBouncerRunner
|
2
|
+
attr_reader :pg_port, :pg_user, :pg_password, :pg_host, :pg_db
|
3
|
+
attr_reader :pgb_port, :pgb_user, :pgb_password
|
4
|
+
attr_reader :tmp_folder, :runner_thread, :runner_pid
|
5
|
+
|
6
|
+
def initialize(pg_port: 5432, pg_user: ENV['USER'], pg_password: '', pg_host: '127.0.0.1', pg_db: ENV['USER'], verbose: true)
|
7
|
+
@pg_port = pg_port
|
8
|
+
@pg_user = pg_user
|
9
|
+
@pg_password = pg_password
|
10
|
+
@pg_host = pg_host
|
11
|
+
@pg_db = pg_db
|
12
|
+
@verbose = verbose
|
13
|
+
|
14
|
+
@pgb_port = Network.find_available_port
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
create_config unless @tmp_folder
|
19
|
+
|
20
|
+
verbose_arg = @verbose ? '-v' : ''
|
21
|
+
pgb_command = "#{pgbouncer_path} #{verbose_arg} ./config.ini"
|
22
|
+
|
23
|
+
if @verbose
|
24
|
+
puts "Running pgbouncer with config at: #{@tmp_folder}"
|
25
|
+
puts "EXEC #{pgb_command}"
|
26
|
+
end
|
27
|
+
|
28
|
+
Dir.chdir(@tmp_folder) do
|
29
|
+
Open3.popen3(pgb_command) {|stdin, stdout, stderr, wait_thr|
|
30
|
+
@runner_pid = wait_thr.pid # pid of the started process.
|
31
|
+
#p "process_started: #{pid}"
|
32
|
+
Thread.new do
|
33
|
+
begin
|
34
|
+
while line = stdout.gets
|
35
|
+
puts "STDOUT: #{line}" if @verbose
|
36
|
+
end
|
37
|
+
rescue Exception => error
|
38
|
+
puts error.message
|
39
|
+
puts error.backtrace
|
40
|
+
end
|
41
|
+
end
|
42
|
+
Thread.new do
|
43
|
+
begin
|
44
|
+
while line = stderr.gets
|
45
|
+
puts "STDERR: #{line}" if @verbose
|
46
|
+
end
|
47
|
+
rescue Exception => error
|
48
|
+
puts error.message
|
49
|
+
puts error.backtrace
|
50
|
+
end
|
51
|
+
end
|
52
|
+
exit_status = wait_thr.value # Process::Status object returned.
|
53
|
+
puts "pgbouncer stoped: #{exit_status}" if @verbose
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def pgbouncer_path
|
59
|
+
if RUBY_PLATFORM =~ /darwin/
|
60
|
+
BASE_DIR + '/pgbouncer'
|
61
|
+
else
|
62
|
+
|
63
|
+
path = `which pgbouncer 2>/dev/null`
|
64
|
+
if path == ''
|
65
|
+
puts "Can not find pgbouncer in your system"
|
66
|
+
puts "Please install it"
|
67
|
+
exit(1)
|
68
|
+
else
|
69
|
+
return path
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def stop_async
|
76
|
+
if @runner_pid && System.process_alive?(@runner_pid, verbose: @verbose)
|
77
|
+
System.stop_process(@runner_pid)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def run_async
|
82
|
+
@runner_thread = Thread.new do
|
83
|
+
begin
|
84
|
+
run
|
85
|
+
rescue Exception => error
|
86
|
+
puts error.message
|
87
|
+
puts error.backtrace
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
def wait_for_pid
|
95
|
+
while !@runner_pid
|
96
|
+
sleep 0.04
|
97
|
+
end
|
98
|
+
|
99
|
+
@runner_pid
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_config
|
103
|
+
@tmp_folder = Dir.mktmpdir
|
104
|
+
pgb_config = render_template('config.ini.erb', {})
|
105
|
+
File.open(@tmp_folder + '/config.ini', 'w') {|f| f.write(pgb_config) }
|
106
|
+
|
107
|
+
@pgb_user = "share"
|
108
|
+
@pgb_password = SecureRandom.hex(10)
|
109
|
+
File.open(@tmp_folder + '/users.txt', 'w') {|f|
|
110
|
+
f.write(%{"#{@pgb_user}" "#{@pgb_password}"})
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def make_db_url(remote_port)
|
115
|
+
"postgres://#{pgb_user}:#{pgb_password}@dblink.tk:#{remote_port}/#{pg_db}"
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
def render_template(file, params)
|
120
|
+
erb = ERB.new(File.read(file))
|
121
|
+
erb.filename = file
|
122
|
+
erb.result(binding)
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module PsqlRunner
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def check_connection(opt = {})
|
5
|
+
psql_path = find_psql
|
6
|
+
|
7
|
+
unless psql_path
|
8
|
+
result = "Can not find #{'psql'.bold} in your system\n"
|
9
|
+
if RUBY_PLATFORM =~ /darwin/
|
10
|
+
result += "Please run 'brew install postgres' or download it from http://postgresapp.com"
|
11
|
+
else
|
12
|
+
result += "Please install postgresql package in your system"
|
13
|
+
end
|
14
|
+
result += "\nOr disable connection checking (--no-check-local and --no-check-remote)"
|
15
|
+
|
16
|
+
return result
|
17
|
+
end
|
18
|
+
|
19
|
+
passwd = opt[:password] && opt[:password] == '' ? '' : "PGPASSWORD=#{opt[:password]}"
|
20
|
+
|
21
|
+
psql_command = "#{passwd} #{psql_path} -h #{opt[:host]} -p #{opt[:port]} -U #{opt[:user]} #{opt[:database]} -c 'select now()'"
|
22
|
+
|
23
|
+
if opt[:verbose]
|
24
|
+
puts "EXEC #{psql_command}"
|
25
|
+
end
|
26
|
+
|
27
|
+
result = %x(#{psql_command} 2>&1)
|
28
|
+
|
29
|
+
#unless result.include?('(1 row)')
|
30
|
+
# puts result
|
31
|
+
#end
|
32
|
+
|
33
|
+
result.include?('(1 row)') ? true : result
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find_psql
|
37
|
+
path = `which psql`
|
38
|
+
|
39
|
+
if path != ''
|
40
|
+
path = 'psql'
|
41
|
+
elsif RUBY_PLATFORM =~ /darwin/
|
42
|
+
paths = Dir.glob('/Applications/Postgres.app/Contents/Versions/**/psql')
|
43
|
+
path = paths.last if paths.size > 0
|
44
|
+
end
|
45
|
+
|
46
|
+
path == '' ? nil : path
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module System
|
2
|
+
|
3
|
+
def self.process_alive?(pid, verbose: true)
|
4
|
+
begin
|
5
|
+
Process.kill(0, pid)
|
6
|
+
return true
|
7
|
+
rescue Errno::EPERM # changed uid
|
8
|
+
puts "No permission to query #{pid}!" if verbose
|
9
|
+
rescue Errno::ESRCH
|
10
|
+
puts "#{pid} is NOT running." if verbose # or zombied
|
11
|
+
rescue
|
12
|
+
puts "Unable to determine status for #{pid} : #{$!}" if verbose
|
13
|
+
end
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.stop_process(pid)
|
18
|
+
Process.kill('INT', pid)
|
19
|
+
while process_alive?(pid, verbose: CLI_OPTS[:verbose])
|
20
|
+
print "."
|
21
|
+
sleep 0.3
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class TonnelRunner
|
2
|
+
SSH_TONNEL_COMMAND = "ssh -N -R 0.0.0.0:0:localhost:{{local_port}} -i {{pem}} -o StrictHostKeyChecking=no share@dblink.tk"
|
3
|
+
PEM_FILE = "#{BASE_DIR}/share.pem"
|
4
|
+
|
5
|
+
attr_reader :local_port, :remote_port, :runner_thread, :runner_pid
|
6
|
+
|
7
|
+
def initialize(local_port, tmp_dir:, verbose: true)
|
8
|
+
@local_port = local_port.to_i
|
9
|
+
@verbose = verbose
|
10
|
+
@tmp_dir = tmp_dir
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
ssh_command = SSH_TONNEL_COMMAND.gsub('{{local_port}}', @local_port.to_s)
|
15
|
+
|
16
|
+
puts "Copying pem file to tmp folder" if @verbose
|
17
|
+
|
18
|
+
tmp_pem = @tmp_dir + '/share.pem'
|
19
|
+
File.open(tmp_pem, 'w') do |file|
|
20
|
+
file.write(File.read(PEM_FILE))
|
21
|
+
end
|
22
|
+
File.chmod(0600, tmp_pem)
|
23
|
+
|
24
|
+
ssh_command.gsub!('{{pem}}', tmp_pem)
|
25
|
+
|
26
|
+
if @verbose
|
27
|
+
puts "EXEC #{ssh_command}"
|
28
|
+
end
|
29
|
+
Open3.popen3(ssh_command) {|stdin, stdout, stderr, wait_thr|
|
30
|
+
@runner_pid = wait_thr.pid # pid of the started process.
|
31
|
+
#p "process_started: #{pid}"
|
32
|
+
Thread.new do
|
33
|
+
begin
|
34
|
+
while line = stdout.gets
|
35
|
+
puts "STDOUT: #{line}" if @verbose
|
36
|
+
end
|
37
|
+
rescue Exception => error
|
38
|
+
puts error.message
|
39
|
+
puts error.backtrace
|
40
|
+
end
|
41
|
+
end
|
42
|
+
Thread.new do
|
43
|
+
begin
|
44
|
+
while line = stderr.gets
|
45
|
+
#puts "STDERR: #{line}"
|
46
|
+
if line =~ /Allocated port/
|
47
|
+
@remote_port = line.match(/Allocated port (\d+)\s/)[1].to_i
|
48
|
+
#puts "Remote port: #{@remote_port}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue Exception => error
|
52
|
+
puts error.message
|
53
|
+
puts error.backtrace
|
54
|
+
end
|
55
|
+
end
|
56
|
+
exit_status = wait_thr.value # Process::Status object returned.
|
57
|
+
puts "SSH tonnel: process stoped: #{exit_status}" if @verbose
|
58
|
+
}
|
59
|
+
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_async
|
64
|
+
@runner_thread = Thread.new do
|
65
|
+
begin
|
66
|
+
run
|
67
|
+
rescue Exception => error
|
68
|
+
puts error.message
|
69
|
+
puts error.backtrace
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def stop_async
|
77
|
+
if @runner_pid && System.process_alive?(@runner_pid, verbose: @verbose)
|
78
|
+
System.stop_process(@runner_pid)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def wait_remote_port_allocated
|
83
|
+
while @remote_port.nil?
|
84
|
+
#print '.'
|
85
|
+
sleep 0.1
|
86
|
+
end
|
87
|
+
|
88
|
+
@remote_port
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module WebService
|
2
|
+
extend self
|
3
|
+
|
4
|
+
#SERVICE_URL = "http://localhost:3000/dblinks"
|
5
|
+
SERVICE_URL = "http://dblink.r15.railsrumble.com/dblinks"
|
6
|
+
|
7
|
+
def register_link(database_url, verbose: false)
|
8
|
+
require 'uri'
|
9
|
+
require 'net/http'
|
10
|
+
|
11
|
+
uri = URI(SERVICE_URL)
|
12
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
13
|
+
request = Net::HTTP::Post.new(uri.path)
|
14
|
+
|
15
|
+
params = {
|
16
|
+
database_url: database_url
|
17
|
+
}
|
18
|
+
|
19
|
+
if verbose
|
20
|
+
puts "HTTP POST: #{SERVICE_URL} #{JSON.generate(params)}"
|
21
|
+
end
|
22
|
+
|
23
|
+
response = http.post(uri.path, URI.encode_www_form(params))
|
24
|
+
|
25
|
+
if verbose
|
26
|
+
puts response.class
|
27
|
+
puts response.body
|
28
|
+
end
|
29
|
+
|
30
|
+
response_json = JSON.parse(response.body)
|
31
|
+
response_json
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/pgbouncer
ADDED
Binary file
|
data/share.pem
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEpQIBAAKCAQEA0Obx6lLUY/tgzQ2RJex95iwzm19iyfWHvQ6MA9syRyaDjiyS
|
3
|
+
+0We5xtJeX+jdi+oI18CFgNMklWj3/PXD3/MDGKNTIlGXAfBLK3VjqyQlHch+jAL
|
4
|
+
PI6kZvMMcWVIlg2eCMxeIeoT1cmYbemoEWNltmzd4i+w7C9qUKPktORhlWuGp6Aj
|
5
|
+
kBmr8K7zFqvO5dQCeOowvmwvNvU106xc+YolIYvQc3cPqgWgqP3Q1ejg3JfzglZr
|
6
|
+
Z4kl/znCtdOoyGMNyq5V5XtJ6wq597BnXFO48mM4i8nwODoQM8itGEmIJJbgfzdw
|
7
|
+
5TwKLVODqNRfw1nECUe0rHiK/75rdKvrJiiB6QIDAQABAoIBAGUsNGWEEBEMIemt
|
8
|
+
dGqliqP7LFNHcxTUsRTDAtSrSEJlSLmGpPjE0xyifoc1ZflLQ2c+EEiQ+Hi2uVMx
|
9
|
+
SSrcffpRkJVMtqLDrq7w0cjzkmIVAIOm2QpPGxgxBgm1P95MYglo0P4j289if0xc
|
10
|
+
KWWiehRLzXpKFqmMnTTI628gXJJ8TQ575UsxOeJ0xTw255XhSYcxmkNOArfHstiI
|
11
|
+
Y6kRS2h0wE+KWwLeBn2UTsmpUpqxYRiVlq4yydhd5yWZpgV1RD7AtpOuJ8g8fDRI
|
12
|
+
9PCnXZaLFGawy2bmpsYHxod4pkSlKStJVwV0Z3cY8TPQ+iUy0rv9LtBantcsQBlc
|
13
|
+
sJjK8AECgYEA5/G+/Gw/HPgUH+BlKKJoYAtYXmjx9i18b97KuBfqm8JrHOrl3H2U
|
14
|
+
AT0VTOrF04/eVER5l822gL3bPhGOYh/8qmPytzxyx8xLWh4rJYAQxN8U5G2A8O+Y
|
15
|
+
Gr5NykIK9cRwr2cVOXVmuN5oRIxWdd+KpF2iUgKI/IUM9uLN+MF3q2kCgYEA5pFq
|
16
|
+
ZdUtqDPhlBEsY8PIz/Isif6wqjr5gp+mqtaMsRJ4vQxmqGeB53dOde7g4VOIU8PC
|
17
|
+
1Ee0MJHuBiVYUE+3lA1scBISpJbK8/V7gRTIRlQVO658upFeAFNkAfnWD4rN6G+0
|
18
|
+
gbbIRrr2JhHcKvHunLIhovMow4k37gYRnW9n0oECgYEA0w2EjcgpvcB2/3J+WfU+
|
19
|
+
KHbqWC4CTdGh82G08MLCdiz4rn2H35RdQqtEYJMYo6O72M3eHqXPNWKMxvPOrbcR
|
20
|
+
gDImP1m/z9IzlrLw69SWba3YIusJczNxIpSb0Dk8I9o190VDaN9NM1cdZZKVcfiQ
|
21
|
+
/XM+KoWoBK9E1roWVg4lSFECgYEAmz3Bo7r7OMtl0OoXNzz1kn1+W5Nuo899e+O+
|
22
|
+
tjwrEnZbcP5OiIjYwF5mJ6WlKFGz8qJWJdP5kNJQrzap1bk246p+Hp8sikE1eTkJ
|
23
|
+
5Ql3J1Mp0LtVI9EIt7NuHJ548JIGvb+oi8Xj5yuu3EceNJQcksilOS/7fFyBmfPg
|
24
|
+
HF6MSwECgYEAx/K84j3mE7sO+CRpjjSZyXw+ETKOhA2f1QogpHIn9/vhztAynj4g
|
25
|
+
dn6j3SntqoMK3DNK8LbPzBoKlPr+Y3IOSTgSpN1RGQTp61ikrIG3K+N7vav382PZ
|
26
|
+
tyv7MiEyLATbci1VExU/pn3Qvcv6s89fzR3ThWX3UlyC6c+uO0qIaFs=
|
27
|
+
-----END RSA PRIVATE KEY-----
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dblink
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pavel Evstigneev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-08 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: CLI utility to share access to your local PostgreSQL server
|
14
|
+
email:
|
15
|
+
- pavel.evst@gmail.com
|
16
|
+
executables:
|
17
|
+
- dblink
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- bin/dblink
|
22
|
+
- config.ini.erb
|
23
|
+
- dblink.gemspec
|
24
|
+
- lib/dblink/color.rb
|
25
|
+
- lib/dblink/network.rb
|
26
|
+
- lib/dblink/pg_bouncer_runner.rb
|
27
|
+
- lib/dblink/psql_runner.rb
|
28
|
+
- lib/dblink/system.rb
|
29
|
+
- lib/dblink/tonnel_runner.rb
|
30
|
+
- lib/dblink/web_service.rb
|
31
|
+
- pgbouncer
|
32
|
+
- share.pem
|
33
|
+
homepage: http://dblink.r15.railsrumble.com
|
34
|
+
licenses:
|
35
|
+
- MIT
|
36
|
+
metadata: {}
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 2.4.5.1
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: Veritrans ruby library
|
57
|
+
test_files: []
|