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.
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
- @log.info(sprintf('dir(%s) output[%s], try with sudo', dir, raw)) unless self.uses_sudo?
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
- @log.info(sprintf('is_file?(%s) output[%s], try with sudo', file, raw)) unless self.uses_sudo?
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
- @log.warn('is_in_file? scp option not implemented yet')
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 other flavors - this will work on RHEL and OSX
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
- p = Rouster.new(:name => 'app', :verbosity => 0)
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\[4\]/, res)
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