harrison 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +137 -0
- data/Rakefile +7 -0
- data/TODO +26 -0
- data/bin/harrison +7 -0
- data/harrison.gemspec +32 -0
- data/lib/harrison/base.rb +101 -0
- data/lib/harrison/config.rb +9 -0
- data/lib/harrison/deploy.rb +110 -0
- data/lib/harrison/package.rb +87 -0
- data/lib/harrison/ssh.rb +80 -0
- data/lib/harrison/version.rb +3 -0
- data/lib/harrison.rb +91 -0
- data/spec/fixtures/Harrisonfile +34 -0
- data/spec/fixtures/eval_script.rb +1 -0
- data/spec/fixtures/nested/.gitkeep +0 -0
- data/spec/spec_helper.rb +83 -0
- data/spec/unit/harrison/base_spec.rb +221 -0
- data/spec/unit/harrison/deploy_spec.rb +181 -0
- data/spec/unit/harrison/package_spec.rb +127 -0
- data/spec/unit/harrison/ssh_spec.rb +5 -0
- data/spec/unit/harrison_spec.rb +157 -0
- metadata +212 -0
data/lib/harrison.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require "trollop"
|
2
|
+
require "harrison/version"
|
3
|
+
require "harrison/ssh"
|
4
|
+
require "harrison/config"
|
5
|
+
require "harrison/base"
|
6
|
+
require "harrison/package"
|
7
|
+
require "harrison/deploy"
|
8
|
+
|
9
|
+
module Harrison
|
10
|
+
|
11
|
+
def self.invoke(args)
|
12
|
+
@@args = args.freeze
|
13
|
+
|
14
|
+
abort("No command given. Run with --help for valid commands and options.") if @@args.empty?
|
15
|
+
|
16
|
+
# Catch root level --help
|
17
|
+
Harrison::Base.new.parse(@@args.dup) and exit(0) if @@args[0] == '--help'
|
18
|
+
|
19
|
+
# Find Harrisonfile.
|
20
|
+
hf = find_harrisonfile
|
21
|
+
abort("ERROR: Could not find a Harrisonfile in this directory or any ancestor.") if hf.nil?
|
22
|
+
|
23
|
+
# Find the class to handle command.
|
24
|
+
@@runner = find_runner(@@args[0])
|
25
|
+
abort("ERROR: Unrecognized command \"#{@@args[0]}\".") unless @@runner
|
26
|
+
|
27
|
+
# Eval the Harrisonfile.
|
28
|
+
eval_script(hf)
|
29
|
+
|
30
|
+
# Invoke command and cleanup afterwards.
|
31
|
+
begin
|
32
|
+
@@runner.call.run
|
33
|
+
ensure
|
34
|
+
@@runner.call.close
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.config(opts={})
|
39
|
+
@@config ||= Harrison::Config.new(opts)
|
40
|
+
|
41
|
+
if block_given?
|
42
|
+
yield @@config
|
43
|
+
else
|
44
|
+
@@config
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.package(opts={})
|
49
|
+
@@packager ||= Harrison::Package.new(opts)
|
50
|
+
|
51
|
+
# Parse options if this is the target command.
|
52
|
+
@@packager.parse(@@args.dup) if @@runner && @@runner.call == @@packager
|
53
|
+
|
54
|
+
yield @@packager
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.deploy(opts={})
|
58
|
+
@@deployer ||= Harrison::Deploy.new(opts)
|
59
|
+
|
60
|
+
# Parse options if this is the target command.
|
61
|
+
@@deployer.parse(@@args.dup) if @@runner && @@runner.call == @@deployer
|
62
|
+
|
63
|
+
yield @@deployer
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def self.find_harrisonfile
|
70
|
+
previous = nil
|
71
|
+
current = File.expand_path(Dir.pwd)
|
72
|
+
|
73
|
+
until !File.directory?(current) || current == previous
|
74
|
+
filename = File.join(current, 'Harrisonfile')
|
75
|
+
return filename if File.file?(filename)
|
76
|
+
current, previous = File.expand_path("..", current), current
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.eval_script(filename)
|
81
|
+
proc = Proc.new {}
|
82
|
+
eval(File.read(filename), proc.binding, filename)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.find_runner(command)
|
86
|
+
case command.downcase
|
87
|
+
when 'package' then lambda { @@packager if self.class_variable_defined?(:@@packager) }
|
88
|
+
when 'deploy' then lambda { @@deployer if self.class_variable_defined?(:@@deployer) }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# This is an valid fixture Harrisonfile used for testing.
|
2
|
+
|
3
|
+
# Project-wide Config
|
4
|
+
Harrison.config do |h|
|
5
|
+
h.project = 'harrison'
|
6
|
+
h.git_src = "git@github.com:scotje/harrison.git"
|
7
|
+
end
|
8
|
+
|
9
|
+
Harrison.package do |h|
|
10
|
+
# Where to build package.
|
11
|
+
h.host = 'localhost'
|
12
|
+
h.user = 'test'
|
13
|
+
|
14
|
+
# Things we don't want to package.
|
15
|
+
h.exclude = %w(.git config coverage examples log module_files pkg tmp spec)
|
16
|
+
|
17
|
+
# Define the build process here.
|
18
|
+
h.run do |h|
|
19
|
+
h.remote_exec("echo \"test-package\"")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Harrison.deploy do |h|
|
24
|
+
# Hosts to deploy to.
|
25
|
+
h.hosts = [ 'localhost' ]
|
26
|
+
|
27
|
+
h.user = 'test'
|
28
|
+
h.base_dir = '/tmp'
|
29
|
+
|
30
|
+
# Run block will be invoked once for each host after new code is in place.
|
31
|
+
h.run do |h|
|
32
|
+
h.remote_exec("echo \"test-deploy\"")
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
puts "this file was eval-led"
|
File without changes
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'harrison'
|
5
|
+
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
18
|
+
|
19
|
+
RSpec::Matchers.define :exit_with_code do |exp_code|
|
20
|
+
actual = nil
|
21
|
+
|
22
|
+
match do |block|
|
23
|
+
begin
|
24
|
+
block.call
|
25
|
+
rescue SystemExit => e
|
26
|
+
actual = e.status
|
27
|
+
end
|
28
|
+
actual and actual == exp_code
|
29
|
+
end
|
30
|
+
|
31
|
+
failure_message_for_should do |block|
|
32
|
+
"expected block to call exit(#{exp_code}) but exit" +
|
33
|
+
(actual.nil? ? " not called" : "(#{actual}) was called")
|
34
|
+
end
|
35
|
+
|
36
|
+
failure_message_for_should_not do |block|
|
37
|
+
"expected block not to call exit(#{exp_code})"
|
38
|
+
end
|
39
|
+
|
40
|
+
description do
|
41
|
+
"expect block to call exit(#{exp_code})"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def capture(io_names = [ :stdout, :stderr ], &block)
|
46
|
+
original_ios = {}
|
47
|
+
fake_ios = {}
|
48
|
+
|
49
|
+
io_names = [ io_names ] unless io_names.respond_to?(:each)
|
50
|
+
|
51
|
+
io_names.each do |io_name|
|
52
|
+
original_ios[io_name] = eval("$#{io_name}")
|
53
|
+
fake_ios[io_name] = StringIO.new
|
54
|
+
|
55
|
+
eval("$#{io_name} = fake_ios[io_name]")
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
yield
|
60
|
+
ensure
|
61
|
+
io_names.each do |io_name|
|
62
|
+
eval("$#{io_name} = original_ios[io_name]")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if io_names.size == 1
|
67
|
+
return fake_ios[io_names.first].string.downcase
|
68
|
+
else
|
69
|
+
return fake_ios.each { |io, output| fake_ios[io] = output.string.downcase }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def fixture_path
|
74
|
+
File.dirname(__FILE__) + "/fixtures"
|
75
|
+
end
|
76
|
+
|
77
|
+
def harrisonfile_fixture_path(type=nil)
|
78
|
+
if type
|
79
|
+
fixture_path + "/Harrisonfile.#{type}"
|
80
|
+
else
|
81
|
+
fixture_path + "/Harrisonfile"
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Harrison::Base do
|
4
|
+
let(:instance) { Harrison::Base.new }
|
5
|
+
|
6
|
+
describe 'initialize' do
|
7
|
+
it 'should persist arg_opts' do
|
8
|
+
instance = Harrison::Base.new(['foo'])
|
9
|
+
|
10
|
+
instance.instance_variable_get('@arg_opts').should include('foo')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should add debug to arg_opts' do
|
14
|
+
instance.instance_variable_get('@arg_opts').to_s.should include(':debug')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should persist options' do
|
18
|
+
instance = Harrison::Base.new([], testopt: 'foo')
|
19
|
+
|
20
|
+
instance.instance_variable_get('@options').should include(testopt: 'foo')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'class methods' do
|
25
|
+
describe '.option_helper' do
|
26
|
+
it 'should define a getter instance method for the option' do
|
27
|
+
Harrison::Base.option_helper('foo')
|
28
|
+
|
29
|
+
instance.methods.should include(:foo)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should define a setter instance method for the option' do
|
33
|
+
Harrison::Base.option_helper('foo')
|
34
|
+
|
35
|
+
instance.methods.should include(:foo=)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'instance methods' do
|
41
|
+
describe '#exec' do
|
42
|
+
it 'should execute a command locally and return the output' do
|
43
|
+
instance.exec('echo "foo"').should == 'foo'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should complain if command returns non-zero' do
|
47
|
+
output = capture(:stderr) do
|
48
|
+
lambda { instance.exec('cat noexist 2>/dev/null') }.should exit_with_code(1)
|
49
|
+
end
|
50
|
+
|
51
|
+
output.should include('unable', 'execute', 'local', 'command')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#remote_exec' do
|
56
|
+
before(:each) do
|
57
|
+
@mock_ssh = double(:ssh)
|
58
|
+
expect(instance).to receive(:ssh).and_return(@mock_ssh)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should delegate command to ssh instance' do
|
62
|
+
expect(@mock_ssh).to receive(:exec).and_return('remote_exec_return')
|
63
|
+
|
64
|
+
instance.remote_exec('remote exec').should == 'remote_exec_return'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should complain if command returns nil' do
|
68
|
+
expect(@mock_ssh).to receive(:exec).and_return(nil)
|
69
|
+
|
70
|
+
output = capture(:stderr) do
|
71
|
+
lambda { instance.remote_exec('remote exec fail') }.should exit_with_code(1)
|
72
|
+
end
|
73
|
+
|
74
|
+
output.should include('unable', 'execute', 'remote', 'command')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#parse' do
|
79
|
+
it 'should recognize options from the command line' do
|
80
|
+
instance = Harrison::Base.new([
|
81
|
+
[ :testopt, "Test option.", :type => :string ]
|
82
|
+
])
|
83
|
+
|
84
|
+
instance.parse(%w(test --testopt foozle))
|
85
|
+
|
86
|
+
instance.options.should include({testopt: 'foozle'})
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should set the debug flag on the module when passed --debug' do
|
90
|
+
instance.parse(%w(test --debug))
|
91
|
+
|
92
|
+
Harrison::DEBUG.should be_true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#run' do
|
97
|
+
context 'when given a block' do
|
98
|
+
it 'should store the block' do
|
99
|
+
test_block = Proc.new { |test| "block_output" }
|
100
|
+
instance.run(&test_block)
|
101
|
+
|
102
|
+
instance.instance_variable_get("@run_block").should == test_block
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when not given a block' do
|
107
|
+
it 'should return nil if no block stored' do
|
108
|
+
instance.run.should == nil
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should invoke the previously stored block if it exists' do
|
112
|
+
test_block = Proc.new { |test| "block_output" }
|
113
|
+
instance.run(&test_block)
|
114
|
+
|
115
|
+
instance.run.should == "block_output"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '#download' do
|
121
|
+
before(:each) do
|
122
|
+
@mock_ssh = double(:ssh)
|
123
|
+
expect(instance).to receive(:ssh).and_return(@mock_ssh)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should delegate downloads to the SSH class' do
|
127
|
+
expect(@mock_ssh).to receive(:download).with('remote', 'local').and_return(true)
|
128
|
+
|
129
|
+
instance.download('remote', 'local').should == true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#upload' do
|
134
|
+
before(:each) do
|
135
|
+
@mock_ssh = double(:ssh)
|
136
|
+
expect(instance).to receive(:ssh).and_return(@mock_ssh)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should delegate uploads to the SSH class' do
|
140
|
+
expect(@mock_ssh).to receive(:upload).with('local', 'remote').and_return(true)
|
141
|
+
|
142
|
+
instance.upload('local', 'remote').should == true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#close' do
|
147
|
+
before(:each) do
|
148
|
+
@mock_ssh = double(:ssh)
|
149
|
+
instance.instance_variable_set('@ssh', @mock_ssh)
|
150
|
+
expect(instance).to receive(:ssh).and_return(@mock_ssh)
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should invoke close on ssh instance' do
|
154
|
+
expect(@mock_ssh).to receive(:close).and_return(true)
|
155
|
+
|
156
|
+
instance.close.should == true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe 'protected methods' do
|
162
|
+
describe '#ssh' do
|
163
|
+
it 'should instantiate a new ssh instance if needed' do
|
164
|
+
mock_ssh = double(:ssh)
|
165
|
+
expect(Harrison::SSH).to receive(:new).and_return(mock_ssh)
|
166
|
+
|
167
|
+
instance.send(:ssh).should == mock_ssh
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should return previously instantiated ssh instance' do
|
171
|
+
mock_ssh = double(:ssh)
|
172
|
+
instance.instance_variable_set('@ssh', mock_ssh)
|
173
|
+
expect(Harrison::SSH).to_not receive(:new)
|
174
|
+
|
175
|
+
instance.send(:ssh).should == mock_ssh
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe '#ensure_local_dir' do
|
180
|
+
it 'should try to create a directory locally' do
|
181
|
+
expect(instance).to receive(:system).with(/local_dir/).and_return(true)
|
182
|
+
|
183
|
+
instance.send(:ensure_local_dir, 'local_dir').should == true
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should only try to create a directory once' do
|
187
|
+
expect(instance).to receive(:system).with(/local_dir/).once.and_return(true)
|
188
|
+
|
189
|
+
instance.send(:ensure_local_dir, 'local_dir').should == true
|
190
|
+
instance.send(:ensure_local_dir, 'local_dir').should == true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe '#ensure_remote_dir' do
|
195
|
+
before(:each) do
|
196
|
+
@mock_ssh = double(:ssh)
|
197
|
+
allow(instance).to receive(:ssh).and_return(@mock_ssh)
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'should try to create a directory remotely' do
|
201
|
+
expect(@mock_ssh).to receive(:exec).with(/remote_dir/).and_return(true)
|
202
|
+
|
203
|
+
instance.send(:ensure_remote_dir, 'testhost', 'remote_dir').should == true
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'should try to create a directory once for each distinct host' do
|
207
|
+
expect(@mock_ssh).to receive(:exec).with(/remote_dir/).twice.and_return(true)
|
208
|
+
|
209
|
+
instance.send(:ensure_remote_dir, 'test-host', 'remote_dir').should == true
|
210
|
+
instance.send(:ensure_remote_dir, 'another-host', 'remote_dir').should == true
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'should only try to create a directory once for the same host' do
|
214
|
+
expect(@mock_ssh).to receive(:exec).with(/remote_dir/).once.and_return(true)
|
215
|
+
|
216
|
+
instance.send(:ensure_remote_dir, 'test-host', 'remote_dir').should == true
|
217
|
+
instance.send(:ensure_remote_dir, 'test-host', 'remote_dir').should == true
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Harrison::Deploy do
|
4
|
+
let(:instance) do
|
5
|
+
Harrison::Deploy.new.tap do |d|
|
6
|
+
d.hosts = [ 'hf_host' ]
|
7
|
+
d.base_dir = '/hf_basedir'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.initialize' do
|
12
|
+
it 'should add --hosts to arg_opts' do
|
13
|
+
instance.instance_variable_get('@arg_opts').to_s.should include(':hosts')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should add --env to arg_opts' do
|
17
|
+
instance.instance_variable_get('@arg_opts').to_s.should include(':env')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should persist options' do
|
21
|
+
instance = Harrison::Deploy.new(testopt: 'foo')
|
22
|
+
|
23
|
+
instance.instance_variable_get('@options').should include(testopt: 'foo')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'instance methods' do
|
28
|
+
describe '#parse' do
|
29
|
+
it 'should require an artifact to be passed in ARGV' do
|
30
|
+
output = capture(:stderr) do
|
31
|
+
lambda { instance.parse(%w(deploy)) }.should exit_with_code(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
output.should include('must', 'specify', 'artifact')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should use "base_dir" from Harrisonfile if present' do
|
38
|
+
instance.parse(%w(deploy test_artifact.tar.gz))
|
39
|
+
|
40
|
+
instance.options.should include({ base_dir: '/hf_basedir' })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#remote_exec' do
|
45
|
+
before(:each) do
|
46
|
+
@mock_ssh = double(:ssh)
|
47
|
+
expect(instance).to receive(:ssh).and_return(@mock_ssh)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should prepend project dir onto passed command' do
|
51
|
+
instance.base_dir = '/opt'
|
52
|
+
instance.project = 'test_project'
|
53
|
+
|
54
|
+
expect(@mock_ssh).to receive(:exec).with("cd /opt/test_project && test_command").and_return('')
|
55
|
+
|
56
|
+
instance.remote_exec("test_command")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#run' do
|
61
|
+
before(:each) do
|
62
|
+
instance.artifact = 'test_artifact.tar.gz'
|
63
|
+
instance.project = 'test_project'
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when passed a block' do
|
67
|
+
it 'should store the block' do
|
68
|
+
test_block = Proc.new { |test| "block_output" }
|
69
|
+
instance.run(&test_block)
|
70
|
+
|
71
|
+
instance.instance_variable_get("@run_block").should == test_block
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when not passed a block' do
|
76
|
+
before(:each) do
|
77
|
+
@mock_ssh = double(:ssh, exec: '', upload: true, download: true)
|
78
|
+
allow(instance).to receive(:ssh).and_return(@mock_ssh)
|
79
|
+
|
80
|
+
instance.instance_variable_set(:@run_block, Proc.new { |h| "block for #{h.host}" })
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should use hosts from --hosts if passed' do
|
84
|
+
instance.instance_variable_set(:@_argv_hosts, [ 'argv_host1', 'argv_host2' ])
|
85
|
+
|
86
|
+
output = capture(:stdout) do
|
87
|
+
instance.run
|
88
|
+
end
|
89
|
+
|
90
|
+
instance.hosts.should == [ 'argv_host1', 'argv_host2' ]
|
91
|
+
output.should include('argv_host1', 'argv_host2')
|
92
|
+
output.should_not include('hf_host')
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should use hosts from Harrisonfile if --hosts not passed' do
|
96
|
+
output = capture(:stdout) do
|
97
|
+
instance.run
|
98
|
+
end
|
99
|
+
|
100
|
+
instance.hosts.should == [ 'hf_host' ]
|
101
|
+
output.should include('hf_host')
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should require hosts to be set somehow' do
|
105
|
+
instance.hosts = nil
|
106
|
+
|
107
|
+
output = capture(:stderr) do
|
108
|
+
lambda { instance.run }.should exit_with_code(1)
|
109
|
+
end
|
110
|
+
|
111
|
+
output.should include('must', 'specify', 'hosts')
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should invoke the previously stored block once for each host' do
|
115
|
+
instance.hosts = [ 'host1', 'host2', 'host3' ]
|
116
|
+
|
117
|
+
output = capture(:stdout) do
|
118
|
+
expect { |b| instance.run(&b); instance.run }.to yield_control.exactly(3).times
|
119
|
+
end
|
120
|
+
|
121
|
+
output.should include('host1', 'host2', 'host3')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#close' do
|
127
|
+
before(:each) do
|
128
|
+
@test_host1_ssh = double(:ssh, 'closed?' => false)
|
129
|
+
@test_host2_ssh = double(:ssh, 'closed?' => false)
|
130
|
+
|
131
|
+
instance.instance_variable_set(:@_conns, { test_host1: @test_host1_ssh, test_host2: @test_host2_ssh })
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when passed a specific host' do
|
135
|
+
it 'should close the connection to that host' do
|
136
|
+
expect(@test_host1_ssh).to receive(:close).and_return(true)
|
137
|
+
|
138
|
+
instance.close(:test_host1)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should close every open ssh connection' do
|
143
|
+
expect(@test_host1_ssh).to receive(:close).and_return(true)
|
144
|
+
expect(@test_host2_ssh).to receive(:close).and_return(true)
|
145
|
+
|
146
|
+
instance.close
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe 'protected methods' do
|
152
|
+
describe '#ssh' do
|
153
|
+
it 'should open a new SSH connection to self.host' do
|
154
|
+
mock_ssh = double(:ssh)
|
155
|
+
expect(Harrison::SSH).to receive(:new).and_return(mock_ssh)
|
156
|
+
|
157
|
+
instance.host = 'test_host'
|
158
|
+
|
159
|
+
instance.send(:ssh).should == mock_ssh
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should reuse an existing connection to self.host' do
|
163
|
+
mock_ssh = double(:ssh)
|
164
|
+
instance.instance_variable_set(:@_conns, { test_host2: mock_ssh })
|
165
|
+
|
166
|
+
instance.host = :test_host2
|
167
|
+
|
168
|
+
instance.send(:ssh).should == mock_ssh
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe '#remote_project_dir' do
|
173
|
+
it 'should combine base_dir and project name' do
|
174
|
+
instance.base_dir = '/test_base_dir'
|
175
|
+
instance.project = 'test_project'
|
176
|
+
|
177
|
+
instance.send(:remote_project_dir).should include('/test_base_dir', 'test_project')
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|