rouster 0.42 → 0.53
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/.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
|