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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a4dc9b3d5de431e99e8aef2e76fdf0a352c03f7
4
- data.tar.gz: eaaba80a605eff86cdddb3bec822a57ae928d871
3
+ metadata.gz: 82497046ef055642ef5009c32253a6ffdf7dfd5b
4
+ data.tar.gz: 791ede35111988aaf40ce86ff1ff7ebcf476a7a0
5
5
  SHA512:
6
- metadata.gz: d86fdfec7a75ba6aa0234551c0b7c326ee6b408175ea41d7fb8e44c96f0f2db2ecd9346029f4ac36295f1e68d9690f9928a74efdba79c6f8e63d88a6905d5253
7
- data.tar.gz: 7a115c06a8479359d96dade21f5467cdba4b50af226d828062b0b9a88722d46ef1772fc1e22c56f991e1d6e874bce0e0fb9105997d9a944d876c3fdd3df29852
6
+ metadata.gz: 090af05a44a38f86a746086df06a4c2d2531076752d8b819bcf9cfd9ef19f4bad03e4aa934ef93148d72e721b2f2cabade69184a817828300eaea39cc0e048be
7
+ data.tar.gz: 4ce62bac547f46a64d00b44d52f051205aaeb4cd40d5637d285a24bce278e1802083f00ce22343cc281a947fc7e30a225a74d32e6e1d1ff500cf1b685baf70dd
@@ -6,6 +6,7 @@ require_relative 'helpers/token'
6
6
  require_relative 'helpers/operation'
7
7
  require_relative 'helpers/environment'
8
8
  require_relative 'helpers/app'
9
+ require_relative 'helpers/database'
9
10
  require_relative 'helpers/env'
10
11
 
11
12
  require_relative 'subcommands/apps'
@@ -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
- environment = ensure_environment(options)
44
- dest = clone_database(source_handle, dest_handle, environment)
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
- environment = ensure_environment(options)
52
- dump_database(handle, environment)
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
- environment = ensure_environment(options)
59
- execute_local_tunnel(handle, environment) do |url|
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
- environment = ensure_environment(options)
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
- environment = ensure_environment(options)
91
- database = database_from_handle(handle, environment)
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
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.6.1'
3
+ VERSION = '0.6.2'
4
4
  end
5
5
  end
@@ -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::Account).to receive(:all) { [account] }
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::Account).to receive(:all) { [account] }
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.1
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-24 00:00:00.000000000 Z
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