harrison 0.0.1
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.
- 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
|