rouster 0.53 → 0.57
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.
- checksums.yaml +4 -4
- data/README.md +5 -2
- data/examples/passthrough.rb +71 -0
- data/lib/rouster.rb +194 -242
- data/lib/rouster/deltas.rb +44 -39
- data/lib/rouster/puppet.rb +82 -21
- data/lib/rouster/testing.rb +129 -19
- data/lib/rouster/tests.rb +34 -6
- data/lib/rouster/vagrant.rb +242 -0
- data/test/basic.rb +4 -1
- data/test/functional/deltas/test_get_crontab.rb +22 -1
- data/test/functional/deltas/test_get_services.rb +6 -0
- data/test/functional/test_inspect.rb +1 -1
- data/test/functional/test_is_file.rb +17 -1
- data/test/functional/test_new.rb +185 -16
- data/test/functional/test_passthroughs.rb +94 -0
- data/test/functional/test_put.rb +2 -2
- data/test/functional/test_validate_file.rb +55 -3
- data/test/puppet/test_apply.rb +7 -5
- data/test/unit/puppet/test_get_puppet_star.rb +27 -4
- data/test/unit/test_new.rb +23 -0
- data/test/unit/test_parse_ls_string.rb +24 -0
- data/test/unit/testing/test_validate_cron.rb +78 -0
- metadata +24 -20
data/lib/rouster/tests.rb
CHANGED
@@ -44,7 +44,7 @@ class Rouster
|
|
44
44
|
if raw.match(/No such file or directory/)
|
45
45
|
res = nil
|
46
46
|
elsif raw.match(/Permission denied/)
|
47
|
-
@
|
47
|
+
@logger.info(sprintf('dir(%s) output[%s], try with sudo', dir, raw)) unless self.uses_sudo?
|
48
48
|
res = nil
|
49
49
|
else
|
50
50
|
res = parse_ls_string(raw)
|
@@ -120,7 +120,7 @@ class Rouster
|
|
120
120
|
end
|
121
121
|
|
122
122
|
if raw.match(/No such file or directory/)
|
123
|
-
@
|
123
|
+
@logger.info(sprintf('is_file?(%s) output[%s], try with sudo', file, raw)) unless self.uses_sudo?
|
124
124
|
res = nil
|
125
125
|
elsif raw.match(/Permission denied/)
|
126
126
|
res = nil
|
@@ -264,7 +264,7 @@ class Rouster
|
|
264
264
|
|
265
265
|
if scp
|
266
266
|
# download the file to a temporary directory
|
267
|
-
@
|
267
|
+
@logger.warn('is_in_file? scp option not implemented yet')
|
268
268
|
end
|
269
269
|
|
270
270
|
begin
|
@@ -373,8 +373,8 @@ class Rouster
|
|
373
373
|
# * RedHat
|
374
374
|
# * Ubuntu
|
375
375
|
def is_process_running?(name)
|
376
|
-
# TODO support
|
377
|
-
# TODO do better validation than just grepping for a matching filename
|
376
|
+
# TODO support Solaris
|
377
|
+
# TODO do better validation than just grepping for a matching filename, start with removing 'grep' from output
|
378
378
|
begin
|
379
379
|
|
380
380
|
os = self.os_type()
|
@@ -467,6 +467,25 @@ class Rouster
|
|
467
467
|
end
|
468
468
|
end
|
469
469
|
|
470
|
+
##
|
471
|
+
# is_symlink?
|
472
|
+
#
|
473
|
+
# uses file() to return boolean indicating whether parameter passed is a symlink
|
474
|
+
#
|
475
|
+
# parameters
|
476
|
+
# * <file> - path of filename to validate
|
477
|
+
def is_symlink?(file)
|
478
|
+
res = nil
|
479
|
+
|
480
|
+
begin
|
481
|
+
res = self.file(file)
|
482
|
+
rescue => e
|
483
|
+
return false
|
484
|
+
end
|
485
|
+
|
486
|
+
res.class.eql?(Hash) ? res[:symlink?] : false
|
487
|
+
end
|
488
|
+
|
470
489
|
##
|
471
490
|
# is_user?
|
472
491
|
#
|
@@ -579,17 +598,26 @@ class Rouster
|
|
579
598
|
end
|
580
599
|
|
581
600
|
res[:mode] = mode
|
582
|
-
res[:name] = tokens[-1] # TODO better here: this does not support files/dirs with spaces
|
583
601
|
res[:owner] = tokens[2]
|
584
602
|
res[:group] = tokens[3]
|
585
603
|
res[:size] = tokens[4]
|
586
604
|
|
587
605
|
res[:directory?] = tokens[0][0].chr.eql?('d')
|
588
606
|
res[:file?] = ! res[:directory?]
|
607
|
+
res[:symlink?] = tokens[0][0].chr.eql?('l')
|
589
608
|
res[:executable?] = [ tokens[0][3].chr.eql?('x'), tokens[0][6].chr.eql?('x'), tokens[0][9].chr.eql?('x') || tokens[0][9].chr.eql?('t') ]
|
590
609
|
res[:writeable?] = [ tokens[0][2].chr.eql?('w'), tokens[0][5].chr.eql?('w'), tokens[0][8].chr.eql?('w') ]
|
591
610
|
res[:readable?] = [ tokens[0][1].chr.eql?('r'), tokens[0][4].chr.eql?('r'), tokens[0][7].chr.eql?('r') ]
|
592
611
|
|
612
|
+
# TODO better here: this does not support files/dirs with spaces
|
613
|
+
if res[:symlink?]
|
614
|
+
# not sure if we should only be adding this value if we're a symlink, or adding it to all results and just using nil if not a link
|
615
|
+
res[:target] = tokens[-1]
|
616
|
+
res[:name] = tokens[-3]
|
617
|
+
else
|
618
|
+
res[:name] = tokens[-1]
|
619
|
+
end
|
620
|
+
|
593
621
|
res
|
594
622
|
end
|
595
623
|
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require sprintf('%s/../../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
|
2
|
+
|
3
|
+
## Vagrant specific (and related) methods
|
4
|
+
|
5
|
+
class Rouster
|
6
|
+
|
7
|
+
##
|
8
|
+
# vagrant
|
9
|
+
#
|
10
|
+
# abstraction layer to call vagrant faces
|
11
|
+
#
|
12
|
+
# parameters
|
13
|
+
# * <face> - vagrant face to call (include arguments)
|
14
|
+
def vagrant(face, sleep_time=10)
|
15
|
+
if self.is_passthrough?
|
16
|
+
@logger.warn(sprintf('calling [vagrant %s] on a passthrough host is a noop', face))
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
|
20
|
+
unless @vagrant_concurrency.eql?(true)
|
21
|
+
# TODO don't (ab|re)use variables
|
22
|
+
0.upto(@retries) do |try|
|
23
|
+
break if self.is_vagrant_running?().eql?(false)
|
24
|
+
|
25
|
+
sleep sleep_time # TODO log a message?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
0.upto(@retries) do |try| # TODO should really be doing this with 'retry', but i think this code is actually cleaner
|
30
|
+
begin
|
31
|
+
return self._run(sprintf('cd %s; vagrant %s', File.dirname(@vagrantfile), face))
|
32
|
+
rescue
|
33
|
+
@logger.error(sprintf('failed vagrant command[%s], attempt[%s/%s]', face, try, retries)) if self.retries > 0
|
34
|
+
sleep sleep_time
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
raise InternalError.new(sprintf('failed to execute [%s], exitcode[%s], output[%s]', face, self.exitcode, self.get_output()))
|
39
|
+
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# up
|
45
|
+
# runs `vagrant up` from the Vagrantfile path
|
46
|
+
# if :sshtunnel is passed to the object during instantiation, the tunnel is created here as well
|
47
|
+
def up
|
48
|
+
@logger.info('up()')
|
49
|
+
self.vagrant(sprintf('up %s', @name))
|
50
|
+
|
51
|
+
@ssh_info = nil # in case the ssh-info has changed, a la destroy/rebuild
|
52
|
+
self.connect_ssh_tunnel() if @sshtunnel
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# destroy
|
57
|
+
# runs `vagrant destroy <name>` from the Vagrantfile path
|
58
|
+
def destroy
|
59
|
+
@logger.info('destroy()')
|
60
|
+
disconnect_ssh_tunnel
|
61
|
+
self.vagrant(sprintf('destroy -f %s', @name))
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# status
|
66
|
+
#
|
67
|
+
# runs `vagrant status <name>` from the Vagrantfile path
|
68
|
+
# parses the status and provider out of output, but only status is returned
|
69
|
+
def status
|
70
|
+
status = nil
|
71
|
+
|
72
|
+
if @cache_timeout
|
73
|
+
if @cache.has_key?(:status)
|
74
|
+
if (Time.now.to_i - @cache[:status][:time]) < @cache_timeout
|
75
|
+
@logger.debug(sprintf('using cached status[%s] from [%s]', @cache[:status][:status], @cache[:status][:time]))
|
76
|
+
return @cache[:status][:status]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@logger.info('status()')
|
82
|
+
self.vagrant(sprintf('status %s', @name))
|
83
|
+
|
84
|
+
# else case here (both for nil/non-matching output) is handled by non-0 exit code
|
85
|
+
output = self.get_output()
|
86
|
+
if output.nil?
|
87
|
+
if self.is_passthrough?()
|
88
|
+
status = 'running'
|
89
|
+
end
|
90
|
+
elsif output.match(/^#{@name}\s*(.*\s?\w+)\s\((.+)\)$/)
|
91
|
+
# vagrant 1.2+, $1 = status, $2 = provider
|
92
|
+
status = $1
|
93
|
+
elsif output.match(/^#{@name}\s+(.+)$/)
|
94
|
+
# vagrant 1.2-, $1 = status
|
95
|
+
status = $1
|
96
|
+
end
|
97
|
+
|
98
|
+
if @cache_timeout
|
99
|
+
@cache[:status] = Hash.new unless @cache[:status].class.eql?(Hash)
|
100
|
+
@cache[:status][:time] = Time.now.to_i
|
101
|
+
@cache[:status][:status] = status
|
102
|
+
@logger.debug(sprintf('caching status[%s] at [%s]', @cache[:status][:status], @cache[:status][:time]))
|
103
|
+
end
|
104
|
+
|
105
|
+
return status
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# suspend
|
110
|
+
#
|
111
|
+
# runs `vagrant suspend <name>` from the Vagrantfile path
|
112
|
+
def suspend
|
113
|
+
@logger.info('suspend()')
|
114
|
+
self.vagrant(sprintf('suspend %s', @name))
|
115
|
+
disconnect_ssh_tunnel() unless self.is_passthrough?()
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# is_vagrant_running?()
|
120
|
+
#
|
121
|
+
# returns true|false if a vagrant process is running on the host machine
|
122
|
+
#
|
123
|
+
# meant to be used to prevent race-y conditions when interacting with VirtualBox (potentially others, haven't tested)
|
124
|
+
def is_vagrant_running?
|
125
|
+
res = false
|
126
|
+
|
127
|
+
begin
|
128
|
+
# TODO would like to get the 2 -v greps into a single call..
|
129
|
+
raw = self._run("ps -ef | grep -v 'grep' | grep -v 'ssh' | grep '#{self.vagrantbinary}'")
|
130
|
+
res = true
|
131
|
+
rescue
|
132
|
+
end
|
133
|
+
|
134
|
+
@logger.debug(sprintf('is_vagrant_running?[%s]', res))
|
135
|
+
res
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# sandbox_available?
|
140
|
+
#
|
141
|
+
# returns true or false after attempting to find out if the sandbox
|
142
|
+
# subcommand is available
|
143
|
+
def sandbox_available?
|
144
|
+
if self.is_passthrough?
|
145
|
+
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
146
|
+
return nil
|
147
|
+
end
|
148
|
+
|
149
|
+
if @cache.has_key?(:sandbox_available?)
|
150
|
+
@logger.debug(sprintf('using cached sandbox_available?[%s]', @cache[:sandbox_available?]))
|
151
|
+
return @cache[:sandbox_available?]
|
152
|
+
end
|
153
|
+
|
154
|
+
@logger.info('sandbox_available()')
|
155
|
+
begin
|
156
|
+
# at some point, vagrant changed its behavior on exit code here, so rescuing
|
157
|
+
self._run(sprintf('cd %s; vagrant', File.dirname(@vagrantfile))) # calling 'vagrant' without parameters to determine available faces
|
158
|
+
rescue
|
159
|
+
end
|
160
|
+
|
161
|
+
sandbox_available = false
|
162
|
+
if self.get_output().match(/^\s+sandbox$/)
|
163
|
+
sandbox_available = true
|
164
|
+
end
|
165
|
+
|
166
|
+
@cache[:sandbox_available?] = sandbox_available
|
167
|
+
@logger.debug(sprintf('caching sandbox_available?[%s]', @cache[:sandbox_available?]))
|
168
|
+
@logger.error('sandbox support is not available, please install the "sahara" gem first, https://github.com/jedi4ever/sahara') unless sandbox_available
|
169
|
+
|
170
|
+
return sandbox_available
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# sandbox_on
|
175
|
+
# runs `vagrant sandbox on` from the Vagrantfile path
|
176
|
+
def sandbox_on
|
177
|
+
if self.is_passthrough?
|
178
|
+
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
179
|
+
return nil
|
180
|
+
end
|
181
|
+
|
182
|
+
if self.sandbox_available?
|
183
|
+
return self.vagrant(sprintf('sandbox on %s', @name))
|
184
|
+
else
|
185
|
+
raise ExternalError.new('sandbox plugin not installed')
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# sandbox_off
|
191
|
+
# runs `vagrant sandbox off` from the Vagrantfile path
|
192
|
+
def sandbox_off
|
193
|
+
if self.is_passthrough?
|
194
|
+
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
195
|
+
return nil
|
196
|
+
end
|
197
|
+
|
198
|
+
if self.sandbox_available?
|
199
|
+
return self.vagrant(sprintf('sandbox off %s', @name))
|
200
|
+
else
|
201
|
+
raise ExternalError.new('sandbox plugin not installed')
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# sandbox_rollback
|
207
|
+
# runs `vagrant sandbox rollback` from the Vagrantfile path
|
208
|
+
def sandbox_rollback
|
209
|
+
if self.is_passthrough?
|
210
|
+
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
211
|
+
return nil
|
212
|
+
end
|
213
|
+
|
214
|
+
if self.sandbox_available?
|
215
|
+
self.disconnect_ssh_tunnel
|
216
|
+
self.vagrant(sprintf('sandbox rollback %s', @name))
|
217
|
+
self.connect_ssh_tunnel
|
218
|
+
else
|
219
|
+
raise ExternalError.new('sandbox plugin not installed')
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# sandbox_commit
|
225
|
+
# runs `vagrant sandbox commit` from the Vagrantfile path
|
226
|
+
def sandbox_commit
|
227
|
+
if self.is_passthrough?
|
228
|
+
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
229
|
+
return nil
|
230
|
+
end
|
231
|
+
|
232
|
+
if self.sandbox_available?
|
233
|
+
self.disconnect_ssh_tunnel
|
234
|
+
self.vagrant(sprintf('sandbox commit %s', @name))
|
235
|
+
self.connect_ssh_tunnel
|
236
|
+
else
|
237
|
+
raise ExternalError.new('sandbox plugin not installed')
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
end
|
data/test/basic.rb
CHANGED
@@ -5,6 +5,9 @@ require 'rouster/puppet'
|
|
5
5
|
require 'rouster/testing'
|
6
6
|
require 'rouster/tests'
|
7
7
|
|
8
|
-
|
8
|
+
#a = Rouster.new(:name => 'app', :verbosity => 1, :vagrantfile => '../piab/Vagrantfile', :retries => 3)
|
9
|
+
#p = Rouster.new(:name => 'ppm', :verbosity => 1, :vagrantfile => '../piab/Vagrantfile')
|
10
|
+
#r = Rouster.new(:name => 'app', :verbosity => 1, :vagrantfile => 'Vagrantfile')
|
11
|
+
l = Rouster.new(:name => 'local', :passthrough => { :type => :local }, :verbosity => 3)
|
9
12
|
|
10
13
|
p 'DBGZ' if nil?
|
@@ -115,8 +115,29 @@ class TestDeltasGetCrontab < Test::Unit::TestCase
|
|
115
115
|
|
116
116
|
end
|
117
117
|
|
118
|
+
def test_unhappy_duplicate_entries
|
119
|
+
|
120
|
+
res = nil
|
121
|
+
|
122
|
+
# do this in a saner way? crontab call is overwriting existing records
|
123
|
+
user = 'puppet'
|
124
|
+
tmp = sprintf('/tmp/rouster.tmp.crontab.%s.%s.%s', user, Time.now.to_i, $$)
|
125
|
+
@app.run("echo '0 0 * * * echo #{user}' > #{tmp}")
|
126
|
+
@app.run("echo '5 5 * * * echo #{user}' >> #{tmp}")
|
127
|
+
@app.run("crontab -u #{user} #{tmp}")
|
128
|
+
|
129
|
+
assert_nothing_raised do
|
130
|
+
res = @app.get_crontab('puppet')
|
131
|
+
end
|
132
|
+
|
133
|
+
assert_equal(Hash, res.class)
|
134
|
+
assert(res.has_key?('echo puppet'))
|
135
|
+
assert(res.has_key?('echo puppet-duplicate.55***echopuppet'))
|
136
|
+
|
137
|
+
end
|
138
|
+
|
118
139
|
def teardown
|
119
140
|
@app = nil
|
120
141
|
end
|
121
142
|
|
122
|
-
end
|
143
|
+
end
|
@@ -31,6 +31,12 @@ class TestDeltasGetServices < Test::Unit::TestCase
|
|
31
31
|
assert(@allowed_states.member?(res[k]))
|
32
32
|
end
|
33
33
|
|
34
|
+
# this isn't the best validation, but does prove a point - no nil keys/values
|
35
|
+
assert_nothing_raised do
|
36
|
+
res.keys.sort
|
37
|
+
res.values.sort
|
38
|
+
end
|
39
|
+
|
34
40
|
end
|
35
41
|
|
36
42
|
def test_happy_path_caching
|
@@ -21,7 +21,7 @@ class TestInspect < Test::Unit::TestCase
|
|
21
21
|
assert_match(/status/, res)
|
22
22
|
assert_match(/sudo\[true\]/, res)
|
23
23
|
assert_match(/vagrantfile/, res)
|
24
|
-
assert_match(/verbosity\[
|
24
|
+
assert_match(/verbosity console\[3\] \/ log\[2/, res)
|
25
25
|
end
|
26
26
|
|
27
27
|
def teardown
|
@@ -22,8 +22,9 @@ class TestIsFile < Test::Unit::TestCase
|
|
22
22
|
@file_other_rwx = sprintf('%s/other', @dir_tmp)
|
23
23
|
@file_644 = sprintf('%s/sixfourfour', @dir_tmp)
|
24
24
|
@file_755 = sprintf('%s/sevenfivefive', @dir_tmp)
|
25
|
+
@symlink = sprintf('%s/symlinking', @dir_tmp)
|
25
26
|
|
26
|
-
@files = [@file_user_rwx, @file_group_rwx, @file_other_rwx, @file_644, @file_755]
|
27
|
+
@files = [@file_user_rwx, @file_group_rwx, @file_other_rwx, @file_644, @file_755, @symlink]
|
27
28
|
end
|
28
29
|
|
29
30
|
def test_user
|
@@ -41,6 +42,8 @@ class TestIsFile < Test::Unit::TestCase
|
|
41
42
|
assert_equal(false, @app.is_readable?(@file_user_rwx, 'o'))
|
42
43
|
assert_equal(false, @app.is_writeable?(@file_user_rwx, 'o'))
|
43
44
|
assert_equal(false, @app.is_executable?(@file_user_rwx, 'o'))
|
45
|
+
|
46
|
+
assert_equal(false, @app.is_symlink?(@file_755))
|
44
47
|
end
|
45
48
|
|
46
49
|
def test_group
|
@@ -58,6 +61,8 @@ class TestIsFile < Test::Unit::TestCase
|
|
58
61
|
assert_equal(false, @app.is_readable?(@file_group_rwx, 'o'))
|
59
62
|
assert_equal(false, @app.is_writeable?(@file_group_rwx, 'o'))
|
60
63
|
assert_equal(false, @app.is_executable?(@file_group_rwx, 'o'))
|
64
|
+
|
65
|
+
assert_equal(false, @app.is_symlink?(@file_755))
|
61
66
|
end
|
62
67
|
|
63
68
|
def test_other
|
@@ -76,6 +81,7 @@ class TestIsFile < Test::Unit::TestCase
|
|
76
81
|
assert_equal(true, @app.is_writeable?(@file_other_rwx, 'o'))
|
77
82
|
assert_equal(true, @app.is_executable?(@file_other_rwx, 'o'))
|
78
83
|
|
84
|
+
assert_equal(false, @app.is_symlink?(@file_755))
|
79
85
|
end
|
80
86
|
|
81
87
|
def test_644
|
@@ -93,6 +99,8 @@ class TestIsFile < Test::Unit::TestCase
|
|
93
99
|
assert_equal(true, @app.is_readable?(@file_644, 'o'))
|
94
100
|
assert_equal(false, @app.is_writeable?(@file_644, 'o'))
|
95
101
|
assert_equal(false, @app.is_executable?(@file_644, 'o'))
|
102
|
+
|
103
|
+
assert_equal(false, @app.is_symlink?(@file_755))
|
96
104
|
end
|
97
105
|
|
98
106
|
def test_755
|
@@ -110,6 +118,14 @@ class TestIsFile < Test::Unit::TestCase
|
|
110
118
|
assert_equal(true, @app.is_readable?(@file_755, 'o'))
|
111
119
|
assert_equal(false, @app.is_writeable?(@file_755, 'o'))
|
112
120
|
assert_equal(true, @app.is_executable?(@file_755, 'o'))
|
121
|
+
|
122
|
+
assert_equal(false, @app.is_symlink?(@file_755))
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_symlink
|
126
|
+
@app.run("ln -s /etc/hosts #{@symlink}")
|
127
|
+
|
128
|
+
assert_equal(true, @app.is_symlink?(@symlink))
|
113
129
|
end
|
114
130
|
|
115
131
|
def teardown
|