rouster 0.42 → 0.53

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5d74bfb451922e3b85675d75df8eab08adcbb8f1
4
- data.tar.gz: 8e16766740fb82932308c8e9ca1ff44e2ece3c70
3
+ metadata.gz: 76526d9f9fbaca98b3ea2bfad51a769f67df6647
4
+ data.tar.gz: 6f9bc1a95bec7be30e00eccb1cd0ca28c6d24a72
5
5
  SHA512:
6
- metadata.gz: b5149f6ecef16db649b445602383708dc7a2f39b30dc8475edf408286dbe4c60105828af134d1a177e1c5a960e5e4515786798fe31fff351c9c1de604af1847c
7
- data.tar.gz: c4b51b717eab079455f3cac15eab60d93c49403b8efe59c1886b0f300e8231143110140d99d3f62bac439cd792768386a5ddbc541a3f2aaa402f3e937feecbc9
6
+ metadata.gz: 345f5b6e4ef69a8a023e4bf3aebbb517e69a0ff7eee1f95b330bb1c373becfa1c231931f5c189099ec44acc355efe5a23b1c07cf057dc9cd6a297c7651343391
7
+ data.tar.gz: 92be5fcb79d74870bea301a722a539459656a89b9a7c139dba1c02e5fcbff5d12207f6dab4720952be5e58ab6606e27781885dfea29ffef1a117de757e713e56
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ doc/*
4
4
  *.swp
5
5
  *tmp*
6
6
  *.gem
7
+ .rakeTasks
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(254))
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.42
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
- @ssh_info = nil # will be hash containing connection information
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
- ## TODO ensure 'vagrant' is in path
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
- unless self.status.eql?('running')
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._run(sprintf('cd %s; vagrant up %s', File.dirname(@vagrantfile), @name))
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
- self._run(sprintf('cd %s; vagrant destroy -f %s', File.dirname(@vagrantfile), @name))
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._run(sprintf('cd %s; vagrant status %s', File.dirname(@vagrantfile), @name))
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 = name, $2 = provider
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
- self._run(sprintf('cd %s; vagrant suspend %s', File.dirname(@vagrantfile), @name))
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._run(sprintf('cd %s; vagrant ssh-config %s', File.dirname(@vagrantfile), @name))
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
- # TODO what to do if the user has specified @sshkey ?
321
- h[:identity_file] = $1
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
- if self.status.eql?('running')
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
- @ssh, @ssh_info = nil, nil
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?
@@ -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
- if self.deltas[:crontab].has_key?(user)
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
- elsif cache and self.deltas[:crontab].class.eql?(Hash) and user.eql?('*')
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
- def get_groups(cache=true)
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
- return self.deltas[:groups]
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
- self.deltas[:groups] = res
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
- res
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
- return self.deltas[:packages]
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
- local = $1 if local_res.match(/version\:\s+(.*?)$/)
177
- else
178
- local = '?'
244
+ version = $1 if local_res.match(/version\:\s+(.*?)$/)
179
245
  end
180
246
 
181
- res[line] = local
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', $2))
191
- local = $1 if local_res.match(/VERSION\:\s+(.*?)$/i)
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[$2] = local
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', $1))
206
- local = $1 if local_res.match(/Version\:\s(.*?)$/)
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[$1] = local
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
- res[$1] = $2
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` -- TODO will this work on other operating systems too?
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
- return self.deltas[:ports]
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] - boolean controlling whether data retrieved/parsed is cached, defaults to true
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
- # raises InternalError if unsupported operating system
311
- def get_services(cache=true)
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
- return self.deltas[:services]
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
- os = self.os_type
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 mode.match(/^\d/)
330
- mode = 'running'
331
- else
332
- mode = 'stopped'
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 mode.match(/online/)
348
- mode = 'running'
349
- elsif mode.match(/legacy_run/)
350
- mode = 'running'
351
- elsif mode.match(//)
352
- mode = 'stopped'
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
- mode = 'stopped' if mode.match('-')
368
- mode = 'running' if mode.match('\+')
369
- mode = 'unsure' if mode.match('\?')
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
- # TODO tighten this up
379
- next if line.match(/^([^\s\:]*).*\s(\w*)(?:\.?){3}$/).nil?
380
- res[$1] = $2
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
- return self.deltas[:users]
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