aptible-cli 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/aptible/cli/agent.rb +1 -0
- data/lib/aptible/cli/helpers/database.rb +123 -0
- data/lib/aptible/cli/subcommands/db.rb +11 -117
- data/lib/aptible/cli/version.rb +1 -1
- data/spec/aptible/cli/subcommands/db_spec.rb +2 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82497046ef055642ef5009c32253a6ffdf7dfd5b
|
4
|
+
data.tar.gz: 791ede35111988aaf40ce86ff1ff7ebcf476a7a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 090af05a44a38f86a746086df06a4c2d2531076752d8b819bcf9cfd9ef19f4bad03e4aa934ef93148d72e721b2f2cabade69184a817828300eaea39cc0e048be
|
7
|
+
data.tar.gz: 4ce62bac547f46a64d00b44d52f051205aaeb4cd40d5637d285a24bce278e1802083f00ce22343cc281a947fc7e30a225a74d32e6e1d1ff500cf1b685baf70dd
|
data/lib/aptible/cli/agent.rb
CHANGED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'aptible/api'
|
2
|
+
|
3
|
+
module Aptible
|
4
|
+
module CLI
|
5
|
+
module Helpers
|
6
|
+
module Database
|
7
|
+
include Helpers::Token
|
8
|
+
include Helpers::Environment
|
9
|
+
|
10
|
+
def ensure_database(options = {})
|
11
|
+
db_handle = options[:db]
|
12
|
+
environment_handle = options[:environment]
|
13
|
+
|
14
|
+
fail Thor::Error, 'Database handle not specified' unless db_handle
|
15
|
+
|
16
|
+
environment = environment_from_handle(environment_handle)
|
17
|
+
if environment_handle && !environment
|
18
|
+
fail Thor::Error, "Could not find environment #{environment_handle}"
|
19
|
+
end
|
20
|
+
databases = databases_from_handle(db_handle, environment)
|
21
|
+
case databases.count
|
22
|
+
when 1
|
23
|
+
return databases.first
|
24
|
+
when 0
|
25
|
+
fail Thor::Error, "Could not find database #{db_handle}"
|
26
|
+
else
|
27
|
+
fail Thor::Error,
|
28
|
+
'Multiple databases exist, please specify environment'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def databases_from_handle(handle, environment)
|
33
|
+
if environment
|
34
|
+
databases = environment.databases
|
35
|
+
else
|
36
|
+
databases = Aptible::Api::Database.all(token: fetch_token)
|
37
|
+
end
|
38
|
+
databases.select { |a| a.handle == handle }
|
39
|
+
end
|
40
|
+
|
41
|
+
def present_environment_databases(environment)
|
42
|
+
say "=== #{environment.handle}"
|
43
|
+
environment.databases.each { |db| say db.handle }
|
44
|
+
say ''
|
45
|
+
end
|
46
|
+
|
47
|
+
def establish_connection(database, local_port)
|
48
|
+
ENV['ACCESS_TOKEN'] = fetch_token
|
49
|
+
ENV['APTIBLE_DATABASE'] = database.handle
|
50
|
+
|
51
|
+
remote_port = claim_remote_port(database)
|
52
|
+
ENV['TUNNEL_PORT'] = remote_port
|
53
|
+
|
54
|
+
tunnel_args = "-L #{local_port}:localhost:#{remote_port}"
|
55
|
+
command = "ssh #{tunnel_args} #{common_ssh_args(database)}"
|
56
|
+
Kernel.exec(command)
|
57
|
+
end
|
58
|
+
|
59
|
+
def clone_database(source, dest_handle)
|
60
|
+
op = source.create_operation(type: 'clone', handle: dest_handle)
|
61
|
+
poll_for_success(op)
|
62
|
+
|
63
|
+
databases_from_handle(dest_handle, source.account).first
|
64
|
+
end
|
65
|
+
|
66
|
+
def dump_database(database)
|
67
|
+
execute_local_tunnel(database) do |url|
|
68
|
+
filename = "#{database.handle}.dump"
|
69
|
+
say "Dumping to #{filename}"
|
70
|
+
`pg_dump #{url} > #{filename}`
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Creates a local tunnel and yields the url to it
|
75
|
+
def execute_local_tunnel(database)
|
76
|
+
local_port = random_local_port
|
77
|
+
pid = fork { establish_connection(database, local_port) }
|
78
|
+
|
79
|
+
# TODO: Better test for connection readiness
|
80
|
+
sleep 10
|
81
|
+
|
82
|
+
auth = "aptible:#{database.passphrase}"
|
83
|
+
host = "localhost:#{local_port}"
|
84
|
+
yield "postgresql://#{auth}@#{host}/db"
|
85
|
+
ensure
|
86
|
+
Process.kill('HUP', pid) if pid
|
87
|
+
end
|
88
|
+
|
89
|
+
def random_local_port
|
90
|
+
# Allocate a dummy server to discover an available port
|
91
|
+
dummy = TCPServer.new('127.0.0.1', 0)
|
92
|
+
port = dummy.addr[1]
|
93
|
+
dummy.close
|
94
|
+
port
|
95
|
+
end
|
96
|
+
|
97
|
+
def local_url(database, local_port)
|
98
|
+
remote_url = database.connection_url
|
99
|
+
uri = URI.parse(remote_url)
|
100
|
+
|
101
|
+
"#{uri.scheme}://#{uri.user}:#{uri.password}@" \
|
102
|
+
"127.0.0.1:#{local_port}#{uri.path}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def claim_remote_port(database)
|
106
|
+
ENV['ACCESS_TOKEN'] = fetch_token
|
107
|
+
|
108
|
+
`ssh #{common_ssh_args(database)} 2>/dev/null`.chomp
|
109
|
+
end
|
110
|
+
|
111
|
+
def common_ssh_args(database)
|
112
|
+
host = database.account.bastion_host
|
113
|
+
port = database.account.bastion_port
|
114
|
+
|
115
|
+
opts = " -o 'SendEnv=*' -o StrictHostKeyChecking=no " \
|
116
|
+
'-o UserKnownHostsFile=/dev/null'
|
117
|
+
connection_args = "-p #{port} root@#{host}"
|
118
|
+
"#{opts} #{connection_args}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -7,6 +7,7 @@ module Aptible
|
|
7
7
|
def self.included(thor)
|
8
8
|
thor.class_eval do
|
9
9
|
include Helpers::Operation
|
10
|
+
include Helpers::Database
|
10
11
|
include Helpers::Token
|
11
12
|
include Term::ANSIColor
|
12
13
|
|
@@ -40,24 +41,24 @@ module Aptible
|
|
40
41
|
desc 'db:clone SOURCE DEST', 'Clone a database to create a new one'
|
41
42
|
option :environment
|
42
43
|
define_method 'db:clone' do |source_handle, dest_handle|
|
43
|
-
|
44
|
-
dest = clone_database(
|
44
|
+
source = ensure_database(options.merge(db: source_handle))
|
45
|
+
dest = clone_database(source, dest_handle)
|
45
46
|
say dest.connection_url
|
46
47
|
end
|
47
48
|
|
48
49
|
desc 'db:dump HANDLE', 'Dump a remote database to file'
|
49
50
|
option :environment
|
50
51
|
define_method 'db:dump' do |handle|
|
51
|
-
|
52
|
-
dump_database(
|
52
|
+
database = ensure_database(options.merge(db: handle))
|
53
|
+
dump_database(database)
|
53
54
|
end
|
54
55
|
|
55
56
|
desc 'db:execute HANDLE SQL_FILE', 'Executes sql against a database'
|
56
57
|
option :environment
|
57
58
|
define_method 'db:execute' do |handle, sql_path|
|
58
|
-
|
59
|
-
execute_local_tunnel(
|
60
|
-
say "Executing #{sql_path} against #{handle}"
|
59
|
+
database = ensure_database(options.merge(db: handle))
|
60
|
+
execute_local_tunnel(database) do |url|
|
61
|
+
say "Executing #{sql_path} against #{database.handle}"
|
61
62
|
`psql #{url} < #{sql_path}`
|
62
63
|
end
|
63
64
|
end
|
@@ -66,8 +67,7 @@ module Aptible
|
|
66
67
|
option :environment
|
67
68
|
option :port, type: :numeric
|
68
69
|
define_method 'db:tunnel' do |handle|
|
69
|
-
|
70
|
-
database = database_from_handle(handle, environment)
|
70
|
+
database = ensure_database(options.merge(db: handle))
|
71
71
|
local_port = options[:port] || random_local_port
|
72
72
|
|
73
73
|
say 'Creating tunnel...', :green
|
@@ -87,117 +87,11 @@ module Aptible
|
|
87
87
|
desc 'db:deprovision HANDLE', 'Deprovision a database'
|
88
88
|
option :environment
|
89
89
|
define_method 'db:deprovision' do |handle|
|
90
|
-
|
91
|
-
|
92
|
-
say "Deprovisioning #{handle}..."
|
90
|
+
database = ensure_database(options.merge(db: handle))
|
91
|
+
say "Deprovisioning #{database.handle}..."
|
93
92
|
database.update!(status: 'deprovisioned')
|
94
93
|
database.create_operation!(type: 'deprovision')
|
95
94
|
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
def present_environment_databases(environment)
|
100
|
-
say "=== #{environment.handle}"
|
101
|
-
environment.databases.each { |db| say db.handle }
|
102
|
-
say ''
|
103
|
-
end
|
104
|
-
|
105
|
-
def establish_connection(database, local_port)
|
106
|
-
ENV['ACCESS_TOKEN'] = fetch_token
|
107
|
-
ENV['APTIBLE_DATABASE'] = database.handle
|
108
|
-
|
109
|
-
remote_port = claim_remote_port(database)
|
110
|
-
ENV['TUNNEL_PORT'] = remote_port
|
111
|
-
|
112
|
-
tunnel_args = "-L #{local_port}:localhost:#{remote_port}"
|
113
|
-
command = "ssh #{tunnel_args} #{common_ssh_args(database)}"
|
114
|
-
Kernel.exec(command)
|
115
|
-
end
|
116
|
-
|
117
|
-
def database_from_handle(handle,
|
118
|
-
environment,
|
119
|
-
options = { postgres_only: false })
|
120
|
-
all = environment.databases
|
121
|
-
database = all.find { |a| a.handle == handle }
|
122
|
-
|
123
|
-
unless database
|
124
|
-
fail Thor::Error, "Could not find database #{handle}"
|
125
|
-
end
|
126
|
-
|
127
|
-
if options[:postgres_only] && database.type != 'postgresql'
|
128
|
-
fail Thor::Error, 'This command only works for PostgreSQL'
|
129
|
-
end
|
130
|
-
|
131
|
-
database
|
132
|
-
end
|
133
|
-
|
134
|
-
def clone_database(source_handle, dest_handle, environment)
|
135
|
-
source = database_from_handle(source_handle, environment)
|
136
|
-
op = source.create_operation(type: 'clone', handle: dest_handle)
|
137
|
-
poll_for_success(op)
|
138
|
-
|
139
|
-
database_from_handle(dest_handle)
|
140
|
-
end
|
141
|
-
|
142
|
-
def dump_database(handle, environment)
|
143
|
-
execute_local_tunnel(handle, environment) do |url|
|
144
|
-
filename = "#{handle}.dump"
|
145
|
-
say "Dumping to #{filename}"
|
146
|
-
`pg_dump #{url} > #{filename}`
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
# Creates a local tunnel and yields the url to it
|
151
|
-
|
152
|
-
def execute_local_tunnel(handle, environment)
|
153
|
-
database = database_from_handle(handle,
|
154
|
-
environment,
|
155
|
-
postgres_only: true)
|
156
|
-
|
157
|
-
local_port = random_local_port
|
158
|
-
pid = fork { establish_connection(database, local_port) }
|
159
|
-
|
160
|
-
# TODO: Better test for connection readiness
|
161
|
-
sleep 10
|
162
|
-
|
163
|
-
auth = "aptible:#{database.passphrase}"
|
164
|
-
host = "localhost:#{local_port}"
|
165
|
-
yield "postgresql://#{auth}@#{host}/db"
|
166
|
-
ensure
|
167
|
-
Process.kill('HUP', pid) if pid
|
168
|
-
end
|
169
|
-
|
170
|
-
def random_local_port
|
171
|
-
# Allocate a dummy server to discover an available port
|
172
|
-
dummy = TCPServer.new('127.0.0.1', 0)
|
173
|
-
port = dummy.addr[1]
|
174
|
-
dummy.close
|
175
|
-
port
|
176
|
-
end
|
177
|
-
|
178
|
-
def local_url(database, local_port)
|
179
|
-
remote_url = database.connection_url
|
180
|
-
uri = URI.parse(remote_url)
|
181
|
-
|
182
|
-
"#{uri.scheme}://#{uri.user}:#{uri.password}@" \
|
183
|
-
"127.0.0.1:#{local_port}#{uri.path}"
|
184
|
-
end
|
185
|
-
|
186
|
-
def claim_remote_port(database)
|
187
|
-
ENV['ACCESS_TOKEN'] = fetch_token
|
188
|
-
|
189
|
-
`ssh #{common_ssh_args(database)} 2>/dev/null`.chomp
|
190
|
-
end
|
191
|
-
|
192
|
-
def common_ssh_args(database)
|
193
|
-
host = database.account.bastion_host
|
194
|
-
port = database.account.bastion_port
|
195
|
-
|
196
|
-
opts = " -o 'SendEnv=*' -o StrictHostKeyChecking=no " \
|
197
|
-
'-o UserKnownHostsFile=/dev/null'
|
198
|
-
connection_args = "-p #{port} root@#{host}"
|
199
|
-
"#{opts} #{connection_args}"
|
200
|
-
end
|
201
95
|
end
|
202
96
|
end
|
203
97
|
end
|
data/lib/aptible/cli/version.rb
CHANGED
@@ -30,16 +30,14 @@ describe Aptible::CLI::Agent do
|
|
30
30
|
|
31
31
|
describe '#db:tunnel' do
|
32
32
|
it 'should fail if database is non-existent' do
|
33
|
-
allow(Aptible::Api::
|
34
|
-
allow(account).to receive(:databases) { [] }
|
33
|
+
allow(Aptible::Api::Database).to receive(:all) { [] }
|
35
34
|
expect do
|
36
35
|
subject.send('db:tunnel', 'foobar')
|
37
36
|
end.to raise_error('Could not find database foobar')
|
38
37
|
end
|
39
38
|
|
40
39
|
it 'should print a message about how to connect' do
|
41
|
-
allow(Aptible::Api::
|
42
|
-
allow(account).to receive(:databases) { [database] }
|
40
|
+
allow(Aptible::Api::Database).to receive(:all) { [database] }
|
43
41
|
local_url = 'postgresql://aptible:password@127.0.0.1:4242/db'
|
44
42
|
expect(subject).to receive(:say).with('Creating tunnel...', :green)
|
45
43
|
expect(subject).to receive(:say).with("Connect at #{local_url}", :green)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aptible-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Macreery
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aptible-api
|
@@ -171,6 +171,7 @@ files:
|
|
171
171
|
- lib/aptible/cli.rb
|
172
172
|
- lib/aptible/cli/agent.rb
|
173
173
|
- lib/aptible/cli/helpers/app.rb
|
174
|
+
- lib/aptible/cli/helpers/database.rb
|
174
175
|
- lib/aptible/cli/helpers/env.rb
|
175
176
|
- lib/aptible/cli/helpers/environment.rb
|
176
177
|
- lib/aptible/cli/helpers/operation.rb
|