rouster 0.42 → 0.53
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +19 -0
- data/Vagrantfile +4 -1
- data/lib/rouster.rb +153 -31
- data/lib/rouster/deltas.rb +224 -58
- data/lib/rouster/puppet.rb +118 -25
- data/lib/rouster/testing.rb +202 -42
- data/lib/rouster/tests.rb +23 -11
- data/path_helper.rb +3 -4
- data/rouster.gemspec +2 -1
- data/test/functional/deltas/test_get_crontab.rb +24 -1
- data/test/functional/deltas/test_get_groups.rb +74 -2
- data/test/functional/deltas/test_get_packages.rb +43 -5
- data/test/functional/deltas/test_get_ports.rb +26 -1
- data/test/functional/deltas/test_get_services.rb +37 -4
- data/test/functional/deltas/test_get_users.rb +35 -2
- data/test/functional/puppet/test_facter.rb +41 -1
- data/test/functional/test_caching.rb +5 -1
- data/test/functional/test_dirs.rb +25 -0
- data/test/functional/test_get.rb +10 -6
- data/test/functional/test_new.rb +10 -9
- data/test/functional/test_put.rb +8 -10
- data/test/functional/test_restart.rb +1 -2
- data/test/functional/test_run.rb +2 -3
- data/test/functional/test_validate_file.rb +30 -0
- data/test/puppet/test_apply.rb +5 -5
- data/test/puppet/test_roles.rb +16 -3
- data/test/unit/test_parse_ls_string.rb +24 -0
- data/test/unit/testing/test_validate_file.rb +39 -46
- data/test/unit/testing/test_validate_port.rb +98 -0
- metadata +33 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76526d9f9fbaca98b3ea2bfad51a769f67df6647
|
4
|
+
data.tar.gz: 6f9bc1a95bec7be30e00eccb1cd0ca28c6d24a72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 345f5b6e4ef69a8a023e4bf3aebbb517e69a0ff7eee1f95b330bb1c373becfa1c231931f5c189099ec44acc355efe5a23b1c07cf057dc9cd6a297c7651343391
|
7
|
+
data.tar.gz: 92be5fcb79d74870bea301a722a539459656a89b9a7c139dba1c02e5fcbff5d12207f6dab4720952be5e58ab6606e27781885dfea29ffef1a117de757e713e56
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -24,6 +24,10 @@ task :examples do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
task :reek do
|
28
|
+
sh "reek lib/**/*.rb"
|
29
|
+
end
|
30
|
+
|
27
31
|
Rake::TestTask.new do |t|
|
28
32
|
t.name = 'test'
|
29
33
|
t.libs << 'lib'
|
@@ -44,3 +48,18 @@ Rake::TestTask.new do |t|
|
|
44
48
|
t.test_files = FileList['test/functional/**/test_*.rb']
|
45
49
|
t.verbose = true
|
46
50
|
end
|
51
|
+
|
52
|
+
Rake::TestTask.new do |t|
|
53
|
+
t.name = 'deltas'
|
54
|
+
t.libs << 'lib'
|
55
|
+
t.test_files = FileList['test/functional/deltas/test_*.rb']
|
56
|
+
t.verbose = true
|
57
|
+
end
|
58
|
+
|
59
|
+
Rake::TestTask.new do |t|
|
60
|
+
t.name = 'puppet'
|
61
|
+
t.libs << 'lib'
|
62
|
+
t.test_files = FileList['test/puppet/test*.rb']
|
63
|
+
t.verbose = true
|
64
|
+
end
|
65
|
+
|
data/Vagrantfile
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# stripped down example piab Vagrantfile for rouster
|
2
2
|
|
3
|
+
#box_url = 'http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210.box'
|
4
|
+
#box_name = 'ubuntu12'
|
3
5
|
box_url = 'http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210.box'
|
4
6
|
box_name = 'centos6'
|
7
|
+
|
5
8
|
boxes = [:ppm, :app]
|
6
9
|
|
7
10
|
Vagrant::Config.run do |config|
|
@@ -11,7 +14,7 @@ Vagrant::Config.run do |config|
|
|
11
14
|
worker.vm.box = box_name
|
12
15
|
worker.vm.box_url = box_url
|
13
16
|
worker.vm.host_name = box.to_s
|
14
|
-
worker.vm.network :hostonly, sprintf('10.0.1.%s', rand(
|
17
|
+
worker.vm.network :hostonly, sprintf('10.0.1.%s', rand(253).to_i + 2)
|
15
18
|
worker.ssh.forward_agent = true
|
16
19
|
|
17
20
|
if box.to_s.eql?('ppm') and File.directory?('../puppet')
|
data/lib/rouster.rb
CHANGED
@@ -11,9 +11,10 @@ require 'rouster/tests'
|
|
11
11
|
class Rouster
|
12
12
|
|
13
13
|
# sporadically updated version number
|
14
|
-
VERSION = 0.
|
14
|
+
VERSION = 0.53
|
15
15
|
|
16
16
|
# custom exceptions -- what else do we want them to include/do?
|
17
|
+
class ArgumentError < StandardError; end # thrown by methods that take parameters from users
|
17
18
|
class FileTransferError < StandardError; end # thrown by get() and put()
|
18
19
|
class InternalError < StandardError; end # thrown by most (if not all) Rouster methods
|
19
20
|
class ExternalError < StandardError; end # thrown when external dependencies do not respond as expected
|
@@ -60,7 +61,8 @@ class Rouster
|
|
60
61
|
@deltas = Hash.new
|
61
62
|
|
62
63
|
@exitcode = nil
|
63
|
-
@
|
64
|
+
@ssh = nil # hash containing the SSH connection object
|
65
|
+
@ssh_info = nil # hash containing connection information
|
64
66
|
|
65
67
|
# set up logging
|
66
68
|
require 'log4r/config'
|
@@ -75,17 +77,23 @@ class Rouster
|
|
75
77
|
raise InternalError.new(sprintf('specified Vagrantfile [%s] does not exist', @vagrantfile))
|
76
78
|
end
|
77
79
|
|
78
|
-
raise InternalError.new() if @name.nil?
|
80
|
+
raise InternalError.new('name of Vagrant VM not specified') if @name.nil?
|
79
81
|
|
80
82
|
return if opts[:unittest].eql?(true) # quick return if we're a unit test
|
81
83
|
|
84
|
+
# this is breaking test/functional/test_caching.rb test_ssh_caching (if the VM was not running when the test started)
|
85
|
+
# it slows down object instantiation, but is a good test to ensure the machine name is valid..
|
82
86
|
begin
|
83
87
|
self.status()
|
84
|
-
rescue Rouster::LocalExecutionError
|
85
|
-
raise InternalError.new()
|
88
|
+
rescue Rouster::LocalExecutionError => e
|
89
|
+
raise InternalError.new(sprintf('caught non-0 exitcode from status(): %s', e.message))
|
86
90
|
end
|
87
91
|
|
88
|
-
|
92
|
+
begin
|
93
|
+
self._run('which vagrant')
|
94
|
+
rescue
|
95
|
+
raise ExternalError.new('vagrant not found in path')
|
96
|
+
end
|
89
97
|
|
90
98
|
@log.debug('SSH key discovery and viability tests..')
|
91
99
|
if @sshkey.nil?
|
@@ -107,12 +115,7 @@ class Rouster
|
|
107
115
|
end
|
108
116
|
|
109
117
|
if @sshtunnel
|
110
|
-
|
111
|
-
@log.info(sprintf('upping machine[%s] in order to open SSH tunnel', @name))
|
112
|
-
self.up()
|
113
|
-
end
|
114
|
-
|
115
|
-
self.connect_ssh_tunnel()
|
118
|
+
self.up()
|
116
119
|
end
|
117
120
|
|
118
121
|
@log.info('Rouster object successfully instantiated')
|
@@ -142,7 +145,7 @@ class Rouster
|
|
142
145
|
# if :sshtunnel is passed to the object during instantiation, the tunnel is created here as well
|
143
146
|
def up
|
144
147
|
@log.info('up()')
|
145
|
-
self.
|
148
|
+
self.vagrant(sprintf('up %s', @name))
|
146
149
|
|
147
150
|
@ssh_info = nil # in case the ssh-info has changed, a la destroy/rebuild
|
148
151
|
self.connect_ssh_tunnel() if @sshtunnel
|
@@ -153,7 +156,8 @@ class Rouster
|
|
153
156
|
# runs `vagrant destroy <name>` from the Vagrantfile path
|
154
157
|
def destroy
|
155
158
|
@log.info('destroy()')
|
156
|
-
|
159
|
+
disconnect_ssh_tunnel
|
160
|
+
self.vagrant(sprintf('destroy -f %s', @name))
|
157
161
|
end
|
158
162
|
|
159
163
|
##
|
@@ -174,11 +178,14 @@ class Rouster
|
|
174
178
|
end
|
175
179
|
|
176
180
|
@log.info('status()')
|
177
|
-
self.
|
181
|
+
self.vagrant(sprintf('status %s', @name))
|
178
182
|
|
179
183
|
# else case here is handled by non-0 exit code
|
180
|
-
if self.get_output().match(/^#{@name}\s*(.*\s?\w+)\s(.+)$/)
|
181
|
-
# $1 =
|
184
|
+
if self.get_output().match(/^#{@name}\s*(.*\s?\w+)\s\((.+)\)$/)
|
185
|
+
# vagrant 1.2+, $1 = status, $2 = provider
|
186
|
+
status = $1
|
187
|
+
elsif self.get_output().match(/^#{@name}\s+(.+)$/)
|
188
|
+
# vagrant 1.2-, $1 = status
|
182
189
|
status = $1
|
183
190
|
end
|
184
191
|
|
@@ -198,7 +205,8 @@ class Rouster
|
|
198
205
|
# runs `vagrant suspend <name>` from the Vagrantfile path
|
199
206
|
def suspend
|
200
207
|
@log.info('suspend()')
|
201
|
-
|
208
|
+
disconnect_ssh_tunnel()
|
209
|
+
self.vagrant(sprintf('suspend %s', @name))
|
202
210
|
end
|
203
211
|
|
204
212
|
## internal methods
|
@@ -293,6 +301,80 @@ class Rouster
|
|
293
301
|
res
|
294
302
|
end
|
295
303
|
|
304
|
+
##
|
305
|
+
# sandbox_available?
|
306
|
+
#
|
307
|
+
# returns true or false after attempting to find out if the sandbox
|
308
|
+
# subcommand is available
|
309
|
+
def sandbox_available?
|
310
|
+
if @cache.has_key?(:sandbox_available?)
|
311
|
+
@log.debug(sprintf('using cached sandbox_available?[%s]', @cache[:sandbox_available?]))
|
312
|
+
return @cache[:sandbox_available?]
|
313
|
+
end
|
314
|
+
|
315
|
+
@log.info('sandbox_available()')
|
316
|
+
self._run(sprintf('cd %s; vagrant', File.dirname(@vagrantfile))) # calling 'vagrant' without parameters to determine available faces
|
317
|
+
|
318
|
+
sandbox_available = false
|
319
|
+
if self.get_output().match(/^\s+sandbox$/)
|
320
|
+
sandbox_available = true
|
321
|
+
end
|
322
|
+
|
323
|
+
@cache[:sandbox_available?] = sandbox_available
|
324
|
+
@log.debug(sprintf('caching sandbox_available?[%s]', @cache[:sandbox_available?]))
|
325
|
+
@log.error('sandbox support is not available, please install the "sahara" gem first, https://github.com/jedi4ever/sahara') unless sandbox_available
|
326
|
+
|
327
|
+
return sandbox_available
|
328
|
+
end
|
329
|
+
|
330
|
+
##
|
331
|
+
# sandbox_on
|
332
|
+
# runs `vagrant sandbox on` from the Vagrantfile path
|
333
|
+
def sandbox_on
|
334
|
+
if self.sandbox_available?
|
335
|
+
return self.vagrant(sprintf('sandbox on %s', @name))
|
336
|
+
else
|
337
|
+
raise ExternalError.new('sandbox plugin not installed')
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
##
|
342
|
+
# sandbox_off
|
343
|
+
# runs `vagrant sandbox off` from the Vagrantfile path
|
344
|
+
def sandbox_off
|
345
|
+
if self.sandbox_available?
|
346
|
+
return self.vagrant(sprintf('sandbox off %s', @name))
|
347
|
+
else
|
348
|
+
raise ExternalError.new('sandbox plugin not installed')
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
##
|
353
|
+
# sandbox_rollback
|
354
|
+
# runs `vagrant sandbox rollback` from the Vagrantfile path
|
355
|
+
def sandbox_rollback
|
356
|
+
if self.sandbox_available?
|
357
|
+
self.disconnect_ssh_tunnel
|
358
|
+
self.vagrant(sprintf('sandbox rollback %s', @name))
|
359
|
+
self.connect_ssh_tunnel
|
360
|
+
else
|
361
|
+
raise ExternalError.new('sandbox plugin not installed')
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
##
|
366
|
+
# sandbox_commit
|
367
|
+
# runs `vagrant sandbox commit` from the Vagrantfile path
|
368
|
+
def sandbox_commit
|
369
|
+
if self.sandbox_available?
|
370
|
+
self.disconnect_ssh_tunnel
|
371
|
+
self.vagrant(sprintf('sandbox commit %s', @name))
|
372
|
+
self.connect_ssh_tunnel
|
373
|
+
else
|
374
|
+
raise ExternalError.new('sandbox plugin not installed')
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
296
378
|
##
|
297
379
|
# get_ssh_info
|
298
380
|
#
|
@@ -304,10 +386,11 @@ class Rouster
|
|
304
386
|
h = Hash.new()
|
305
387
|
|
306
388
|
if @ssh_info.class.eql?(Hash)
|
389
|
+
@log.debug('using cached SSH info')
|
307
390
|
h = @ssh_info
|
308
391
|
else
|
309
392
|
|
310
|
-
res = self.
|
393
|
+
res = self.vagrant(sprintf('ssh-config %s', @name))
|
311
394
|
|
312
395
|
res.split("\n").each do |line|
|
313
396
|
if line.match(/HostName (.*?)$/)
|
@@ -317,8 +400,14 @@ class Rouster
|
|
317
400
|
elsif line.match(/Port (\d*?)$/)
|
318
401
|
h[:ssh_port] = $1
|
319
402
|
elsif line.match(/IdentityFile (.*?)$/)
|
320
|
-
|
321
|
-
|
403
|
+
key = $1
|
404
|
+
unless @sshkey.eql?(key)
|
405
|
+
h[:identity_file] = key
|
406
|
+
else
|
407
|
+
@log.info(sprintf('using specified key[%s] instead of discovered key[%s]', @sshkey, key))
|
408
|
+
h[:identity_file] = @sshkey
|
409
|
+
end
|
410
|
+
|
322
411
|
end
|
323
412
|
end
|
324
413
|
|
@@ -337,16 +426,29 @@ class Rouster
|
|
337
426
|
def connect_ssh_tunnel
|
338
427
|
@log.debug('opening SSH tunnel..')
|
339
428
|
|
340
|
-
|
429
|
+
status = self.status()
|
430
|
+
if status.eql?('running')
|
341
431
|
self.get_ssh_info()
|
342
432
|
@ssh = Net::SSH.start(@ssh_info[:hostname], @ssh_info[:user], :port => @ssh_info[:ssh_port], :keys => [@sshkey], :paranoid => false)
|
343
433
|
else
|
344
|
-
raise InternalError.new('VM is not running, unable open SSH tunnel')
|
434
|
+
raise InternalError.new(sprintf('VM is not running[%s], unable open SSH tunnel', status))
|
345
435
|
end
|
346
436
|
|
347
437
|
@ssh
|
348
438
|
end
|
349
439
|
|
440
|
+
##
|
441
|
+
# disconnect_ssh_tunnel
|
442
|
+
#
|
443
|
+
# shuts down the persistent Net::SSH tunnel
|
444
|
+
#
|
445
|
+
def disconnect_ssh_tunnel
|
446
|
+
@log.debug('closing SSH tunnel..')
|
447
|
+
|
448
|
+
@ssh.shutdown! unless @ssh.nil?
|
449
|
+
@ssh = nil
|
450
|
+
end
|
451
|
+
|
350
452
|
##
|
351
453
|
# os_type
|
352
454
|
#
|
@@ -367,6 +469,8 @@ class Rouster
|
|
367
469
|
res =:solaris
|
368
470
|
when /Ubuntu/i
|
369
471
|
res = :ubuntu
|
472
|
+
when /Debian/i
|
473
|
+
res = :debian
|
370
474
|
else
|
371
475
|
if self.is_file?('/etc/redhat-release')
|
372
476
|
res = :redhat
|
@@ -389,20 +493,21 @@ class Rouster
|
|
389
493
|
# * [local_file] - full or relative path (based on $PWD) of file to download to
|
390
494
|
#
|
391
495
|
# if no local_file is specified, will be downloaded to $PWD with the same shortname as it had in the VM
|
496
|
+
#
|
497
|
+
# returns true on successful download, false if the file DNE and raises a FileTransferError.. well, you know
|
392
498
|
def get(remote_file, local_file=nil)
|
393
499
|
# TODO what happens when we pass a wildcard as remote_file?
|
394
500
|
|
395
501
|
local_file = local_file.nil? ? File.basename(remote_file) : local_file
|
396
502
|
@log.debug(sprintf('scp from VM[%s] to host[%s]', remote_file, local_file))
|
397
503
|
|
398
|
-
# TODO should we do a self.file?(remote_file) test before trying to download?
|
399
|
-
|
400
504
|
begin
|
401
505
|
@ssh.scp.download!(remote_file, local_file)
|
402
506
|
rescue => e
|
403
507
|
raise FileTransferError.new(sprintf('unable to get[%s], exception[%s]', remote_file, e.message()))
|
404
508
|
end
|
405
509
|
|
510
|
+
return true
|
406
511
|
end
|
407
512
|
|
408
513
|
##
|
@@ -471,7 +576,7 @@ class Rouster
|
|
471
576
|
case os_type
|
472
577
|
when :osx
|
473
578
|
self.run('shutdown -r now')
|
474
|
-
when :redhat, :ubuntu
|
579
|
+
when :redhat, :ubuntu, :debian
|
475
580
|
self.run('/sbin/shutdown -rf now')
|
476
581
|
when :solaris
|
477
582
|
self.run('shutdown -y -i5 -g0')
|
@@ -479,18 +584,20 @@ class Rouster
|
|
479
584
|
raise InternalError.new(sprintf('unsupported OS[%s]', @ostype))
|
480
585
|
end
|
481
586
|
|
587
|
+
@ssh, @ssh_info = nil # severing the SSH tunnel, getting ready in case this box is brought back up on a different port
|
588
|
+
|
482
589
|
if wait
|
483
590
|
inc = wait.to_i / 10
|
484
591
|
0..wait.each do |e|
|
485
592
|
@log.debug(sprintf('waiting for reboot: round[%s], step[%s], total[%s]', e, inc, wait))
|
486
|
-
true if self.is_available_via_ssh?()
|
593
|
+
return true if self.is_available_via_ssh?()
|
487
594
|
sleep inc
|
488
595
|
end
|
489
596
|
|
490
|
-
false
|
597
|
+
return false
|
491
598
|
end
|
492
599
|
|
493
|
-
|
600
|
+
return true
|
494
601
|
end
|
495
602
|
|
496
603
|
##
|
@@ -504,7 +611,7 @@ class Rouster
|
|
504
611
|
# parameters
|
505
612
|
# * <command> - command to be run
|
506
613
|
def _run(command)
|
507
|
-
tmp_file = sprintf('/tmp/rouster.%s.%s', Time.now.to_i, $$)
|
614
|
+
tmp_file = sprintf('/tmp/rouster-cmd_output.%s.%s', Time.now.to_i, $$)
|
508
615
|
cmd = sprintf('%s > %s 2> %s', command, tmp_file, tmp_file) # this is a holdover from Salesforce::Vagrant, can we use '2&>1' here?
|
509
616
|
res = `#{cmd}` # what does this actually hold?
|
510
617
|
|
@@ -524,6 +631,22 @@ class Rouster
|
|
524
631
|
output
|
525
632
|
end
|
526
633
|
|
634
|
+
##
|
635
|
+
# vagrant
|
636
|
+
#
|
637
|
+
# abstraction layer to call vagrant faces
|
638
|
+
#
|
639
|
+
# parameters
|
640
|
+
# * <face> - vagrant face to call (include arguments)
|
641
|
+
def vagrant(face)
|
642
|
+
if self.is_passthrough?
|
643
|
+
@log.info(sprintf('calling [vagrant %s] on a passthrough host is a noop', face))
|
644
|
+
return nil
|
645
|
+
end
|
646
|
+
|
647
|
+
self._run(sprintf('cd %s; vagrant %s', File.dirname(@vagrantfile), face))
|
648
|
+
end
|
649
|
+
|
527
650
|
##
|
528
651
|
# get_output
|
529
652
|
#
|
@@ -561,7 +684,6 @@ class Rouster
|
|
561
684
|
# * [filename] - filename you are looking for
|
562
685
|
# * [levels] - number of directory levels to examine, default is 10
|
563
686
|
def traverse_up(startdir=Dir.pwd, filename=nil, levels=10)
|
564
|
-
# TODO not sure this signature is exactly right..
|
565
687
|
raise InternalError.new('must specify a filename') if filename.nil?
|
566
688
|
|
567
689
|
@log.debug(sprintf('traverse_up() looking for [%s] in [%s], up to [%s] levels', filename, startdir, levels)) unless @log.nil?
|
data/lib/rouster/deltas.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
require sprintf('%s/../../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
|
2
2
|
|
3
|
-
# deltas.rb - get information about groups, packages, services and users inside a Vagrant VM
|
3
|
+
# deltas.rb - get information about crontabs, groups, packages, ports, services and users inside a Vagrant VM
|
4
4
|
require 'rouster'
|
5
5
|
require 'rouster/tests'
|
6
6
|
|
7
|
-
# TODO use @cache_timeout to invalidate data cached here
|
8
|
-
|
9
7
|
class Rouster
|
10
8
|
|
11
9
|
##
|
@@ -33,13 +31,22 @@ class Rouster
|
|
33
31
|
def get_crontab(user='root', cache=true)
|
34
32
|
|
35
33
|
if cache and self.deltas[:crontab].class.eql?(Hash)
|
36
|
-
|
34
|
+
|
35
|
+
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:crontab]) > self.cache_timeout
|
36
|
+
@log.debug(sprintf('invalidating [crontab] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:crontab]), self.cache_timeout))
|
37
|
+
self.deltas.delete(:crontab)
|
38
|
+
end
|
39
|
+
|
40
|
+
if self.deltas.has_key?(:crontab) and self.deltas[:crontab].has_key?(user)
|
41
|
+
@log.debug(sprintf('using cached [crontab] from [%s]', self.cache[:crontab]))
|
37
42
|
return self.deltas[:crontab][user]
|
43
|
+
elsif self.deltas.has_key?(:crontab) and user.eql?('*')
|
44
|
+
@log.debug(sprintf('using cached [crontab] from [%s]', self.cache[:crontab]))
|
45
|
+
return self.deltas[:crontab]
|
38
46
|
else
|
39
47
|
# noop fallthrough to gather data to cache
|
40
48
|
end
|
41
|
-
|
42
|
-
return self.deltas[:crontab]
|
49
|
+
|
43
50
|
end
|
44
51
|
|
45
52
|
i = 0
|
@@ -62,6 +69,7 @@ class Rouster
|
|
62
69
|
end
|
63
70
|
|
64
71
|
raw.split("\n").each do |line|
|
72
|
+
next if line.match(/^#/)
|
65
73
|
elements = line.split("\s")
|
66
74
|
|
67
75
|
res[u] ||= Hash.new
|
@@ -72,13 +80,15 @@ class Rouster
|
|
72
80
|
res[u][i][:dom] = elements[2]
|
73
81
|
res[u][i][:mon] = elements[3]
|
74
82
|
res[u][i][:dow] = elements[4]
|
75
|
-
res[u][i][:command] = elements[5..elements.size].join(
|
83
|
+
res[u][i][:command] = elements[5..elements.size].join(' ')
|
76
84
|
end
|
77
85
|
|
78
86
|
i += 1
|
79
87
|
end
|
80
88
|
|
81
89
|
if cache
|
90
|
+
@log.debug(sprintf('caching [crontab] at [%s]', Time.now.asctime))
|
91
|
+
|
82
92
|
if ! user.eql?('*')
|
83
93
|
self.deltas[:crontab] ||= Hash.new
|
84
94
|
self.deltas[:crontab][user] ||= Hash.new
|
@@ -87,6 +97,9 @@ class Rouster
|
|
87
97
|
self.deltas[:crontab] ||= Hash.new
|
88
98
|
self.deltas[:crontab] = res
|
89
99
|
end
|
100
|
+
|
101
|
+
self.cache[:crontab] = Time.now.to_i
|
102
|
+
|
90
103
|
end
|
91
104
|
|
92
105
|
return user.eql?('*') ? res : res[user]
|
@@ -105,9 +118,19 @@ class Rouster
|
|
105
118
|
#
|
106
119
|
# parameters
|
107
120
|
# * [cache] - boolean controlling whether data retrieved/parsed is cached, defaults to true
|
108
|
-
|
121
|
+
# * [deep] - boolean controlling whether get_users() is called in order to correctly populate res[group][:users]
|
122
|
+
def get_groups(cache=true, deep=true)
|
123
|
+
|
109
124
|
if cache and ! self.deltas[:groups].nil?
|
110
|
-
|
125
|
+
|
126
|
+
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:groups]) > self.cache_timeout
|
127
|
+
@log.debug(sprintf('invalidating [groups] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:groups]), self.cache_timeout))
|
128
|
+
self.deltas.delete(:groups)
|
129
|
+
else
|
130
|
+
@log.debug(sprintf('using cached [groups] from [%s]', self.cache[:groups]))
|
131
|
+
return self.deltas[:groups]
|
132
|
+
end
|
133
|
+
|
111
134
|
end
|
112
135
|
|
113
136
|
res = Hash.new()
|
@@ -121,6 +144,8 @@ class Rouster
|
|
121
144
|
|
122
145
|
group = data[0]
|
123
146
|
gid = data[2]
|
147
|
+
|
148
|
+
# this works in some cases, deep functionality picks up the others
|
124
149
|
users = data[3].nil? ? ['NONE'] : data[3].split(',')
|
125
150
|
|
126
151
|
res[group] = Hash.new() # i miss autovivification
|
@@ -128,11 +153,45 @@ class Rouster
|
|
128
153
|
res[group][:users] = users
|
129
154
|
end
|
130
155
|
|
156
|
+
groups = res
|
157
|
+
|
158
|
+
if deep
|
159
|
+
users = self.get_users(cache)
|
160
|
+
|
161
|
+
known_valid_gids = groups.keys.map { |g| groups[g][:gid] } # no need to calculate this in every loop
|
162
|
+
|
163
|
+
# TODO better, much better -- since the number of users/groups is finite and usually small, this is a low priority
|
164
|
+
users.each_key do |user|
|
165
|
+
# iterate over each user to get their gid
|
166
|
+
gid = users[user][:gid]
|
167
|
+
|
168
|
+
unless known_valid_gids.member?(gid)
|
169
|
+
@log.warn(sprintf('found user[%s] with unknown GID[%s], known GIDs[%s]', user, gid, known_valid_gids))
|
170
|
+
next
|
171
|
+
end
|
172
|
+
|
173
|
+
## do this more efficiently
|
174
|
+
groups.each_key do |group|
|
175
|
+
# iterate over each group to find the matching gid
|
176
|
+
if gid.eql?(groups[group][:gid])
|
177
|
+
if groups[group][:users].eql?(['NONE'])
|
178
|
+
groups[group][:users] = []
|
179
|
+
end
|
180
|
+
groups[group][:users] << user unless groups[group][:users].member?(user)
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
131
188
|
if cache
|
132
|
-
|
189
|
+
@log.debug(sprintf('caching [groups] at [%s]', Time.now.asctime))
|
190
|
+
self.deltas[:groups] = groups
|
191
|
+
self.cache[:groups] = Time.now.to_i
|
133
192
|
end
|
134
193
|
|
135
|
-
|
194
|
+
groups
|
136
195
|
end
|
137
196
|
|
138
197
|
##
|
@@ -158,7 +217,15 @@ class Rouster
|
|
158
217
|
# raises InternalError if unsupported operating system
|
159
218
|
def get_packages(cache=true, deep=true)
|
160
219
|
if cache and ! self.deltas[:packages].nil?
|
161
|
-
|
220
|
+
|
221
|
+
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:packages]) > self.cache_timeout
|
222
|
+
@log.debug(sprintf('invalidating [packages] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:packages]), self.cache_timeout))
|
223
|
+
self.deltas.delete(:packages)
|
224
|
+
else
|
225
|
+
@log.debug(sprintf('using cached [packages] from [%s]', self.cache[:packages]))
|
226
|
+
return self.deltas[:packages]
|
227
|
+
end
|
228
|
+
|
162
229
|
end
|
163
230
|
|
164
231
|
res = Hash.new()
|
@@ -169,53 +236,62 @@ class Rouster
|
|
169
236
|
|
170
237
|
raw = self.run('pkgutil --pkgs')
|
171
238
|
raw.split("\n").each do |line|
|
239
|
+
version = '?'
|
172
240
|
|
173
241
|
if deep
|
174
242
|
# can get install time, volume and location as well
|
175
243
|
local_res = self.run(sprintf('pkgutil --pkg-info=%s', line))
|
176
|
-
|
177
|
-
else
|
178
|
-
local = '?'
|
244
|
+
version = $1 if local_res.match(/version\:\s+(.*?)$/)
|
179
245
|
end
|
180
246
|
|
181
|
-
res[line] =
|
247
|
+
res[line] = version
|
182
248
|
end
|
183
249
|
|
184
250
|
elsif os.eql?(:solaris)
|
185
251
|
raw = self.run('pkginfo')
|
186
252
|
raw.split("\n").each do |line|
|
187
253
|
next if line.match(/(.*?)\s+(.*?)\s(.*)$/).empty?
|
254
|
+
name = $2
|
255
|
+
version = '?'
|
188
256
|
|
189
257
|
if deep
|
190
|
-
local_res = self.run(sprintf('pkginfo -l %s',
|
191
|
-
|
192
|
-
else
|
193
|
-
local = '?'
|
258
|
+
local_res = self.run(sprintf('pkginfo -l %s', name))
|
259
|
+
version = $1 if local_res.match(/VERSION\:\s+(.*?)$/i)
|
194
260
|
end
|
195
261
|
|
196
|
-
res[
|
262
|
+
res[name] = version
|
197
263
|
end
|
198
264
|
|
199
|
-
elsif os.eql?(:ubuntu)
|
265
|
+
elsif os.eql?(:ubuntu) or os.eql?(:debian)
|
200
266
|
raw = self.run('dpkg --get-selections')
|
201
267
|
raw.split("\n").each do |line|
|
202
268
|
next if line.match(/^(.*?)\s/).nil?
|
269
|
+
name = $1
|
270
|
+
version = '?'
|
203
271
|
|
204
272
|
if deep
|
205
|
-
local_res = self.run(sprintf('dpkg -s %s',
|
206
|
-
|
207
|
-
else
|
208
|
-
local = '?'
|
273
|
+
local_res = self.run(sprintf('dpkg -s %s', name))
|
274
|
+
version = $1 if local_res.match(/Version\:\s(.*?)$/)
|
209
275
|
end
|
210
276
|
|
211
|
-
res[
|
277
|
+
res[name] = version
|
212
278
|
end
|
213
279
|
|
214
280
|
elsif os.eql?(:redhat)
|
215
281
|
raw = self.run('rpm -qa')
|
216
282
|
raw.split("\n").each do |line|
|
217
283
|
next if line.match(/(.*?)-(\d*\..*)/).nil? # ht petersen.allen
|
218
|
-
|
284
|
+
#next if line.match(/(.*)-(\d+\.\d+.*)/).nil? # another alternate, but still not perfect
|
285
|
+
name = $1
|
286
|
+
version = '?' # we could use $2, but we don't trust it
|
287
|
+
|
288
|
+
if deep
|
289
|
+
local_res = self.run(sprintf('rpm -qi %s', line))
|
290
|
+
name = $1 if local_res.match(/Name\s+:\s(\S*)/)
|
291
|
+
version = $1 if local_res.match(/Version\s+:\s(\S*)/)
|
292
|
+
end
|
293
|
+
|
294
|
+
res[name] = version
|
219
295
|
end
|
220
296
|
|
221
297
|
else
|
@@ -223,7 +299,9 @@ class Rouster
|
|
223
299
|
end
|
224
300
|
|
225
301
|
if cache
|
302
|
+
@log.debug(sprintf('caching [packages] at [%s]', Time.now.asctime))
|
226
303
|
self.deltas[:packages] = res
|
304
|
+
self.cache[:packages] = Time.now.to_i
|
227
305
|
end
|
228
306
|
|
229
307
|
res
|
@@ -245,7 +323,7 @@ class Rouster
|
|
245
323
|
# * [cache] - boolean controlling whether data retrieved/parsed is cached, defaults to true
|
246
324
|
#
|
247
325
|
# supported OS
|
248
|
-
# * RedHat - runs `netstat -ln`
|
326
|
+
# * RedHat, Ubuntu - runs `netstat -ln`
|
249
327
|
#
|
250
328
|
# raises InternalError if unsupported operating system
|
251
329
|
def get_ports(cache=false)
|
@@ -253,13 +331,19 @@ class Rouster
|
|
253
331
|
# TODO improve ipv6 support
|
254
332
|
|
255
333
|
if cache and ! self.deltas[:ports].nil?
|
256
|
-
|
334
|
+
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:ports]) > self.cache_timeout
|
335
|
+
@log.debug(sprintf('invalidating [ports] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:ports]), self.cache_timeout))
|
336
|
+
self.deltas.delete(:ports)
|
337
|
+
else
|
338
|
+
@log.debug(sprintf('using cached [ports] from [%s]', self.cache[:ports]))
|
339
|
+
return self.deltas[:ports]
|
340
|
+
end
|
257
341
|
end
|
258
342
|
|
259
343
|
res = Hash.new()
|
260
344
|
os = self.os_type()
|
261
345
|
|
262
|
-
if os.eql?(:redhat)
|
346
|
+
if os.eql?(:redhat) or os.eql?(:ubuntu) or os.eql?(:debian)
|
263
347
|
|
264
348
|
raw = self.run('netstat -ln')
|
265
349
|
|
@@ -278,13 +362,14 @@ class Rouster
|
|
278
362
|
res[protocol][port][:address][address] = state
|
279
363
|
|
280
364
|
end
|
281
|
-
|
282
365
|
else
|
283
366
|
raise InternalError.new(sprintf('unable to get port information from VM operating system[%s]', os))
|
284
367
|
end
|
285
368
|
|
286
369
|
if cache
|
370
|
+
@log.debug(sprintf('caching [ports] at [%s]', Time.now.asctime))
|
287
371
|
self.deltas[:ports] = res
|
372
|
+
self.cache[:ports] = Time.now.to_i
|
288
373
|
end
|
289
374
|
|
290
375
|
res
|
@@ -295,11 +380,12 @@ class Rouster
|
|
295
380
|
#
|
296
381
|
# runs an OS appropriate command to gather service information, returns hash:
|
297
382
|
# {
|
298
|
-
# serviceN => mode # running|stopped|unsure
|
383
|
+
# serviceN => mode # exists|installed|operational|running|stopped|unsure
|
299
384
|
# }
|
300
385
|
#
|
301
386
|
# parameters
|
302
|
-
# * [cache]
|
387
|
+
# * [cache] - boolean controlling whether data retrieved/parsed is cached, defaults to true
|
388
|
+
# * [humanize] - boolean controlling whether data retrieved is massaged into simplified names or returned mostly as retrieved
|
303
389
|
#
|
304
390
|
# supported OS
|
305
391
|
# * OSX - runs `launchctl list`
|
@@ -307,15 +393,27 @@ class Rouster
|
|
307
393
|
# * Solaris - runs `svcs`
|
308
394
|
# * Ubuntu - runs `service --status-all`
|
309
395
|
#
|
310
|
-
#
|
311
|
-
|
396
|
+
# notes
|
397
|
+
# * raises InternalError if unsupported operating system
|
398
|
+
# * OSX, Solaris and Ubuntu/Debian will only return running|stopped|unsure, the exists|installed|operational modes are RHEL/CentOS only
|
399
|
+
def get_services(cache=true, humanize=true)
|
312
400
|
if cache and ! self.deltas[:services].nil?
|
313
|
-
|
401
|
+
|
402
|
+
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:services]) > self.cache_timeout
|
403
|
+
@log.debug(sprintf('invalidating [services] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:services]), self.cache_timeout))
|
404
|
+
self.deltas.delete(:services)
|
405
|
+
else
|
406
|
+
@log.debug(sprintf('using cached [services] from [%s]', self.cache[:services]))
|
407
|
+
return self.deltas[:services]
|
408
|
+
end
|
409
|
+
|
314
410
|
end
|
315
411
|
|
316
412
|
res = Hash.new()
|
413
|
+
os = self.os_type
|
317
414
|
|
318
|
-
|
415
|
+
allowed_modes = %w(exists installed operational running stopped unsure)
|
416
|
+
failover_mode = 'unsure'
|
319
417
|
|
320
418
|
if os.eql?(:osx)
|
321
419
|
|
@@ -326,10 +424,12 @@ class Rouster
|
|
326
424
|
service = $2
|
327
425
|
mode = $1
|
328
426
|
|
329
|
-
if
|
330
|
-
mode
|
331
|
-
|
332
|
-
|
427
|
+
if humanize # should we do this with a .freeze instead?
|
428
|
+
if mode.match(/^\d/)
|
429
|
+
mode = 'running'
|
430
|
+
else
|
431
|
+
mode = 'stopped'
|
432
|
+
end
|
333
433
|
end
|
334
434
|
|
335
435
|
res[service] = mode
|
@@ -337,26 +437,36 @@ class Rouster
|
|
337
437
|
|
338
438
|
elsif os.eql?(:solaris)
|
339
439
|
|
340
|
-
raw = self.run('svcs')
|
440
|
+
raw = self.run('svcs -a')
|
341
441
|
raw.split("\n").each do |line|
|
342
442
|
next if line.match(/(.*?)\s+(?:.*?)\s+(.*?)$/).nil?
|
343
443
|
|
344
444
|
service = $2
|
345
445
|
mode = $1
|
346
446
|
|
347
|
-
if
|
348
|
-
mode
|
349
|
-
|
350
|
-
mode
|
351
|
-
|
352
|
-
mode
|
447
|
+
if humanize
|
448
|
+
if mode.match(/^online/)
|
449
|
+
mode = 'running'
|
450
|
+
elsif mode.match(/^legacy_run/)
|
451
|
+
mode = 'running'
|
452
|
+
elsif mode.match(/^disabled/)
|
453
|
+
mode = 'stopped'
|
454
|
+
end
|
455
|
+
|
456
|
+
if service.match(/^svc:\/.*\/(.*?):.*/)
|
457
|
+
# turning 'svc:/network/cswpuppetd:default' into 'cswpuppetd'
|
458
|
+
service = $1
|
459
|
+
elsif service.match(/^lrc:\/.*?\/.*\/(.*)/)
|
460
|
+
# turning 'lrc:/etc/rcS_d/S50sk98Sol' into 'S50sk98Sol'
|
461
|
+
service = $1
|
462
|
+
end
|
353
463
|
end
|
354
464
|
|
355
465
|
res[service] = mode
|
356
466
|
|
357
467
|
end
|
358
468
|
|
359
|
-
elsif os.eql?(:ubuntu)
|
469
|
+
elsif os.eql?(:ubuntu) or os.eql?(:debian)
|
360
470
|
|
361
471
|
raw = self.run('service --status-all 2>&1')
|
362
472
|
raw.split("\n").each do |line|
|
@@ -364,9 +474,11 @@ class Rouster
|
|
364
474
|
mode = $1
|
365
475
|
service = $2
|
366
476
|
|
367
|
-
|
368
|
-
|
369
|
-
|
477
|
+
if humanize
|
478
|
+
mode = 'stopped' if mode.match('-')
|
479
|
+
mode = 'running' if mode.match('\+')
|
480
|
+
mode = 'unsure' if mode.match('\?')
|
481
|
+
end
|
370
482
|
|
371
483
|
res[service] = mode
|
372
484
|
end
|
@@ -375,9 +487,51 @@ class Rouster
|
|
375
487
|
|
376
488
|
raw = self.run('/sbin/service --status-all')
|
377
489
|
raw.split("\n").each do |line|
|
378
|
-
|
379
|
-
|
380
|
-
|
490
|
+
|
491
|
+
if humanize
|
492
|
+
|
493
|
+
if line.match(/^(\w+?)\sis\s(.*)$/)
|
494
|
+
# <service> is <state>
|
495
|
+
res[$1] = $2
|
496
|
+
|
497
|
+
if $2.match(/^not/)
|
498
|
+
# this catches 'Kdump is not operational'
|
499
|
+
res[$1] = 'stopped'
|
500
|
+
end
|
501
|
+
|
502
|
+
elsif line.match(/^(\w+?)\s\(pid.*?\)\sis\s(\w+)$/)
|
503
|
+
# <service> (pid <pid> [pid]) is <state>...
|
504
|
+
res[$1] = $2
|
505
|
+
elsif line.match(/^(\w+?)\sis\s(\w+)\.*$/) # not sure this is actually needed
|
506
|
+
@log.debug('triggered supposedly unnecessary regex')
|
507
|
+
# <service> is <state>. whatever
|
508
|
+
res[$1] = $2
|
509
|
+
elsif line.match(/^(\w+?)\:.*?(\w+)$/)
|
510
|
+
# <service>: whatever <state>
|
511
|
+
res[$1] = $2
|
512
|
+
elsif line.match(/^(\w+)\s(\w+).*$/)
|
513
|
+
# <process> <state> whatever
|
514
|
+
res[$1] = $2
|
515
|
+
else
|
516
|
+
# original regex implementation, if we didn't match anything else, failover to this
|
517
|
+
next if line.match(/^([^\s:]*).*\s(\w*)(?:\.?){3}$/).nil?
|
518
|
+
res[$1] = $2
|
519
|
+
end
|
520
|
+
|
521
|
+
else
|
522
|
+
next if line.match(/^([^\s:]*).*\s(\w*)(?:\.?){3}$/).nil?
|
523
|
+
res[$1] = $2
|
524
|
+
end
|
525
|
+
|
526
|
+
end
|
527
|
+
|
528
|
+
# issue #63 handling
|
529
|
+
if humanize
|
530
|
+
res.each_pair do |k,v|
|
531
|
+
next if allowed_modes.member?(v)
|
532
|
+
@log.debug(sprintf('replacing service[%s] status of [%s] with [%s] for uniformity', k, v, failover_mode))
|
533
|
+
res[k] = failover_mode
|
534
|
+
end
|
381
535
|
end
|
382
536
|
|
383
537
|
else
|
@@ -385,7 +539,9 @@ class Rouster
|
|
385
539
|
end
|
386
540
|
|
387
541
|
if cache
|
542
|
+
@log.debug(sprintf('caching [services] at [%s]', Time.now.asctime))
|
388
543
|
self.deltas[:services] = res
|
544
|
+
self.cache[:services] = Time.now.to_i
|
389
545
|
end
|
390
546
|
|
391
547
|
res
|
@@ -408,7 +564,15 @@ class Rouster
|
|
408
564
|
# * [cache] - boolean controlling whether data retrieved/parsed is cached, defaults to true
|
409
565
|
def get_users(cache=true)
|
410
566
|
if cache and ! self.deltas[:users].nil?
|
411
|
-
|
567
|
+
|
568
|
+
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:users]) > self.cache_timeout
|
569
|
+
@log.debug(sprintf('invalidating [users] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:users]), self.cache_timeout))
|
570
|
+
self.deltas.delete(:users)
|
571
|
+
else
|
572
|
+
@log.debug(sprintf('using cached [users] from [%s]', self.cache[:users]))
|
573
|
+
return self.deltas[:users]
|
574
|
+
end
|
575
|
+
|
412
576
|
end
|
413
577
|
|
414
578
|
res = Hash.new()
|
@@ -430,10 +594,12 @@ class Rouster
|
|
430
594
|
end
|
431
595
|
|
432
596
|
if cache
|
597
|
+
@log.debug(sprintf('caching [users] at [%s]', Time.now.asctime))
|
433
598
|
self.deltas[:users] = res
|
599
|
+
self.cache[:users] = Time.now.to_i
|
434
600
|
end
|
435
601
|
|
436
602
|
res
|
437
603
|
end
|
438
604
|
|
439
|
-
end
|
605
|
+
end
|