aptible-cli 0.6.8 → 0.6.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/aptible-cli.gemspec +1 -0
- data/lib/aptible/cli/agent.rb +1 -0
- data/lib/aptible/cli/helpers/database.rb +28 -47
- data/lib/aptible/cli/helpers/tunnel.rb +76 -0
- data/lib/aptible/cli/subcommands/db.rb +30 -15
- data/lib/aptible/cli/subcommands/logs.rb +1 -1
- data/lib/aptible/cli/subcommands/ps.rb +1 -1
- data/lib/aptible/cli/subcommands/ssh.rb +1 -1
- data/lib/aptible/cli/version.rb +1 -1
- data/spec/aptible/cli/helpers/tunnel_spec.rb +71 -0
- data/spec/aptible/cli/subcommands/db_spec.rb +15 -4
- data/spec/mock/ssh_mock.rb +18 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c58858b887b9868e017e93d7248f4e7d1f898dc2
|
4
|
+
data.tar.gz: 7b2d52cc1b316205abb7d35c13722a32dccda29a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c326bb7a9cc09e2689804e43b0fa00643eb759dbe838f572b0dc44df32caba6f6b76f1a14375e4aa6c1d5cac83457b8a37274c72f14ed843b6afcaa6acb2287
|
7
|
+
data.tar.gz: 07bd87aa8c414600d845ce133c868ad1a0df02e9c6518f6202717eabd7f12d8d7b95df42cc5a1f4177bb4f315e6950fc4fa6e13b64059ce2f12f8b0b0a6bf2c3
|
data/aptible-cli.gemspec
CHANGED
data/lib/aptible/cli/agent.rb
CHANGED
@@ -44,18 +44,6 @@ module Aptible
|
|
44
44
|
say ''
|
45
45
|
end
|
46
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
47
|
def clone_database(source, dest_handle)
|
60
48
|
op = source.create_operation(type: 'clone', handle: dest_handle)
|
61
49
|
poll_for_success(op)
|
@@ -63,35 +51,33 @@ module Aptible
|
|
63
51
|
databases_from_handle(dest_handle, source.account).first
|
64
52
|
end
|
65
53
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
54
|
+
# Creates a local tunnel and yields the helper
|
55
|
+
|
56
|
+
def with_local_tunnel(database, port = 0)
|
57
|
+
env = {
|
58
|
+
'ACCESS_TOKEN' => fetch_token,
|
59
|
+
'APTIBLE_DATABASE' => database.href
|
60
|
+
}
|
61
|
+
command = ['ssh', '-q'] + ssh_args(database)
|
62
|
+
Helpers::Tunnel.new(env, command).tap do |tunnel_helper|
|
63
|
+
tunnel_helper.start(port)
|
64
|
+
yield tunnel_helper
|
65
|
+
tunnel_helper.stop
|
71
66
|
end
|
72
67
|
end
|
73
68
|
|
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
|
69
|
+
# Creates a local PG tunnel and yields the url to it
|
81
70
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
Process.kill('HUP', pid) if pid
|
87
|
-
end
|
71
|
+
def with_postgres_tunnel(database)
|
72
|
+
if database.type != 'postgresql'
|
73
|
+
fail Thor::Error, 'This command only works for PostgreSQL'
|
74
|
+
end
|
88
75
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
port
|
76
|
+
with_local_tunnel(database) do |tunnel_helper|
|
77
|
+
auth = "aptible:#{database.passphrase}"
|
78
|
+
host = "localhost:#{tunnel_helper.port}"
|
79
|
+
yield "postgresql://#{auth}@#{host}/db"
|
80
|
+
end
|
95
81
|
end
|
96
82
|
|
97
83
|
def local_url(database, local_port)
|
@@ -102,20 +88,15 @@ module Aptible
|
|
102
88
|
"127.0.0.1:#{local_port}#{uri.path}"
|
103
89
|
end
|
104
90
|
|
105
|
-
def
|
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)
|
91
|
+
def ssh_args(database)
|
112
92
|
host = database.account.bastion_host
|
113
93
|
port = database.account.bastion_port
|
114
94
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
95
|
+
['-o', 'SendEnv=APTIBLE_DATABASE',
|
96
|
+
'-o', 'SendEnv=ACCESS_TOKEN',
|
97
|
+
'-o', 'StrictHostKeyChecking=no',
|
98
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
99
|
+
'-p', port.to_s, "root@#{host}"]
|
119
100
|
end
|
120
101
|
end
|
121
102
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Aptible
|
5
|
+
module CLI
|
6
|
+
module Helpers
|
7
|
+
class Tunnel
|
8
|
+
def initialize(env, cmd)
|
9
|
+
@env = env
|
10
|
+
@cmd = cmd
|
11
|
+
end
|
12
|
+
|
13
|
+
def start(desired_port = 0, err_fd = $stderr)
|
14
|
+
@local_port = desired_port
|
15
|
+
@local_port = random_local_port if @local_port == 0
|
16
|
+
|
17
|
+
# First, grab a remote port
|
18
|
+
out, err, status = Open3.capture3(@env, *@cmd)
|
19
|
+
fail "Failed to request remote port: #{err}" unless status.success?
|
20
|
+
remote_port = out.chomp
|
21
|
+
|
22
|
+
# Then, spin up a SSH session using that port and port forwarding
|
23
|
+
tunnel_env = @env.merge(
|
24
|
+
'TUNNEL_PORT' => remote_port, # Request a specific port
|
25
|
+
'TUNNEL_SIGNAL_OPEN' => '1' # Request signal when tunnel is up
|
26
|
+
)
|
27
|
+
|
28
|
+
tunnel_cmd = @cmd + [
|
29
|
+
'-L', "#{@local_port}:localhost:#{remote_port}",
|
30
|
+
'-o', 'SendEnv=TUNNEL_PORT',
|
31
|
+
'-o', 'SendEnv=TUNNEL_SIGNAL_OPEN'
|
32
|
+
]
|
33
|
+
|
34
|
+
r_pipe, w_pipe = IO.pipe
|
35
|
+
@pid = Process.spawn(tunnel_env, *tunnel_cmd, in: :close,
|
36
|
+
out: w_pipe,
|
37
|
+
err: err_fd)
|
38
|
+
|
39
|
+
# Wait for the tunnel to come up before returning. The other end
|
40
|
+
# will send a message on stdout to indicate that the tunnel is ready.
|
41
|
+
w_pipe.close
|
42
|
+
begin
|
43
|
+
r_pipe.readline
|
44
|
+
rescue EOFError
|
45
|
+
raise 'Server closed the tunnel'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def stop
|
50
|
+
fail 'You must call #start before calling #stop' if @pid.nil?
|
51
|
+
Process.kill('HUP', @pid)
|
52
|
+
wait
|
53
|
+
end
|
54
|
+
|
55
|
+
def wait
|
56
|
+
Process.wait @pid
|
57
|
+
end
|
58
|
+
|
59
|
+
def port
|
60
|
+
fail 'You must call #start before calling #port!' if @local_port.nil?
|
61
|
+
@local_port
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def random_local_port
|
67
|
+
# Allocate a dummy server to discover an available port
|
68
|
+
dummy = TCPServer.new('127.0.0.1', 0)
|
69
|
+
port = dummy.addr[1]
|
70
|
+
dummy.close
|
71
|
+
port
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'term/ansicolor'
|
2
|
+
require 'uri'
|
2
3
|
|
3
4
|
module Aptible
|
4
5
|
module CLI
|
@@ -50,15 +51,19 @@ module Aptible
|
|
50
51
|
option :environment
|
51
52
|
define_method 'db:dump' do |handle|
|
52
53
|
database = ensure_database(options.merge(db: handle))
|
53
|
-
|
54
|
+
with_postgres_tunnel(database) do |url|
|
55
|
+
filename = "#{handle}.dump"
|
56
|
+
say "Dumping to #{filename}"
|
57
|
+
`pg_dump #{url} > #{filename}`
|
58
|
+
end
|
54
59
|
end
|
55
60
|
|
56
61
|
desc 'db:execute HANDLE SQL_FILE', 'Executes sql against a database'
|
57
62
|
option :environment
|
58
63
|
define_method 'db:execute' do |handle, sql_path|
|
59
64
|
database = ensure_database(options.merge(db: handle))
|
60
|
-
|
61
|
-
say "Executing #{sql_path} against #{
|
65
|
+
with_postgres_tunnel(database) do |url|
|
66
|
+
say "Executing #{sql_path} against #{handle}"
|
62
67
|
`psql #{url} < #{sql_path}`
|
63
68
|
end
|
64
69
|
end
|
@@ -67,21 +72,31 @@ module Aptible
|
|
67
72
|
option :environment
|
68
73
|
option :port, type: :numeric
|
69
74
|
define_method 'db:tunnel' do |handle|
|
75
|
+
desired_port = Integer(options[:port] || 0)
|
70
76
|
database = ensure_database(options.merge(db: handle))
|
71
|
-
local_port = options[:port] || random_local_port
|
72
|
-
|
73
77
|
say 'Creating tunnel...', :green
|
74
|
-
say "Connect at #{local_url(database, local_port)}", :green
|
75
78
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
79
|
+
with_local_tunnel(database, desired_port) do |tunnel_helper|
|
80
|
+
port = tunnel_helper.port
|
81
|
+
say "Connect at #{local_url(database, port)}", :green
|
82
|
+
|
83
|
+
uri = URI(local_url(database, port))
|
84
|
+
db = uri.path.gsub(%r{^/}, '')
|
85
|
+
say 'Or, use the following arguments:', :green
|
86
|
+
say("* Host: #{uri.host}", :green)
|
87
|
+
say("* Port: #{uri.port}", :green)
|
88
|
+
say("* Username: #{uri.user}", :green) unless uri.user.empty?
|
89
|
+
say("* Password: #{uri.password}", :green)
|
90
|
+
say("* Database: #{db}", :green) unless db.empty?
|
91
|
+
|
92
|
+
say 'Connected. Ctrl-C to close connection.'
|
93
|
+
|
94
|
+
begin
|
95
|
+
tunnel_helper.wait
|
96
|
+
rescue Interrupt
|
97
|
+
say 'Closing tunnel'
|
98
|
+
end
|
99
|
+
end
|
85
100
|
end
|
86
101
|
|
87
102
|
desc 'db:deprovision HANDLE', 'Deprovision a database'
|
@@ -22,7 +22,7 @@ module Aptible
|
|
22
22
|
port = app.account.dumptruck_port
|
23
23
|
|
24
24
|
ENV['ACCESS_TOKEN'] = fetch_token
|
25
|
-
ENV['APTIBLE_APP'] = app.
|
25
|
+
ENV['APTIBLE_APP'] = app.href
|
26
26
|
ENV['APTIBLE_CLI_COMMAND'] = 'logs'
|
27
27
|
|
28
28
|
opts = " -o 'SendEnv=*' -o StrictHostKeyChecking=no " \
|
@@ -20,7 +20,7 @@ module Aptible
|
|
20
20
|
port = app.account.dumptruck_port
|
21
21
|
|
22
22
|
set_env('ACCESS_TOKEN', fetch_token)
|
23
|
-
set_env('APTIBLE_APP', app.
|
23
|
+
set_env('APTIBLE_APP', app.href)
|
24
24
|
set_env('APTIBLE_CLI_COMMAND', 'ps')
|
25
25
|
|
26
26
|
opts = " -o 'SendEnv=*' -o StrictHostKeyChecking=no " \
|
@@ -24,7 +24,7 @@ module Aptible
|
|
24
24
|
|
25
25
|
ENV['ACCESS_TOKEN'] = fetch_token
|
26
26
|
ENV['APTIBLE_COMMAND'] = command_from_args(*args)
|
27
|
-
ENV['APTIBLE_APP'] = app.
|
27
|
+
ENV['APTIBLE_APP'] = app.href
|
28
28
|
|
29
29
|
opts = options[:force_tty] ? '-t -t' : ''
|
30
30
|
opts << " -o 'SendEnv=*' -o StrictHostKeyChecking=no " \
|
data/lib/aptible/cli/version.rb
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'climate_control'
|
3
|
+
|
4
|
+
describe Aptible::CLI::Helpers::Tunnel do
|
5
|
+
around do |example|
|
6
|
+
mocks_path = File.expand_path('../../../../mock', __FILE__)
|
7
|
+
path = "#{mocks_path}:#{ENV['PATH']}"
|
8
|
+
ClimateControl.modify PATH: path do
|
9
|
+
example.run
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'reuses the port it was given' do
|
14
|
+
helper = described_class.new({}, ['ssh_mock.rb'])
|
15
|
+
|
16
|
+
r, w = IO.pipe
|
17
|
+
helper.start(0, w)
|
18
|
+
helper.stop
|
19
|
+
|
20
|
+
expect(r.readline.chomp).to eq('6')
|
21
|
+
expect(r.readline.chomp).to eq('-L')
|
22
|
+
expect(r.readline.chomp).to match(/\d+:localhost:1234$/)
|
23
|
+
expect(r.readline.chomp).to eq('-o')
|
24
|
+
expect(r.readline.chomp).to eq('SendEnv=TUNNEL_PORT')
|
25
|
+
expect(r.readline.chomp).to eq('-o')
|
26
|
+
expect(r.readline.chomp).to eq('SendEnv=TUNNEL_SIGNAL_OPEN')
|
27
|
+
|
28
|
+
r.close
|
29
|
+
w.close
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'accepts a desired port' do
|
33
|
+
helper = described_class.new({}, ['ssh_mock.rb'])
|
34
|
+
r, w = IO.pipe
|
35
|
+
helper.start(5678, w)
|
36
|
+
helper.stop
|
37
|
+
|
38
|
+
expect(r.readline.chomp).to eq('6')
|
39
|
+
expect(r.readline.chomp).to eq('-L')
|
40
|
+
expect(r.readline.chomp).to eq('5678:localhost:1234')
|
41
|
+
expect(r.readline.chomp).to eq('-o')
|
42
|
+
expect(r.readline.chomp).to eq('SendEnv=TUNNEL_PORT')
|
43
|
+
expect(r.readline.chomp).to eq('-o')
|
44
|
+
expect(r.readline.chomp).to eq('SendEnv=TUNNEL_SIGNAL_OPEN')
|
45
|
+
|
46
|
+
r.close
|
47
|
+
w.close
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'captures and displays port discovery errors' do
|
51
|
+
helper = described_class.new({ 'FAIL_PORT' => '1' }, ['ssh_mock.rb'])
|
52
|
+
expect { helper.start }.to raise_error(/Something went wrong/)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'captures and displays tunnel errors' do
|
56
|
+
helper = described_class.new({ 'FAIL_TUNNEL' => '1' }, ['ssh_mock.rb'])
|
57
|
+
expect do
|
58
|
+
helper.start(0, File.open(File::NULL, 'w'))
|
59
|
+
end.to raise_error(/Server closed the tunnel/)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should fail if #port is called before #start' do
|
63
|
+
socat = described_class.new({}, [])
|
64
|
+
expect { socat.port }.to raise_error(/You must call #start/)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should fail if #stop is called before #start' do
|
68
|
+
socat = described_class.new({}, [])
|
69
|
+
expect { socat.stop }.to raise_error(/You must call #start/)
|
70
|
+
end
|
71
|
+
end
|
@@ -7,12 +7,13 @@ end
|
|
7
7
|
class Account < OpenStruct
|
8
8
|
end
|
9
9
|
|
10
|
+
class SocatHelperMock < OpenStruct
|
11
|
+
end
|
12
|
+
|
10
13
|
describe Aptible::CLI::Agent do
|
11
14
|
before { subject.stub(:ask) }
|
12
15
|
before { subject.stub(:save_token) }
|
13
16
|
before { subject.stub(:fetch_token) { double 'token' } }
|
14
|
-
before { subject.stub(:random_local_port) { 4242 } }
|
15
|
-
before { subject.stub(:establish_connection) }
|
16
17
|
|
17
18
|
let(:account) do
|
18
19
|
Account.new(bastion_host: 'localhost',
|
@@ -24,7 +25,14 @@ describe Aptible::CLI::Agent do
|
|
24
25
|
type: 'postgresql',
|
25
26
|
handle: 'foobar',
|
26
27
|
passphrase: 'password',
|
27
|
-
connection_url: 'postgresql://aptible:password@10.252.1.125:49158/db'
|
28
|
+
connection_url: 'postgresql://aptible:password@10.252.1.125:49158/db',
|
29
|
+
account: account
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:socat_helper) do
|
34
|
+
SocatHelperMock.new(
|
35
|
+
port: 4242
|
28
36
|
)
|
29
37
|
end
|
30
38
|
|
@@ -39,11 +47,14 @@ describe Aptible::CLI::Agent do
|
|
39
47
|
it 'should print a message about how to connect' do
|
40
48
|
allow(Aptible::Api::Database).to receive(:all) { [database] }
|
41
49
|
local_url = 'postgresql://aptible:password@127.0.0.1:4242/db'
|
50
|
+
|
51
|
+
expect(subject).to receive(:with_local_tunnel).with(database, 0)
|
52
|
+
.and_yield(socat_helper)
|
42
53
|
expect(subject).to receive(:say).with('Creating tunnel...', :green)
|
43
54
|
expect(subject).to receive(:say).with("Connect at #{local_url}", :green)
|
44
55
|
|
45
56
|
# db:tunnel should also explain each component of the URL:
|
46
|
-
expect(subject).to receive(:say).exactly(
|
57
|
+
expect(subject).to receive(:say).exactly(7).times
|
47
58
|
subject.send('db:tunnel', 'foobar')
|
48
59
|
end
|
49
60
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Emulate server behavior
|
4
|
+
|
5
|
+
if ENV['TUNNEL_PORT']
|
6
|
+
fail 'Something went wrong!' if ENV['FAIL_TUNNEL']
|
7
|
+
puts 'TUNNEL READY'
|
8
|
+
else
|
9
|
+
fail 'Something went wrong!' if ENV['FAIL_PORT']
|
10
|
+
puts 1234
|
11
|
+
end
|
12
|
+
|
13
|
+
# Log to stderr so we can collect in test
|
14
|
+
|
15
|
+
$stderr.puts ARGV.size
|
16
|
+
ARGV.each do |a|
|
17
|
+
$stderr.puts a
|
18
|
+
end
|
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.9
|
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-05-
|
11
|
+
date: 2016-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aptible-api
|
@@ -150,6 +150,20 @@ dependencies:
|
|
150
150
|
- - ">="
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: climate_control
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
153
167
|
description: Aptible CLI
|
154
168
|
email:
|
155
169
|
- frank@macreery.com
|
@@ -176,6 +190,7 @@ files:
|
|
176
190
|
- lib/aptible/cli/helpers/environment.rb
|
177
191
|
- lib/aptible/cli/helpers/operation.rb
|
178
192
|
- lib/aptible/cli/helpers/token.rb
|
193
|
+
- lib/aptible/cli/helpers/tunnel.rb
|
179
194
|
- lib/aptible/cli/subcommands/apps.rb
|
180
195
|
- lib/aptible/cli/subcommands/config.rb
|
181
196
|
- lib/aptible/cli/subcommands/db.rb
|
@@ -188,11 +203,13 @@ files:
|
|
188
203
|
- lib/aptible/cli/version.rb
|
189
204
|
- spec/aptible/cli/agent_spec.rb
|
190
205
|
- spec/aptible/cli/helpers/handle_from_git_remote.rb
|
206
|
+
- spec/aptible/cli/helpers/tunnel_spec.rb
|
191
207
|
- spec/aptible/cli/subcommands/apps_spec.rb
|
192
208
|
- spec/aptible/cli/subcommands/db_spec.rb
|
193
209
|
- spec/aptible/cli/subcommands/domains_spec.rb
|
194
210
|
- spec/aptible/cli/subcommands/logs_spec.rb
|
195
211
|
- spec/aptible/cli/subcommands/ps_spec.rb
|
212
|
+
- spec/mock/ssh_mock.rb
|
196
213
|
- spec/spec_helper.rb
|
197
214
|
homepage: https://github.com/aptible/aptible-cli
|
198
215
|
licenses:
|
@@ -221,9 +238,11 @@ summary: Command-line interface for Aptible services
|
|
221
238
|
test_files:
|
222
239
|
- spec/aptible/cli/agent_spec.rb
|
223
240
|
- spec/aptible/cli/helpers/handle_from_git_remote.rb
|
241
|
+
- spec/aptible/cli/helpers/tunnel_spec.rb
|
224
242
|
- spec/aptible/cli/subcommands/apps_spec.rb
|
225
243
|
- spec/aptible/cli/subcommands/db_spec.rb
|
226
244
|
- spec/aptible/cli/subcommands/domains_spec.rb
|
227
245
|
- spec/aptible/cli/subcommands/logs_spec.rb
|
228
246
|
- spec/aptible/cli/subcommands/ps_spec.rb
|
247
|
+
- spec/mock/ssh_mock.rb
|
229
248
|
- spec/spec_helper.rb
|