harrison 0.0.1 → 0.1.0
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/.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
|
+
[](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
|