aptible-cli 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|