harrison 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +6 -0
- data/CHANGELOG +10 -0
- data/IDEAS +12 -0
- data/README.md +34 -2
- data/harrison.gemspec +1 -1
- data/lib/harrison/base.rb +9 -2
- data/lib/harrison/deploy.rb +12 -4
- data/lib/harrison/package.rb +26 -7
- data/lib/harrison/ssh.rb +42 -30
- data/lib/harrison/version.rb +1 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/unit/harrison/base_spec.rb +61 -37
- data/spec/unit/harrison/deploy_spec.rb +46 -19
- data/spec/unit/harrison/package_spec.rb +31 -15
- data/spec/unit/harrison/ssh_spec.rb +241 -1
- data/spec/unit/harrison_spec.rb +14 -14
- metadata +10 -8
- data/TODO +0 -26
data/.travis.yml
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
0.1.0
|
2
|
+
-------------------
|
3
|
+
- Renamed --pkg_dir option of 'package' task to --destination
|
4
|
+
- Implemented remote destinations for 'package' task.
|
5
|
+
- Implemented remote artifact sources for 'deploy' task.
|
6
|
+
- Added a default timeout of 10 seconds when establishing an SSH connection.
|
7
|
+
|
8
|
+
0.0.1
|
9
|
+
-------------------
|
10
|
+
- Initial public release.
|
data/IDEAS
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Ideas:
|
2
|
+
------
|
3
|
+
[ ] Allow more elaborate hosts config, e.g.: h.hosts = [ { host: '10.16.18.207', tags: %w(util migrate) } ]
|
4
|
+
[ ] Some kind of --dry-run option.
|
5
|
+
[ ] Allow deploy_via to include alternate user/connection options.
|
6
|
+
[ ] Rename "releases" => "builds" (and "deploys" => "releases"?)
|
7
|
+
[ ] Upload artifact to all hosts before the rest of the deployment process begins.
|
8
|
+
[ ] --force option for deploy task to overwrite an existing release
|
9
|
+
[ ] Something like a "status" command that shows what commit is live for env (on each host?)
|
10
|
+
[ ] Move artifacts out of pkg/ (and into like pkg/deployed) once they have been deployed? (Some sort of flag for the significant env?)
|
11
|
+
[ ] Include branch name in artifact file name.
|
12
|
+
[ ] Make --purge the default behavior for the package step.
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Simple artifact-based deployment for web applications.
|
4
4
|
|
5
|
+
[![Build Status](https://travis-ci.org/scotje/harrison.svg?branch=master)](https://travis-ci.org/scotje/harrison)
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
Add this line to your application's Gemfile:
|
@@ -35,6 +37,10 @@ Harrison.package do |h|
|
|
35
37
|
# Things we don't want to package.
|
36
38
|
h.exclude = %w(.git ./config ./coverage ./examples ./log ./pkg ./tmp ./spec)
|
37
39
|
|
40
|
+
# Where to save the artifact by default.
|
41
|
+
h.destination = 'pkg' # Local folder
|
42
|
+
# h.destination = 'jesse@artifact-host.example.com:/tmp/artifacts' # Remote folder
|
43
|
+
|
38
44
|
# Define the build process here.
|
39
45
|
h.run do |h|
|
40
46
|
# Bundle Install
|
@@ -82,7 +88,7 @@ The `--commit` option understands anything that `git rev-parse` understands. *NO
|
|
82
88
|
reference must be pushed to the repository referenced as `git_src` in the Harrisonfile before
|
83
89
|
you can build it.*
|
84
90
|
|
85
|
-
The packaged release artifact will, by default, be saved into a 'pkg' subfolder:
|
91
|
+
The packaged release artifact will, by default, be saved into a local 'pkg' subfolder:
|
86
92
|
|
87
93
|
```
|
88
94
|
$ harrison package
|
@@ -90,6 +96,23 @@ Packaging 5a547d8 for "harrison" on build-server.example.com...
|
|
90
96
|
Sucessfully packaged 5a547d8 to pkg/20140711170226-5a547d8.tar.gz
|
91
97
|
```
|
92
98
|
|
99
|
+
You can set the destination on the command line with the `--destination` option, or
|
100
|
+
specify a new default in your Harrisonfile:
|
101
|
+
|
102
|
+
```
|
103
|
+
h.destination = '/tmp'
|
104
|
+
```
|
105
|
+
|
106
|
+
You can also specify a remote destination:
|
107
|
+
|
108
|
+
```
|
109
|
+
h.destination = 'jesse@artifact-host.example.com:/tmp/artifacts'
|
110
|
+
```
|
111
|
+
|
112
|
+
The username is optional and, if omitted, the build user will be used. *NOTE: Your build server
|
113
|
+
must have already accepted the SSH host key of the destination server in order to transfer the
|
114
|
+
artifact.*
|
115
|
+
|
93
116
|
There are some additional options available, run `harrison package --help` to see everything available.
|
94
117
|
|
95
118
|
|
@@ -101,7 +124,16 @@ Use the `harrison deploy` command passing the artifact to be deployed as an argu
|
|
101
124
|
$ harrison deploy pkg/20140711170226-5a547d8.tar.gz
|
102
125
|
```
|
103
126
|
|
104
|
-
|
127
|
+
You can also deploy from a remote artifact source:
|
128
|
+
|
129
|
+
```
|
130
|
+
$ harrison deploy jesse@artifact-host.example.com:/tmp/artifacts/20140711170226-5a547d8.tar.gz
|
131
|
+
```
|
132
|
+
|
133
|
+
*NOTE: Each target server must have already accepted the SSH host key of the source server in order to
|
134
|
+
transfer the artifact.*
|
135
|
+
|
136
|
+
By default, the artifact will be deployed to the list of hosts defined in your Harrisonfile.
|
105
137
|
|
106
138
|
You can override the target hosts by passing a `--hosts` option:
|
107
139
|
|
data/harrison.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.6"
|
27
27
|
spec.add_development_dependency "rake"
|
28
|
-
spec.add_development_dependency "rspec"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
29
|
spec.add_development_dependency "debugger" if RUBY_VERSION < "2.0.0"
|
30
30
|
spec.add_development_dependency "byebug" if RUBY_VERSION >= "2.0.0"
|
31
31
|
spec.add_development_dependency "sourcify"
|
data/lib/harrison/base.rb
CHANGED
@@ -87,15 +87,22 @@ module Harrison
|
|
87
87
|
@ssh ||= Harrison::SSH.new(host: @options[:host], user: @options[:user])
|
88
88
|
end
|
89
89
|
|
90
|
+
def remote_regex
|
91
|
+
/^(?:(\S+)@)?(\S+):(\S+)$/
|
92
|
+
end
|
93
|
+
|
90
94
|
def ensure_local_dir(dir)
|
91
95
|
@_ensured_local ||= {}
|
92
96
|
@_ensured_local[dir] || (system("if [ ! -d #{dir} ] ; then mkdir -p #{dir} ; fi") && @_ensured_local[dir] = true) || abort("Error: Unable to create local directory \"#{dir}\".")
|
93
97
|
end
|
94
98
|
|
95
|
-
def ensure_remote_dir(
|
99
|
+
def ensure_remote_dir(dir, with_ssh = nil)
|
100
|
+
with_ssh = ssh if with_ssh.nil?
|
101
|
+
host = with_ssh.host
|
102
|
+
|
96
103
|
@_ensured_remote ||= {}
|
97
104
|
@_ensured_remote[host] ||= {}
|
98
|
-
@_ensured_remote[host][dir] || (
|
105
|
+
@_ensured_remote[host][dir] || (with_ssh.exec("if [ ! -d #{dir} ] ; then mkdir -p #{dir} ; fi") && @_ensured_remote[host][dir] = true) || abort("Error: Unable to create remote directory \"#{dir}\" on \"#{host}\".")
|
99
106
|
end
|
100
107
|
end
|
101
108
|
end
|
data/lib/harrison/deploy.rb
CHANGED
@@ -56,14 +56,22 @@ module Harrison
|
|
56
56
|
hosts.each do |h|
|
57
57
|
self.host = h
|
58
58
|
|
59
|
-
ensure_remote_dir(
|
60
|
-
ensure_remote_dir(
|
59
|
+
ensure_remote_dir("#{remote_project_dir}/deploys", self.ssh)
|
60
|
+
ensure_remote_dir("#{remote_project_dir}/releases", self.ssh)
|
61
61
|
|
62
62
|
# Make folder for release or bail if it already exists.
|
63
63
|
remote_exec("mkdir #{release_dir}")
|
64
64
|
|
65
|
-
|
66
|
-
|
65
|
+
if match = remote_regex.match(artifact)
|
66
|
+
# Copy artifact to host from remote source.
|
67
|
+
src_user, src_host, src_path = match.captures
|
68
|
+
src_user ||= self.user
|
69
|
+
|
70
|
+
remote_exec("scp #{src_user}@#{src_host}:#{src_path} #{remote_project_dir}/releases/")
|
71
|
+
else
|
72
|
+
# Upload artifact to host.
|
73
|
+
upload(artifact, "#{remote_project_dir}/releases/")
|
74
|
+
end
|
67
75
|
|
68
76
|
# Unpack.
|
69
77
|
remote_exec("cd #{release_dir} && tar -xzf ../#{File.basename(artifact)}")
|
data/lib/harrison/package.rb
CHANGED
@@ -5,7 +5,7 @@ module Harrison
|
|
5
5
|
self.class.option_helper(:host)
|
6
6
|
self.class.option_helper(:commit)
|
7
7
|
self.class.option_helper(:purge)
|
8
|
-
self.class.option_helper(:
|
8
|
+
self.class.option_helper(:destination)
|
9
9
|
self.class.option_helper(:remote_dir)
|
10
10
|
self.class.option_helper(:exclude)
|
11
11
|
|
@@ -13,7 +13,7 @@ module Harrison
|
|
13
13
|
arg_opts = [
|
14
14
|
[ :commit, "Specific commit to be packaged. Accepts anything that `git rev-parse` understands.", :type => :string, :default => "HEAD" ],
|
15
15
|
[ :purge, "Remove all previously packaged commits and working copies from the build host when finished.", :type => :boolean, :default => false ],
|
16
|
-
[ :
|
16
|
+
[ :destination, "Local or remote folder to save package to. Remote syntax is: (user@)host:/path", :type => :string, :default => "pkg" ],
|
17
17
|
[ :remote_dir, "Remote working folder.", :type => :string, :default => "~/.harrison" ],
|
18
18
|
]
|
19
19
|
|
@@ -21,7 +21,7 @@ module Harrison
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def remote_exec(cmd)
|
24
|
-
ensure_remote_dir(
|
24
|
+
ensure_remote_dir("#{remote_project_dir}/package")
|
25
25
|
|
26
26
|
super("cd #{remote_project_dir}/package && #{cmd}")
|
27
27
|
end
|
@@ -35,7 +35,7 @@ module Harrison
|
|
35
35
|
puts "Packaging #{commit} for \"#{project}\" on #{host}..."
|
36
36
|
|
37
37
|
# Make sure the folder to save the artifact to locally exists.
|
38
|
-
|
38
|
+
ensure_destination(destination)
|
39
39
|
|
40
40
|
# Fetch/clone git repo on remote host.
|
41
41
|
remote_exec("if [ -d cached ] ; then cd cached && git fetch origin -p ; else git clone #{git_src} cached ; fi")
|
@@ -53,14 +53,22 @@ module Harrison
|
|
53
53
|
# Package build folder into tgz.
|
54
54
|
remote_exec("rm -f #{artifact_name(commit)}.tar.gz && cd #{commit} && tar #{excludes_for_tar} -czf ../#{artifact_name(commit)}.tar.gz .")
|
55
55
|
|
56
|
-
|
57
|
-
|
56
|
+
if match = remote_regex.match(destination)
|
57
|
+
# Copy artifact to remote destination.
|
58
|
+
dest_user, dest_host, dest_path = match.captures
|
59
|
+
dest_user ||= self.user
|
60
|
+
|
61
|
+
remote_exec("scp #{artifact_name(commit)}.tar.gz #{dest_user}@#{dest_host}:#{dest_path}")
|
62
|
+
else
|
63
|
+
# Download (Expand remote path since Net::SCP doesn't expand ~)
|
64
|
+
download(remote_exec("readlink -m #{artifact_name(commit)}.tar.gz"), "#{destination}/#{artifact_name(commit)}.tar.gz")
|
65
|
+
end
|
58
66
|
|
59
67
|
if purge
|
60
68
|
remote_exec("cd .. && rm -rf package")
|
61
69
|
end
|
62
70
|
|
63
|
-
puts "Sucessfully packaged #{commit} to #{
|
71
|
+
puts "Sucessfully packaged #{commit} to #{destination}/#{artifact_name(commit)}.tar.gz"
|
64
72
|
end
|
65
73
|
|
66
74
|
protected
|
@@ -83,5 +91,16 @@ module Harrison
|
|
83
91
|
@_timestamp ||= Time.new.utc.strftime('%Y%m%d%H%M%S')
|
84
92
|
"#{@_timestamp}-#{commit}"
|
85
93
|
end
|
94
|
+
|
95
|
+
def ensure_destination(destination)
|
96
|
+
if match = remote_regex.match(destination)
|
97
|
+
dest_user, dest_host, dest_path = match.captures
|
98
|
+
dest_user ||= self.user
|
99
|
+
|
100
|
+
ensure_remote_dir(dest_path, Harrison::SSH.new(host: dest_host, user: dest_user))
|
101
|
+
else
|
102
|
+
ensure_local_dir(destination)
|
103
|
+
end
|
104
|
+
end
|
86
105
|
end
|
87
106
|
end
|
data/lib/harrison/ssh.rb
CHANGED
@@ -7,46 +7,23 @@ module Harrison
|
|
7
7
|
def initialize(opts={})
|
8
8
|
if opts[:proxy]
|
9
9
|
@proxy = Net::SSH::Proxy::Command.new("ssh #{opts[:proxy]} \"nc %h %p\" 2>/dev/null")
|
10
|
-
@conn = Net::SSH.start(opts[:host], opts[:user], forward_agent: true, proxy: @proxy)
|
10
|
+
@conn = Net::SSH.start(opts[:host], opts[:user], forward_agent: true, proxy: @proxy, timeout: 10)
|
11
11
|
else
|
12
|
-
@conn = Net::SSH.start(opts[:host], opts[:user], forward_agent: true)
|
12
|
+
@conn = Net::SSH.start(opts[:host], opts[:user], forward_agent: true, timeout: 10)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
# Helper to catch non-zero exit status and report errors.
|
17
16
|
def exec(command)
|
18
17
|
puts "INFO (ssh-exec #{desc}): #{command}" if Harrison::DEBUG
|
19
18
|
|
20
|
-
|
21
|
-
stderr_data = ""
|
22
|
-
exit_code = nil
|
23
|
-
|
24
|
-
@conn.open_channel do |channel|
|
25
|
-
channel.exec(command) do |ch, success|
|
26
|
-
warn "Couldn't execute command (ssh.channel.exec) on remote host: #{command}" unless success
|
27
|
-
|
28
|
-
channel.on_data do |ch,data|
|
29
|
-
stdout_data += data
|
30
|
-
end
|
31
|
-
|
32
|
-
channel.on_extended_data do |ch,type,data|
|
33
|
-
stderr_data += data
|
34
|
-
end
|
19
|
+
result = invoke(@conn, command)
|
35
20
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
21
|
+
if Harrison::DEBUG || result[:status] != 0
|
22
|
+
warn "STDERR (ssh-exec #{desc}): #{result[:stderr]}" unless result[:stderr].empty?
|
23
|
+
warn "STDOUT (ssh-exec #{desc}): #{result[:stdout]}" unless result[:stdout].empty?
|
40
24
|
end
|
41
25
|
|
42
|
-
|
43
|
-
|
44
|
-
if Harrison::DEBUG || exit_code != 0
|
45
|
-
warn "STDERR (ssh-exec #{desc}): #{stderr_data.strip}" unless stderr_data.empty?
|
46
|
-
warn "STDOUT (ssh-exec #{desc}): #{stdout_data.strip}" unless stdout_data.empty?
|
47
|
-
end
|
48
|
-
|
49
|
-
(exit_code == 0) ? stdout_data : nil
|
26
|
+
(result[:status] == 0) ? result[:stdout] : nil
|
50
27
|
end
|
51
28
|
|
52
29
|
def download(remote_path, local_path)
|
@@ -76,5 +53,40 @@ module Harrison
|
|
76
53
|
@conn.host
|
77
54
|
end
|
78
55
|
end
|
56
|
+
|
57
|
+
def host
|
58
|
+
@conn.host
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
# ----------------------------------------
|
63
|
+
|
64
|
+
def invoke(conn, cmd)
|
65
|
+
stdout_data = ""
|
66
|
+
stderr_data = ""
|
67
|
+
exit_code = nil
|
68
|
+
|
69
|
+
conn.open_channel do |channel|
|
70
|
+
channel.exec(cmd) do |ch, success|
|
71
|
+
warn "Couldn't execute command (ssh.channel.exec) on remote host: #{cmd}" unless success
|
72
|
+
|
73
|
+
channel.on_data do |ch,data|
|
74
|
+
stdout_data += data
|
75
|
+
end
|
76
|
+
|
77
|
+
channel.on_extended_data do |ch,type,data|
|
78
|
+
stderr_data += data
|
79
|
+
end
|
80
|
+
|
81
|
+
channel.on_request("exit-status") do |ch,data|
|
82
|
+
exit_code = data.read_long
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
conn.loop
|
88
|
+
|
89
|
+
{ status: exit_code, stdout: stdout_data.strip, stderr: stderr_data.strip }
|
90
|
+
end
|
79
91
|
end
|
80
92
|
end
|
data/lib/harrison/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -5,7 +5,6 @@ require 'harrison'
|
|
5
5
|
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
7
|
RSpec.configure do |config|
|
8
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
8
|
config.run_all_when_everything_filtered = true
|
10
9
|
config.filter_run :focus
|
11
10
|
|
@@ -28,12 +27,12 @@ RSpec::Matchers.define :exit_with_code do |exp_code|
|
|
28
27
|
actual and actual == exp_code
|
29
28
|
end
|
30
29
|
|
31
|
-
|
30
|
+
failure_message do |block|
|
32
31
|
"expected block to call exit(#{exp_code}) but exit" +
|
33
32
|
(actual.nil? ? " not called" : "(#{actual}) was called")
|
34
33
|
end
|
35
34
|
|
36
|
-
|
35
|
+
failure_message_when_negated do |block|
|
37
36
|
"expected block not to call exit(#{exp_code})"
|
38
37
|
end
|
39
38
|
|
@@ -7,17 +7,17 @@ describe Harrison::Base do
|
|
7
7
|
it 'should persist arg_opts' do
|
8
8
|
instance = Harrison::Base.new(['foo'])
|
9
9
|
|
10
|
-
instance.instance_variable_get('@arg_opts').
|
10
|
+
expect(instance.instance_variable_get('@arg_opts')).to include('foo')
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'should add debug to arg_opts' do
|
14
|
-
instance.instance_variable_get('@arg_opts').to_s.
|
14
|
+
expect(instance.instance_variable_get('@arg_opts').to_s).to include(':debug')
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'should persist options' do
|
18
18
|
instance = Harrison::Base.new([], testopt: 'foo')
|
19
19
|
|
20
|
-
instance.instance_variable_get('@options').
|
20
|
+
expect(instance.instance_variable_get('@options')).to include(testopt: 'foo')
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -26,13 +26,13 @@ describe Harrison::Base do
|
|
26
26
|
it 'should define a getter instance method for the option' do
|
27
27
|
Harrison::Base.option_helper('foo')
|
28
28
|
|
29
|
-
instance.methods.
|
29
|
+
expect(instance.methods).to include(:foo)
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'should define a setter instance method for the option' do
|
33
33
|
Harrison::Base.option_helper('foo')
|
34
34
|
|
35
|
-
instance.methods.
|
35
|
+
expect(instance.methods).to include(:foo=)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -40,15 +40,15 @@ describe Harrison::Base do
|
|
40
40
|
describe 'instance methods' do
|
41
41
|
describe '#exec' do
|
42
42
|
it 'should execute a command locally and return the output' do
|
43
|
-
instance.exec('echo "foo"').
|
43
|
+
expect(instance.exec('echo "foo"')).to eq('foo')
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'should complain if command returns non-zero' do
|
47
47
|
output = capture(:stderr) do
|
48
|
-
lambda { instance.exec('cat noexist 2>/dev/null') }.
|
48
|
+
expect(lambda { instance.exec('cat noexist 2>/dev/null') }).to exit_with_code(1)
|
49
49
|
end
|
50
50
|
|
51
|
-
output.
|
51
|
+
expect(output).to include('unable', 'execute', 'local', 'command')
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
@@ -61,21 +61,29 @@ describe Harrison::Base do
|
|
61
61
|
it 'should delegate command to ssh instance' do
|
62
62
|
expect(@mock_ssh).to receive(:exec).and_return('remote_exec_return')
|
63
63
|
|
64
|
-
instance.remote_exec('remote exec').
|
64
|
+
expect(instance.remote_exec('remote exec')).to eq('remote_exec_return')
|
65
65
|
end
|
66
66
|
|
67
67
|
it 'should complain if command returns nil' do
|
68
68
|
expect(@mock_ssh).to receive(:exec).and_return(nil)
|
69
69
|
|
70
70
|
output = capture(:stderr) do
|
71
|
-
lambda { instance.remote_exec('remote exec fail') }.
|
71
|
+
expect(lambda { instance.remote_exec('remote exec fail') }).to exit_with_code(1)
|
72
72
|
end
|
73
73
|
|
74
|
-
output.
|
74
|
+
expect(output).to include('unable', 'execute', 'remote', 'command')
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
78
|
describe '#parse' do
|
79
|
+
before(:each) do
|
80
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
81
|
+
end
|
82
|
+
|
83
|
+
after(:each) do
|
84
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
85
|
+
end
|
86
|
+
|
79
87
|
it 'should recognize options from the command line' do
|
80
88
|
instance = Harrison::Base.new([
|
81
89
|
[ :testopt, "Test option.", :type => :string ]
|
@@ -83,13 +91,13 @@ describe Harrison::Base do
|
|
83
91
|
|
84
92
|
instance.parse(%w(test --testopt foozle))
|
85
93
|
|
86
|
-
instance.options.
|
94
|
+
expect(instance.options).to include({testopt: 'foozle'})
|
87
95
|
end
|
88
96
|
|
89
97
|
it 'should set the debug flag on the module when passed --debug' do
|
90
98
|
instance.parse(%w(test --debug))
|
91
99
|
|
92
|
-
Harrison::DEBUG.
|
100
|
+
expect(Harrison::DEBUG).to be true
|
93
101
|
end
|
94
102
|
end
|
95
103
|
|
@@ -99,20 +107,20 @@ describe Harrison::Base do
|
|
99
107
|
test_block = Proc.new { |test| "block_output" }
|
100
108
|
instance.run(&test_block)
|
101
109
|
|
102
|
-
instance.instance_variable_get("@run_block").
|
110
|
+
expect(instance.instance_variable_get("@run_block")).to eq(test_block)
|
103
111
|
end
|
104
112
|
end
|
105
113
|
|
106
114
|
context 'when not given a block' do
|
107
115
|
it 'should return nil if no block stored' do
|
108
|
-
instance.run.
|
116
|
+
expect(instance.run).to be_nil
|
109
117
|
end
|
110
118
|
|
111
119
|
it 'should invoke the previously stored block if it exists' do
|
112
120
|
test_block = Proc.new { |test| "block_output" }
|
113
121
|
instance.run(&test_block)
|
114
122
|
|
115
|
-
instance.run.
|
123
|
+
expect(instance.run).to eq("block_output")
|
116
124
|
end
|
117
125
|
end
|
118
126
|
end
|
@@ -124,9 +132,9 @@ describe Harrison::Base do
|
|
124
132
|
end
|
125
133
|
|
126
134
|
it 'should delegate downloads to the SSH class' do
|
127
|
-
expect(@mock_ssh).to receive(:download).with('remote', 'local')
|
135
|
+
expect(@mock_ssh).to receive(:download).with('remote', 'local')
|
128
136
|
|
129
|
-
instance.download('remote', 'local')
|
137
|
+
instance.download('remote', 'local')
|
130
138
|
end
|
131
139
|
end
|
132
140
|
|
@@ -137,9 +145,9 @@ describe Harrison::Base do
|
|
137
145
|
end
|
138
146
|
|
139
147
|
it 'should delegate uploads to the SSH class' do
|
140
|
-
expect(@mock_ssh).to receive(:upload).with('local', 'remote')
|
148
|
+
expect(@mock_ssh).to receive(:upload).with('local', 'remote')
|
141
149
|
|
142
|
-
instance.upload('local', 'remote')
|
150
|
+
instance.upload('local', 'remote')
|
143
151
|
end
|
144
152
|
end
|
145
153
|
|
@@ -151,9 +159,9 @@ describe Harrison::Base do
|
|
151
159
|
end
|
152
160
|
|
153
161
|
it 'should invoke close on ssh instance' do
|
154
|
-
expect(@mock_ssh).to receive(:close)
|
162
|
+
expect(@mock_ssh).to receive(:close)
|
155
163
|
|
156
|
-
instance.close
|
164
|
+
instance.close
|
157
165
|
end
|
158
166
|
end
|
159
167
|
end
|
@@ -164,7 +172,7 @@ describe Harrison::Base do
|
|
164
172
|
mock_ssh = double(:ssh)
|
165
173
|
expect(Harrison::SSH).to receive(:new).and_return(mock_ssh)
|
166
174
|
|
167
|
-
instance.send(:ssh).
|
175
|
+
expect(instance.send(:ssh)).to be mock_ssh
|
168
176
|
end
|
169
177
|
|
170
178
|
it 'should return previously instantiated ssh instance' do
|
@@ -172,7 +180,21 @@ describe Harrison::Base do
|
|
172
180
|
instance.instance_variable_set('@ssh', mock_ssh)
|
173
181
|
expect(Harrison::SSH).to_not receive(:new)
|
174
182
|
|
175
|
-
instance.send(:ssh).
|
183
|
+
expect(instance.send(:ssh)).to be mock_ssh
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe '#remote_regex' do
|
188
|
+
it 'should match a standard remote SCP target without a username' do
|
189
|
+
expect(instance.send(:remote_regex)).to match("test_host1:/tmp/target")
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should match a standard remote SCP target with a username' do
|
193
|
+
expect(instance.send(:remote_regex)).to match("testuser@test_host:/tmp/target")
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'should not match a local file path' do
|
197
|
+
expect(instance.send(:remote_regex)).not_to match("tmp/target")
|
176
198
|
end
|
177
199
|
end
|
178
200
|
|
@@ -180,41 +202,43 @@ describe Harrison::Base do
|
|
180
202
|
it 'should try to create a directory locally' do
|
181
203
|
expect(instance).to receive(:system).with(/local_dir/).and_return(true)
|
182
204
|
|
183
|
-
instance.send(:ensure_local_dir, 'local_dir').
|
205
|
+
expect(instance.send(:ensure_local_dir, 'local_dir')).to be true
|
184
206
|
end
|
185
207
|
|
186
208
|
it 'should only try to create a directory once' do
|
187
209
|
expect(instance).to receive(:system).with(/local_dir/).once.and_return(true)
|
188
210
|
|
189
|
-
instance.send(:ensure_local_dir, 'local_dir').
|
190
|
-
instance.send(:ensure_local_dir, 'local_dir').
|
211
|
+
expect(instance.send(:ensure_local_dir, 'local_dir')).to be true
|
212
|
+
expect(instance.send(:ensure_local_dir, 'local_dir')).to be true
|
191
213
|
end
|
192
214
|
end
|
193
215
|
|
194
216
|
describe '#ensure_remote_dir' do
|
195
217
|
before(:each) do
|
196
|
-
@mock_ssh = double(:ssh)
|
218
|
+
@mock_ssh = double(:ssh, host: 'test_host1')
|
219
|
+
@mock_ssh2 = double(:ssh, host: 'test_host2')
|
220
|
+
|
197
221
|
allow(instance).to receive(:ssh).and_return(@mock_ssh)
|
198
222
|
end
|
199
223
|
|
200
224
|
it 'should try to create a directory remotely' do
|
201
225
|
expect(@mock_ssh).to receive(:exec).with(/remote_dir/).and_return(true)
|
202
|
-
|
203
|
-
instance.send(:ensure_remote_dir, 'testhost', 'remote_dir').should == true
|
226
|
+
expect(instance.send(:ensure_remote_dir, 'remote_dir')).to be true
|
204
227
|
end
|
205
228
|
|
206
|
-
it 'should try to create a directory once for each distinct
|
207
|
-
expect(@mock_ssh).to receive(:exec).with(/remote_dir/).
|
229
|
+
it 'should try to create a directory once for each distinct ssh connection' do
|
230
|
+
expect(@mock_ssh).to receive(:exec).with(/remote_dir/).once.and_return(true)
|
231
|
+
expect(@mock_ssh2).to receive(:exec).with(/remote_dir/).once.and_return(true)
|
208
232
|
|
209
|
-
instance.send(:ensure_remote_dir, '
|
210
|
-
instance.send(:ensure_remote_dir, '
|
233
|
+
expect(instance.send(:ensure_remote_dir, 'remote_dir')).to be true
|
234
|
+
expect(instance.send(:ensure_remote_dir, 'remote_dir', @mock_ssh2)).to be true
|
211
235
|
end
|
212
236
|
|
213
|
-
it 'should only try to create a directory once for the same
|
237
|
+
it 'should only try to create a directory once for the same ssh connection' do
|
214
238
|
expect(@mock_ssh).to receive(:exec).with(/remote_dir/).once.and_return(true)
|
215
239
|
|
216
|
-
instance.send(:ensure_remote_dir, '
|
217
|
-
instance.send(:ensure_remote_dir, '
|
240
|
+
expect(instance.send(:ensure_remote_dir, 'remote_dir')).to be true
|
241
|
+
expect(instance.send(:ensure_remote_dir, 'remote_dir')).to be true
|
218
242
|
end
|
219
243
|
end
|
220
244
|
end
|
@@ -10,17 +10,17 @@ describe Harrison::Deploy do
|
|
10
10
|
|
11
11
|
describe '.initialize' do
|
12
12
|
it 'should add --hosts to arg_opts' do
|
13
|
-
instance.instance_variable_get('@arg_opts').to_s.
|
13
|
+
expect(instance.instance_variable_get('@arg_opts').to_s).to include(':hosts')
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'should add --env to arg_opts' do
|
17
|
-
instance.instance_variable_get('@arg_opts').to_s.
|
17
|
+
expect(instance.instance_variable_get('@arg_opts').to_s).to include(':env')
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'should persist options' do
|
21
21
|
instance = Harrison::Deploy.new(testopt: 'foo')
|
22
22
|
|
23
|
-
instance.instance_variable_get('@options').
|
23
|
+
expect(instance.instance_variable_get('@options')).to include(testopt: 'foo')
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -28,16 +28,16 @@ describe Harrison::Deploy do
|
|
28
28
|
describe '#parse' do
|
29
29
|
it 'should require an artifact to be passed in ARGV' do
|
30
30
|
output = capture(:stderr) do
|
31
|
-
lambda { instance.parse(%w(deploy)) }.
|
31
|
+
expect(lambda { instance.parse(%w(deploy)) }).to exit_with_code(1)
|
32
32
|
end
|
33
33
|
|
34
|
-
output.
|
34
|
+
expect(output).to include('must', 'specify', 'artifact')
|
35
35
|
end
|
36
36
|
|
37
37
|
it 'should use "base_dir" from Harrisonfile if present' do
|
38
38
|
instance.parse(%w(deploy test_artifact.tar.gz))
|
39
39
|
|
40
|
-
instance.options.
|
40
|
+
expect(instance.options).to include({ base_dir: '/hf_basedir' })
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -68,13 +68,13 @@ describe Harrison::Deploy do
|
|
68
68
|
test_block = Proc.new { |test| "block_output" }
|
69
69
|
instance.run(&test_block)
|
70
70
|
|
71
|
-
instance.instance_variable_get("@run_block").
|
71
|
+
expect(instance.instance_variable_get("@run_block")).to be test_block
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
75
|
context 'when not passed a block' do
|
76
76
|
before(:each) do
|
77
|
-
@mock_ssh = double(:ssh, exec: '', upload: true, download: true)
|
77
|
+
@mock_ssh = double(:ssh, host: 'test_host1', exec: '', upload: true, download: true)
|
78
78
|
allow(instance).to receive(:ssh).and_return(@mock_ssh)
|
79
79
|
|
80
80
|
instance.instance_variable_set(:@run_block, Proc.new { |h| "block for #{h.host}" })
|
@@ -87,9 +87,9 @@ describe Harrison::Deploy do
|
|
87
87
|
instance.run
|
88
88
|
end
|
89
89
|
|
90
|
-
instance.hosts.
|
91
|
-
output.
|
92
|
-
output.
|
90
|
+
expect(instance.hosts).to eq([ 'argv_host1', 'argv_host2' ])
|
91
|
+
expect(output).to include('argv_host1', 'argv_host2')
|
92
|
+
expect(output).to_not include('hf_host')
|
93
93
|
end
|
94
94
|
|
95
95
|
it 'should use hosts from Harrisonfile if --hosts not passed' do
|
@@ -97,18 +97,18 @@ describe Harrison::Deploy do
|
|
97
97
|
instance.run
|
98
98
|
end
|
99
99
|
|
100
|
-
instance.hosts.
|
101
|
-
output.
|
100
|
+
expect(instance.hosts).to eq([ 'hf_host' ])
|
101
|
+
expect(output).to include('hf_host')
|
102
102
|
end
|
103
103
|
|
104
104
|
it 'should require hosts to be set somehow' do
|
105
105
|
instance.hosts = nil
|
106
106
|
|
107
107
|
output = capture(:stderr) do
|
108
|
-
lambda { instance.run }.
|
108
|
+
expect(lambda { instance.run }).to exit_with_code(1)
|
109
109
|
end
|
110
110
|
|
111
|
-
output.
|
111
|
+
expect(output).to include('must', 'specify', 'hosts')
|
112
112
|
end
|
113
113
|
|
114
114
|
it 'should invoke the previously stored block once for each host' do
|
@@ -118,7 +118,34 @@ describe Harrison::Deploy do
|
|
118
118
|
expect { |b| instance.run(&b); instance.run }.to yield_control.exactly(3).times
|
119
119
|
end
|
120
120
|
|
121
|
-
output.
|
121
|
+
expect(output).to include('host1', 'host2', 'host3')
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when deploying from a remote artifact source' do
|
125
|
+
before(:each) do
|
126
|
+
instance.artifact = 'test_user@test_host1:/tmp/test_artifact.tar.gz'
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should invoke scp on the remote host' do
|
130
|
+
allow(instance).to receive(:remote_exec).and_return('')
|
131
|
+
expect(instance).to receive(:remote_exec).with(/scp test_user@test_host1:\/tmp\/test_artifact.tar.gz/).and_return('')
|
132
|
+
|
133
|
+
output = capture(:stdout) do
|
134
|
+
instance.run
|
135
|
+
end
|
136
|
+
|
137
|
+
expect(output).to include('deployed', 'test_user', 'test_host1', '/tmp/test_artifact.tar.gz')
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should not invoke Harrison::SSH.upload' do
|
141
|
+
expect(@mock_ssh).not_to receive(:upload)
|
142
|
+
|
143
|
+
output = capture(:stdout) do
|
144
|
+
instance.run
|
145
|
+
end
|
146
|
+
|
147
|
+
expect(output).to include('deployed', 'test_user', 'test_host1', '/tmp/test_artifact.tar.gz')
|
148
|
+
end
|
122
149
|
end
|
123
150
|
end
|
124
151
|
end
|
@@ -156,7 +183,7 @@ describe Harrison::Deploy do
|
|
156
183
|
|
157
184
|
instance.host = 'test_host'
|
158
185
|
|
159
|
-
instance.send(:ssh).
|
186
|
+
expect(instance.send(:ssh)).to be mock_ssh
|
160
187
|
end
|
161
188
|
|
162
189
|
it 'should reuse an existing connection to self.host' do
|
@@ -165,7 +192,7 @@ describe Harrison::Deploy do
|
|
165
192
|
|
166
193
|
instance.host = :test_host2
|
167
194
|
|
168
|
-
instance.send(:ssh).
|
195
|
+
expect(instance.send(:ssh)).to be mock_ssh
|
169
196
|
end
|
170
197
|
end
|
171
198
|
|
@@ -174,7 +201,7 @@ describe Harrison::Deploy do
|
|
174
201
|
instance.base_dir = '/test_base_dir'
|
175
202
|
instance.project = 'test_project'
|
176
203
|
|
177
|
-
instance.send(:remote_project_dir).
|
204
|
+
expect(instance.send(:remote_project_dir)).to include('/test_base_dir', 'test_project')
|
178
205
|
end
|
179
206
|
end
|
180
207
|
end
|
@@ -11,25 +11,25 @@ describe Harrison::Package do
|
|
11
11
|
|
12
12
|
describe '.initialize' do
|
13
13
|
it 'should add --commit to arg_opts' do
|
14
|
-
instance.instance_variable_get('@arg_opts').to_s.
|
14
|
+
expect(instance.instance_variable_get('@arg_opts').to_s).to include(':commit')
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'should add --purge to arg_opts' do
|
18
|
-
instance.instance_variable_get('@arg_opts').to_s.
|
18
|
+
expect(instance.instance_variable_get('@arg_opts').to_s).to include(':purge')
|
19
19
|
end
|
20
20
|
|
21
|
-
it 'should add --
|
22
|
-
instance.instance_variable_get('@arg_opts').to_s.
|
21
|
+
it 'should add --destination to arg_opts' do
|
22
|
+
expect(instance.instance_variable_get('@arg_opts').to_s).to include(':destination')
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'should add --remote-dir to arg_opts' do
|
26
|
-
instance.instance_variable_get('@arg_opts').to_s.
|
26
|
+
expect(instance.instance_variable_get('@arg_opts').to_s).to include(':remote_dir')
|
27
27
|
end
|
28
28
|
|
29
29
|
it 'should persist options' do
|
30
30
|
instance = Harrison::Package.new(testopt: 'foo')
|
31
31
|
|
32
|
-
instance.instance_variable_get('@options').
|
32
|
+
expect(instance.instance_variable_get('@options')).to include(testopt: 'foo')
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -57,16 +57,16 @@ describe Harrison::Package do
|
|
57
57
|
test_block = Proc.new { |test| "block_output" }
|
58
58
|
instance.run(&test_block)
|
59
59
|
|
60
|
-
instance.instance_variable_get("@run_block").
|
60
|
+
expect(instance.instance_variable_get("@run_block")).to be test_block
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
64
|
context 'when not passed a block' do
|
65
65
|
before(:each) do
|
66
|
-
@mock_ssh = double(:ssh, exec: '', upload: true, download: true)
|
66
|
+
@mock_ssh = double(:ssh, host: 'test_host1', exec: '', upload: true, download: true)
|
67
67
|
allow(instance).to receive(:ssh).at_least(:once).and_return(@mock_ssh)
|
68
68
|
|
69
|
-
allow(instance).to receive(:
|
69
|
+
allow(instance).to receive(:ensure_destination).and_return(true)
|
70
70
|
allow(instance).to receive(:resolve_commit!).and_return('test')
|
71
71
|
allow(instance).to receive(:excludes_for_tar).and_return('')
|
72
72
|
end
|
@@ -79,7 +79,7 @@ describe Harrison::Package do
|
|
79
79
|
instance.run
|
80
80
|
end
|
81
81
|
|
82
|
-
output.
|
82
|
+
expect(output).to include('block for hf_host')
|
83
83
|
end
|
84
84
|
end
|
85
85
|
end
|
@@ -91,7 +91,7 @@ describe Harrison::Package do
|
|
91
91
|
instance.remote_dir = '~/.harrison'
|
92
92
|
instance.project = 'test_project'
|
93
93
|
|
94
|
-
instance.send(:remote_project_dir).
|
94
|
+
expect(instance.send(:remote_project_dir)).to include('~/.harrison', 'test_project')
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
@@ -100,7 +100,7 @@ describe Harrison::Package do
|
|
100
100
|
instance.commit = 'giant'
|
101
101
|
expect(instance).to receive(:exec).with(/git rev-parse.*giant/).and_return('fef1f0')
|
102
102
|
|
103
|
-
instance.send(:resolve_commit!).
|
103
|
+
expect(instance.send(:resolve_commit!)).to eq('fef1f0')
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
@@ -108,19 +108,35 @@ describe Harrison::Package do
|
|
108
108
|
it 'should return an empty string if exclude is nil' do
|
109
109
|
instance.exclude = nil
|
110
110
|
|
111
|
-
instance.send(:excludes_for_tar).
|
111
|
+
expect(instance.send(:excludes_for_tar)).to be_empty
|
112
112
|
end
|
113
113
|
|
114
114
|
it 'should return an empty string if exclude is empty' do
|
115
115
|
instance.exclude = []
|
116
116
|
|
117
|
-
instance.send(:excludes_for_tar).
|
117
|
+
expect(instance.send(:excludes_for_tar)).to be_empty
|
118
118
|
end
|
119
119
|
|
120
120
|
it 'should return an --exclude option for each member of exclude' do
|
121
121
|
instance.exclude = [ 'fee', 'fi', 'fo', 'fum' ]
|
122
122
|
|
123
|
-
instance.send(:excludes_for_tar).scan(/--exclude/).size.
|
123
|
+
expect(instance.send(:excludes_for_tar).scan(/--exclude/).size).to eq(instance.exclude.size)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '#ensure_destination' do
|
128
|
+
it 'should ensure a local destination' do
|
129
|
+
expect(instance).to receive(:ensure_local_dir).with('/tmp/test_path').and_return(true)
|
130
|
+
|
131
|
+
instance.send(:ensure_destination, '/tmp/test_path')
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should ensure a remote destination' do
|
135
|
+
mock_ssh = double(:ssh, host: 'test_host1')
|
136
|
+
expect(mock_ssh).to receive(:exec).with(/\/tmp\/test_path/).and_return(true)
|
137
|
+
expect(Harrison::SSH).to receive(:new).with({ host: 'test_host1', user: 'test_user' }).and_return(mock_ssh)
|
138
|
+
|
139
|
+
instance.send(:ensure_destination, 'test_user@test_host1:/tmp/test_path')
|
124
140
|
end
|
125
141
|
end
|
126
142
|
end
|
@@ -1,5 +1,245 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Harrison::SSH do
|
4
|
-
|
4
|
+
before(:all) do
|
5
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
6
|
+
Harrison.const_set("DEBUG", false)
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
11
|
+
end
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@mock_ssh_chan = double(:ssh_channel)
|
15
|
+
|
16
|
+
@mock_ssh_conn = double(:ssh_conn, host: 'test.example.com')
|
17
|
+
allow(@mock_ssh_conn).to receive(:loop)
|
18
|
+
allow(@mock_ssh_conn).to receive(:open_channel).and_yield(@mock_ssh_chan)
|
19
|
+
|
20
|
+
allow(Net::SSH).to receive(:start).and_return(@mock_ssh_conn)
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:instance) { Harrison::SSH.new(host: 'test.example.com', user: 'test_user') }
|
24
|
+
|
25
|
+
describe 'initialize' do
|
26
|
+
it 'should open an SSH connection' do
|
27
|
+
expect(Net::SSH).to receive(:start).with('test.example.com', 'test_user', anything).and_return(double(:ssh_conn))
|
28
|
+
|
29
|
+
Harrison::SSH.new(host: 'test.example.com', user: 'test_user')
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when passed a :proxy option' do
|
33
|
+
it 'should open an SSH connection with a proxy command' do
|
34
|
+
proxy = double(:ssh_proxy)
|
35
|
+
|
36
|
+
expect(Net::SSH::Proxy::Command).to receive(:new).and_return(proxy)
|
37
|
+
expect(Net::SSH).to receive(:start).with('test.example.com', 'test_user', { forward_agent: true, proxy: proxy, timeout: 10 }).and_return(double(:ssh_conn))
|
38
|
+
|
39
|
+
Harrison::SSH.new(host: 'test.example.com', user: 'test_user', proxy: 'test-proxy.example.com')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#exec' do
|
45
|
+
it 'should exec the passed command on an SSH connection channel' do
|
46
|
+
command = "pwd"
|
47
|
+
expect(@mock_ssh_chan).to receive(:exec).with(command)
|
48
|
+
|
49
|
+
instance.exec(command)
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when the command exits non-zero' do
|
53
|
+
before(:each) do
|
54
|
+
allow(instance).to receive(:invoke).with(@mock_ssh_conn, anything).and_return({ status: 1, stdout: 'standard output', stderr: 'standard error' })
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should warn whatever the command emitted to stdout' do
|
58
|
+
output = capture(:stderr) do
|
59
|
+
instance.exec('cat noexist 2>/dev/null')
|
60
|
+
end
|
61
|
+
|
62
|
+
expect(output).to include('stdout', 'standard output')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should warn whatever the command emitted to stderr' do
|
66
|
+
output = capture(:stderr) do
|
67
|
+
instance.exec('cat noexist 2>/dev/null')
|
68
|
+
end
|
69
|
+
|
70
|
+
expect(output).to include('stderr', 'standard error')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should return nil' do
|
74
|
+
capture(:stderr) do
|
75
|
+
expect(instance.exec('cat noexist 2>/dev/null')).to be_nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when --debug is set' do
|
81
|
+
before(:each) do
|
82
|
+
allow(instance).to receive(:invoke).with(@mock_ssh_conn, anything).and_return({ status: 0, stdout: 'standard output', stderr: 'standard error' })
|
83
|
+
end
|
84
|
+
|
85
|
+
before(:each) do
|
86
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
87
|
+
Harrison.const_set("DEBUG", true)
|
88
|
+
end
|
89
|
+
|
90
|
+
after(:each) do
|
91
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
92
|
+
Harrison.const_set("DEBUG", false)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should output the command being run' do
|
96
|
+
output = capture(:stdout) do
|
97
|
+
capture(:stderr) do
|
98
|
+
instance.exec('touch testfile')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
expect(output).to include('info', 'touch testfile')
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should warn whatever the command emitted to stdout' do
|
106
|
+
output = capture(:stderr) do
|
107
|
+
capture(:stdout) do
|
108
|
+
instance.exec('touch testfile')
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
expect(output).to include('stdout', 'standard output')
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should warn whatever the command emitted to stderr' do
|
116
|
+
output = capture(:stderr) do
|
117
|
+
capture(:stdout) do
|
118
|
+
instance.exec('touch testfile')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
expect(output).to include('stderr', 'standard error')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '#download' do
|
128
|
+
before(:each) do
|
129
|
+
@mock_scp = double(:net_scp)
|
130
|
+
allow(@mock_ssh_conn).to receive(:scp).and_return(@mock_scp)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should delegate to net-scp' do
|
134
|
+
expect(@mock_scp).to receive(:download!).with('remote', 'local')
|
135
|
+
|
136
|
+
instance.download('remote', 'local')
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'when --debug is set' do
|
140
|
+
before(:each) do
|
141
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
142
|
+
Harrison.const_set("DEBUG", true)
|
143
|
+
end
|
144
|
+
|
145
|
+
after(:each) do
|
146
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
147
|
+
Harrison.const_set("DEBUG", false)
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should output what is being downloaded and to where' do
|
151
|
+
expect(@mock_scp).to receive(:download!).with('remote', 'local')
|
152
|
+
|
153
|
+
output = capture(:stdout) do
|
154
|
+
instance.download('remote', 'local')
|
155
|
+
end
|
156
|
+
|
157
|
+
expect(output).to include('scp-down', 'local', 'remote')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe '#upload' do
|
163
|
+
before(:each) do
|
164
|
+
@mock_scp = double(:net_scp)
|
165
|
+
allow(@mock_ssh_conn).to receive(:scp).and_return(@mock_scp)
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should delegate to net-scp' do
|
169
|
+
expect(@mock_scp).to receive(:upload!).with('local', 'remote')
|
170
|
+
|
171
|
+
instance.upload('local', 'remote')
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'when --debug is set' do
|
175
|
+
before(:each) do
|
176
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
177
|
+
Harrison.const_set("DEBUG", true)
|
178
|
+
end
|
179
|
+
|
180
|
+
after(:each) do
|
181
|
+
Harrison.send(:remove_const, "DEBUG") if Harrison.const_defined?("DEBUG")
|
182
|
+
Harrison.const_set("DEBUG", false)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should output what is being uploaded and to where' do
|
186
|
+
expect(@mock_scp).to receive(:upload!).with('local', 'remote')
|
187
|
+
|
188
|
+
output = capture(:stdout) do
|
189
|
+
instance.upload('local', 'remote')
|
190
|
+
end
|
191
|
+
|
192
|
+
expect(output).to include('scp-up', 'local', 'remote')
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe '#close' do
|
198
|
+
it 'should delegate to net-ssh' do
|
199
|
+
expect(@mock_ssh_conn).to receive(:close)
|
200
|
+
|
201
|
+
instance.close
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'when using a proxy command' do
|
205
|
+
before(:each) do
|
206
|
+
@proxy = double(:ssh_proxy)
|
207
|
+
instance.instance_variable_set(:@proxy, @proxy)
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should send TERM to the proxy command' do
|
211
|
+
allow(@mock_ssh_conn).to receive(:transport).and_return(double(:transport, socket: double(:socket, pid: 100000)))
|
212
|
+
|
213
|
+
expect(Process).to receive(:kill).with("TERM", 100000)
|
214
|
+
expect(@mock_ssh_conn).to receive(:close)
|
215
|
+
|
216
|
+
instance.close
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe '#closed?' do
|
222
|
+
it 'should delegate to net-ssh' do
|
223
|
+
expect(@mock_ssh_conn).to receive(:closed?)
|
224
|
+
|
225
|
+
instance.closed?
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe '#desc' do
|
230
|
+
it 'should include the host connected to' do
|
231
|
+
expect(instance.desc).to include('test.example.com')
|
232
|
+
end
|
233
|
+
|
234
|
+
context 'when using a proxy host' do
|
235
|
+
before(:each) do
|
236
|
+
@proxy = double(:ssh_proxy, command_line: "ssh proxy.example.com arg1 arg2")
|
237
|
+
instance.instance_variable_set(:@proxy, @proxy)
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should include the proxy host' do
|
241
|
+
expect(instance.desc).to include('proxy.example.com')
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
5
245
|
end
|
data/spec/unit/harrison_spec.rb
CHANGED
@@ -16,38 +16,38 @@ describe Harrison do
|
|
16
16
|
describe '.invoke' do
|
17
17
|
it 'should exit when no args are passed' do
|
18
18
|
output = capture(:stderr) do
|
19
|
-
lambda { Harrison.invoke([]) }.
|
19
|
+
expect(lambda { Harrison.invoke([]) }).to exit_with_code(1)
|
20
20
|
end
|
21
21
|
|
22
|
-
output.
|
22
|
+
expect(output).to include('no', 'command', 'given')
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'should output base help when first arg is --help' do
|
26
26
|
output = capture(:stdout) do
|
27
|
-
lambda { Harrison.invoke(['--help']) }.
|
27
|
+
expect(lambda { Harrison.invoke(['--help']) }).to exit_with_code(0)
|
28
28
|
end
|
29
29
|
|
30
|
-
output.
|
30
|
+
expect(output).to include('options', 'debug', 'help')
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'should look for a Harrisonfile' do
|
34
34
|
expect(Harrison).to receive(:find_harrisonfile).and_return(harrisonfile_fixture_path)
|
35
35
|
|
36
36
|
output = capture(:stderr) do
|
37
|
-
lambda { Harrison.invoke(['test']) }.
|
37
|
+
expect(lambda { Harrison.invoke(['test']) }).to exit_with_code(1)
|
38
38
|
end
|
39
39
|
|
40
|
-
output.
|
40
|
+
expect(output).to include('unrecognized', 'command', 'test')
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'should complain if unable to find a Harrisonfile' do
|
44
44
|
expect(Harrison).to receive(:find_harrisonfile).and_return(nil)
|
45
45
|
|
46
46
|
output = capture(:stderr) do
|
47
|
-
lambda { Harrison.invoke(['test']) }.
|
47
|
+
expect(lambda { Harrison.invoke(['test']) }).to exit_with_code(1)
|
48
48
|
end
|
49
49
|
|
50
|
-
output.
|
50
|
+
expect(output).to include('could', 'not', 'find', 'harrisonfile')
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
@@ -56,7 +56,7 @@ describe Harrison do
|
|
56
56
|
mock_config = double(:config)
|
57
57
|
expect(Harrison::Config).to receive(:new).and_return(mock_config)
|
58
58
|
|
59
|
-
Harrison.config.
|
59
|
+
expect(Harrison.config).to be mock_config
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'should return existing Harrison::Config if defined' do
|
@@ -64,7 +64,7 @@ describe Harrison do
|
|
64
64
|
Harrison.class_variable_set(:@@config, mock_config)
|
65
65
|
expect(Harrison::Config).to_not receive(:new)
|
66
66
|
|
67
|
-
Harrison.config.
|
67
|
+
expect(Harrison.config).to be mock_config
|
68
68
|
end
|
69
69
|
|
70
70
|
it 'should yield config if given a block' do
|
@@ -124,13 +124,13 @@ describe Harrison do
|
|
124
124
|
it 'should find a Harrisonfile if it exists in pwd' do
|
125
125
|
expect(Dir).to receive(:pwd).and_return(fixture_path)
|
126
126
|
|
127
|
-
Harrison.send(:find_harrisonfile).
|
127
|
+
expect(Harrison.send(:find_harrisonfile)).to eq(harrisonfile_fixture_path)
|
128
128
|
end
|
129
129
|
|
130
130
|
it 'should find a Harrisonfile if it exists in parent of pwd' do
|
131
131
|
expect(Dir).to receive(:pwd).and_return(fixture_path + '/nested')
|
132
132
|
|
133
|
-
Harrison.send(:find_harrisonfile).
|
133
|
+
expect(Harrison.send(:find_harrisonfile)).to eq(harrisonfile_fixture_path)
|
134
134
|
end
|
135
135
|
|
136
136
|
it 'should return nil if there is no Harrisonfile in tree' do
|
@@ -140,7 +140,7 @@ describe Harrison do
|
|
140
140
|
allow(File).to receive(:expand_path).and_call_original
|
141
141
|
allow(File).to receive(:expand_path).with("..", File.dirname(__FILE__)).and_return(File.dirname(__FILE__))
|
142
142
|
|
143
|
-
Harrison.send(:find_harrisonfile).
|
143
|
+
expect(Harrison.send(:find_harrisonfile)).to be_nil
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
@@ -150,7 +150,7 @@ describe Harrison do
|
|
150
150
|
Harrison.send(:eval_script, fixture_path + '/eval_script.rb')
|
151
151
|
end
|
152
152
|
|
153
|
-
output.
|
153
|
+
expect(output).to eq("this file was eval-led\n")
|
154
154
|
end
|
155
155
|
end
|
156
156
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: harrison
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-08-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: trollop
|
@@ -96,17 +96,17 @@ dependencies:
|
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
97
97
|
none: false
|
98
98
|
requirements:
|
99
|
-
- -
|
99
|
+
- - ~>
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version: '0'
|
101
|
+
version: '3.0'
|
102
102
|
type: :development
|
103
103
|
prerelease: false
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
|
-
- -
|
107
|
+
- - ~>
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: '0'
|
109
|
+
version: '3.0'
|
110
110
|
- !ruby/object:Gem::Dependency
|
111
111
|
name: debugger
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,11 +149,13 @@ extra_rdoc_files: []
|
|
149
149
|
files:
|
150
150
|
- .gitignore
|
151
151
|
- .rspec
|
152
|
+
- .travis.yml
|
153
|
+
- CHANGELOG
|
152
154
|
- Gemfile
|
155
|
+
- IDEAS
|
153
156
|
- LICENSE.txt
|
154
157
|
- README.md
|
155
158
|
- Rakefile
|
156
|
-
- TODO
|
157
159
|
- bin/harrison
|
158
160
|
- harrison.gemspec
|
159
161
|
- lib/harrison.rb
|
@@ -193,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
193
195
|
version: '0'
|
194
196
|
segments:
|
195
197
|
- 0
|
196
|
-
hash:
|
198
|
+
hash: -1749031995180641465
|
197
199
|
requirements: []
|
198
200
|
rubyforge_project:
|
199
201
|
rubygems_version: 1.8.29
|
data/TODO
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
Bugs:
|
2
|
-
-----
|
3
|
-
[ ] Test that artifact exists before doing anything for deploy task.
|
4
|
-
|
5
|
-
In Progress:
|
6
|
-
------------
|
7
|
-
[ ] Unit Testing
|
8
|
-
|
9
|
-
Definitely:
|
10
|
-
-----------
|
11
|
-
[ ] Rollback action.
|
12
|
-
[ ] Error handling (with automatic rollback/undo)
|
13
|
-
[ ] Further de-compose actions so that specific parts can be easily overridden.
|
14
|
-
[ ] When deploying via a proxy, cache artifact on proxy before deploying to hosts.
|
15
|
-
[ ] Banner text in --help output.
|
16
|
-
[ ] Improve unit testing, try to minimize coupling to implementation details.
|
17
|
-
[ ] Include branch name in artifact file name.
|
18
|
-
|
19
|
-
Maybe:
|
20
|
-
------
|
21
|
-
[ ] Allow more elaborate hosts config, e.g.: h.hosts = [ { host: '10.16.18.207', tags: %w(util migrate) } ]
|
22
|
-
[ ] Some kind of --dry-run option.
|
23
|
-
[ ] Allow deploy_via to include alternate user/connection options.
|
24
|
-
[ ] Rename "releases" => "builds" (and "deploys" => "releases"?)
|
25
|
-
[ ] Upload artifact to all hosts before the rest of the deployment process begins.
|
26
|
-
[ ] --force option for deploy task to overwrite an existing release
|