rouster 0.57 → 0.61

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,7 @@ require 'rouster/vagrant'
12
12
  class Rouster
13
13
 
14
14
  # sporadically updated version number
15
- VERSION = 0.57
15
+ VERSION = 0.61
16
16
 
17
17
  # custom exceptions -- what else do we want them to include/do?
18
18
  class ArgumentError < StandardError; end # thrown by methods that take parameters from users
@@ -76,15 +76,6 @@ class Rouster
76
76
  @verbosity_logfile = 2 # this is kind of arbitrary, but won't actually be created unless opts[:logfile] is also passed
77
77
  end
78
78
 
79
- if opts.has_key?(:sudo)
80
- @sudo = opts[:sudo]
81
- elsif @passthrough.class.eql?(Hash)
82
- # TODO say something here.. or maybe check to see if our user has passwordless sudo?
83
- @sudo = false
84
- else
85
- @sudo = true
86
- end
87
-
88
79
  @ostype = nil
89
80
  @output = Array.new
90
81
  @cache = Hash.new
@@ -109,26 +100,98 @@ class Rouster
109
100
 
110
101
  @logger.outputters[0].level = @verbosity_console # can't set this when instantiating a .std* logger, and want the FileOutputter at a different level
111
102
 
103
+ if opts.has_key?(:sudo)
104
+ @sudo = opts[:sudo]
105
+ elsif @passthrough.class.eql?(Hash)
106
+ @logger.debug(sprintf('passthrough without sudo specification, defaulting to false'))
107
+ @sudo = false
108
+ else
109
+ @sudo = true
110
+ end
111
+
112
112
  if @passthrough
113
- # TODO do better about informing of required specifications, maybe point them to an URL?
114
113
  @vagrantbinary = 'vagrant' # hacky fix to is_vagrant_running?() grepping, doesn't need to actually be in $PATH
114
+ @sshtunnel = opts[:sshtunnel].nil? ? false : @sshtunnel # unless user has specified it, non-local passthroughs default to not open tunnel
115
+
116
+ defaults = {
117
+ :paranoid => false, # valid overrides are: false, true, :very, or :secure
118
+ :ssh_sleep_ceiling => 9,
119
+ :ssh_sleep_time => 10,
120
+ }
121
+
122
+ @passthrough = defaults.merge(@passthrough)
123
+
115
124
  if @passthrough.class != Hash
116
125
  raise ArgumentError.new('passthrough specification should be hash')
117
126
  elsif @passthrough[:type].nil?
118
- raise ArgumentError.new('passthrough :type must be specified, :local or :remote allowed')
127
+ raise ArgumentError.new('passthrough :type must be specified, :local, :remote or :aws allowed')
119
128
  elsif @passthrough[:type].eql?(:local)
120
- @sshtunnel = false
121
129
  @logger.debug('instantiating a local passthrough worker')
130
+ @sshtunnel = opts[:sshtunnel].nil? ? true : opts[:sshtunnel] # override default, if local, open immediately
131
+
122
132
  elsif @passthrough[:type].eql?(:remote)
123
- raise ArgumentError.new('remote passthrough requires :host specification') if @passthrough[:host].nil?
124
- raise ArgumentError.new('remote passthrough requires :user specification') if @passthrough[:user].nil?
125
- raise ArgumentError.new('remote passthrough requires :key specification') if @passthrough[:key].nil?
133
+ @logger.debug('instantiating a remote passthrough worker')
134
+
135
+ [:host, :user, :key].each do |r|
136
+ raise ArgumentError.new(sprintf('remote passthrough requires[%s] specification', r)) if @passthrough[r].nil?
137
+ end
138
+
126
139
  raise ArgumentError.new('remote passthrough requires valid :key specification, should be path to private half') unless File.file?(@passthrough[:key])
127
140
  @sshkey = @passthrough[:key] # TODO refactor so that you don't have to do this..
128
- @logger.debug('instantiating a remote passthrough worker')
141
+
142
+ elsif @passthrough[:type].eql?(:aws) or @passthrough[:type].eql?(:raiden)
143
+ @logger.debug(sprintf('instantiating an %s passthrough worker', @passthrough[:type]))
144
+
145
+ aws_defaults = {
146
+ :ami => 'ami-7bdaa84b', # RHEL 6.5 x64 in us-west-2
147
+ :dns_propagation_sleep => 30, # how much time to wait after ELB creation before attempting to connect
148
+ :key_id => ENV['AWS_ACCESS_KEY_ID'],
149
+ :min_count => 1,
150
+ :max_count => 1,
151
+ :region => 'us-west-2',
152
+ :secret_key => ENV['AWS_SECRET_ACCESS_KEY'],
153
+ :size => 't1.micro',
154
+ :ssh_port => 22,
155
+ :user => 'ec2-user',
156
+ }
157
+
158
+ if @passthrough.has_key?(:ami)
159
+ @logger.debug(':ami specified, will start new EC2 instance')
160
+
161
+ @passthrough[:security_groups] = @passthrough[:security_groups].is_a?(Array) ? @passthrough[:security_groups] : [ @passthrough[:security_groups] ]
162
+
163
+ @passthrough = aws_defaults.merge(@passthrough)
164
+
165
+ [:ami, :size, :user, :region, :key, :keypair, :key_id, :secret_key, :security_groups].each do |r|
166
+ raise ArgumentError.new(sprintf('AWS passthrough requires %s specification', r)) if @passthrough[r].nil?
167
+ end
168
+
169
+ elsif @passthrough.has_key?(:instance)
170
+ @logger.debug(':instance specified, will connect to existing EC2 instance')
171
+
172
+ @passthrough = aws_defaults.merge(@passthrough)
173
+
174
+ if @passthrough[:type].eql?(:aws)
175
+ @passthrough[:host] = self.aws_describe_instance(@passthrough[:instance])['dnsName']
176
+ else
177
+ @passthrough[:host] = self.find_ssh_elb(true)
178
+ end
179
+
180
+ [:instance, :key, :user, :host].each do |r|
181
+ raise ArgumentError.new(sprintf('AWS passthrough requires [%s] specification', r)) if @passthrough[r].nil?
182
+ end
183
+
184
+ else
185
+ raise ArgumentError.new('AWS passthrough requires either :ami or :instance specification')
186
+ end
187
+
188
+ raise ArgumentError.new('AWS passthrough requires valid :sshkey specification, should be path to private half') unless File.file?(@passthrough[:key])
189
+ @sshkey = @passthrough[:key]
190
+
129
191
  else
130
- raise ArgumentError.new(sprintf('passthrough :type [%s] unknown, allowed: :local, :remote', @passthrough[:type]))
192
+ raise ArgumentError.new(sprintf('passthrough :type [%s] unknown, allowed: :aws, :local, :remote', @passthrough[:type]))
131
193
  end
194
+
132
195
  else
133
196
 
134
197
  @logger.debug('Vagrantfile and VM name validation..')
@@ -154,14 +217,6 @@ class Rouster
154
217
  end
155
218
  end
156
219
 
157
- # this is breaking test/functional/test_caching.rb test_ssh_caching (if the VM was not running when the test started)
158
- # it slows down object instantiation, but is a good test to ensure the machine name is valid..
159
- begin
160
- self.status()
161
- rescue Rouster::LocalExecutionError => e
162
- raise InternalError.new(sprintf('caught non-0 exitcode from status(): %s', e.message))
163
- end
164
-
165
220
  begin
166
221
  raise InternalError.new('ssh key not specified') if @sshkey.nil?
167
222
  raise InternalError.new('ssh key does not exist') unless File.file?(@sshkey)
@@ -282,23 +337,22 @@ class Rouster
282
337
 
283
338
  if @ssh.nil? or @ssh.closed?
284
339
  begin
285
- self.connect_ssh_tunnel()
340
+ res = self.connect_ssh_tunnel()
286
341
  rescue Rouster::InternalError, Net::SSH::Disconnect => e
287
342
  res = false
288
343
  end
289
344
 
290
345
  end
291
346
 
292
- if res.nil?
347
+ if res.nil? or res.is_a?(Net::SSH::Connection::Session)
293
348
  begin
294
349
  self.run('echo functional test of SSH tunnel')
350
+ res = true
295
351
  rescue
296
352
  res = false
297
353
  end
298
354
  end
299
355
 
300
- res = true if res.nil?
301
-
302
356
  if @cache_timeout
303
357
  @cache[:is_available_via_ssh?] = Hash.new unless @cache[:is_available_via_ssh?].class.eql?(Hash)
304
358
  @cache[:is_available_via_ssh?][:time] = Time.now.to_i
@@ -360,18 +414,36 @@ class Rouster
360
414
  def connect_ssh_tunnel
361
415
 
362
416
  if self.is_passthrough?
363
- if self.passthrough[:type].eql?(:local)
417
+ if @passthrough[:type].eql?(:local)
418
+ @logger.debug("local passthroughs don't need ssh tunnel, shell execs are used")
419
+ return false
420
+ elsif @passthrough[:host].nil?
421
+ @logger.info(sprintf('not attempting to connect, no known hostname for[%s]', self.passthrough))
364
422
  return false
365
423
  else
366
- @logger.debug('opening remote SSH tunnel..')
367
- @ssh = Net::SSH.start(
368
- @passthrough[:host],
369
- @passthrough[:user],
370
- :port => @passthrough[:port],
371
- :keys => [ @passthrough[:key] ],
372
- :paranoid => false
373
- )
424
+ ceiling = @passthrough[:ssh_sleep_ceiling]
425
+ sleep_time = @passthrough[:ssh_sleep_time]
426
+
427
+ 0.upto(ceiling) do |try|
428
+ @logger.debug(sprintf('opening remote SSH tunnel[%s]..', @passthrough[:host]))
429
+ begin
430
+ @ssh = Net::SSH.start(
431
+ @passthrough[:host],
432
+ @passthrough[:user],
433
+ :port => @passthrough[:ssh_port],
434
+ :keys => [ @passthrough[:key] ], # TODO this should be @sshkey
435
+ :paranoid => false
436
+ )
437
+ break
438
+ rescue => e
439
+ raise e if try.eql?(ceiling) # eventually want to throw a SocketError
440
+ @logger.debug(sprintf('failed to open tunnel[%s], trying again in %ss', e.message, sleep_time))
441
+ sleep sleep_time
442
+ end
443
+ end
374
444
  end
445
+ @logger.debug(sprintf('successfully opened SSH tunnel to[%s]', passthrough[:host]))
446
+
375
447
  else
376
448
  # not a passthrough, normal connection
377
449
  status = self.status()
@@ -387,6 +459,7 @@ class Rouster
387
459
  :paranoid => false
388
460
  )
389
461
  else
462
+ # TODO will we ever hit this? or will we be thrown first?
390
463
  raise InternalError.new(sprintf('VM is not running[%s], unable open SSH tunnel', status))
391
464
  end
392
465
  end
@@ -416,32 +489,25 @@ class Rouster
416
489
  return @ostype
417
490
  end
418
491
 
419
- # TODO switch to file based detection
420
- # Ubuntu - /etc/os-release
421
- # Solaris - /etc/release
422
- # RHEL/CentOS - /etc/redhat-release
423
- # OSX - ?
492
+ files = {
493
+ :ubuntu => '/etc/os-release', # debian too
494
+ :solaris => '/etc/release',
495
+ :redhat => '/etc/redhat-release', # centos too
496
+ :osx => '/System/Library/CoreServices/SystemVersion.plist',
497
+ }
424
498
 
425
499
  res = nil
426
- uname = self.run('uname -a')
427
-
428
- case uname
429
- when /Darwin/i
430
- res = :osx
431
- when /SunOS|Solaris/i
432
- res =:solaris
433
- when /Ubuntu/i
434
- res = :ubuntu
435
- when /Debian/i
436
- res = :debian
437
- else
438
- if self.is_file?('/etc/redhat-release')
439
- res = :redhat
440
- else
441
- res = nil
442
- end
500
+
501
+ files.each do |os,file|
502
+ if self.is_file?(file)
503
+ @logger.debug(sprintf('determined OS to be[%s] via[%s]', os, file))
504
+ res = os
505
+ break
506
+ end
443
507
  end
444
508
 
509
+ @logger.error(sprintf('unable to determine OS, looking for[%s]', files)) if res.nil?
510
+
445
511
  @ostype = res
446
512
  res
447
513
  end
@@ -493,6 +559,7 @@ class Rouster
493
559
  raise FileTransferError.new(sprintf('unable to put[%s], exception[%s]', local_file, e.message()))
494
560
  end
495
561
 
562
+ return true
496
563
  end
497
564
 
498
565
  ##
@@ -213,9 +213,9 @@ class Rouster
213
213
  #
214
214
  # supported OS
215
215
  # * OSX - runs `pkgutil --pkgs` and `pkgutil --pkg-info=<package>` (if deep)
216
- # * RedHat - runs `rpm -qa`
216
+ # * RedHat - runs `rpm -qa --qf "%{n}@%{v}@%{arch}\n"` (does not support deep)
217
217
  # * Solaris - runs `pkginfo` and `pkginfo -l <package>` (if deep)
218
- # * Ubuntu - runs `dpkg --get-selections` and `dpkg -s <package>` (if deep)
218
+ # * Ubuntu - runs `dpkg-query -W -f='${Package}\@${Version}\@${Architecture}\n'` (does not support deep)
219
219
  #
220
220
  # raises InternalError if unsupported operating system
221
221
  def get_packages(cache=true, deep=true)
@@ -239,62 +239,93 @@ class Rouster
239
239
 
240
240
  raw = self.run('pkgutil --pkgs')
241
241
  raw.split("\n").each do |line|
242
+ name = line
243
+ arch = '?'
242
244
  version = '?'
243
245
 
244
246
  if deep
245
247
  # can get install time, volume and location as well
246
- local_res = self.run(sprintf('pkgutil --pkg-info=%s', line))
247
- version = $1 if local_res.match(/version\:\s+(.*?)$/)
248
+ local_res = self.run(sprintf('pkgutil --pkg-info=%s', name))
249
+ version = $1 if local_res.match(/version\:\s+(.*?)$/)
250
+ end
251
+
252
+ if res.has_key?(name)
253
+ # different architecture of an already known package
254
+ @logger.debug(sprintf('found package with already known name[%s], value[%s], new line[%s], turning into array', name, res[name], line))
255
+ new_element = { :version => version, :arch => arch }
256
+ res[name] = [ res[name], new_element ]
257
+ else
258
+ res[name] = { :version => version, :arch => arch }
248
259
  end
249
260
 
250
- res[line] = version
251
261
  end
252
262
 
253
263
  elsif os.eql?(:solaris)
254
264
  raw = self.run('pkginfo')
255
265
  raw.split("\n").each do |line|
256
- next if line.match(/(.*?)\s+(.*?)\s(.*)$/).empty?
266
+ next if line.match(/(.*?)\s+(.*?)\s(.*)$/).nil?
257
267
  name = $2
268
+ arch = '?'
258
269
  version = '?'
259
270
 
260
271
  if deep
261
- local_res = self.run(sprintf('pkginfo -l %s', name))
262
- version = $1 if local_res.match(/VERSION\:\s+(.*?)$/i)
272
+ begin
273
+ # who throws non-0 exit codes when querying for legit package information? solaris does.
274
+ local_res = self.run(sprintf('pkginfo -l %s', name))
275
+ arch = $1 if local_res.match(/ARCH\:\s+(.*?)$/)
276
+ version = $1 if local_res.match(/VERSION\:\s+(.*?)$/)
277
+ rescue
278
+ arch = '?' if arch.nil?
279
+ version = '?' if arch.nil?
280
+ end
263
281
  end
264
282
 
265
- res[name] = version
283
+ if res.has_key?(name)
284
+ # different architecture of an already known package
285
+ @logger.debug(sprintf('found package with already known name[%s], value[%s], new line[%s], turning into array', name, res[name], line))
286
+ new_element = { :version => version, :arch => arch }
287
+ res[name] = [ res[name], new_element ]
288
+ else
289
+ res[name] = { :version => version, :arch => arch }
290
+ end
266
291
  end
267
292
 
268
293
  elsif os.eql?(:ubuntu) or os.eql?(:debian)
269
- raw = self.run('dpkg --get-selections')
294
+ raw = self.run("dpkg-query -W -f='${Package}@${Version}@${Architecture}\n'")
270
295
  raw.split("\n").each do |line|
271
- next if line.match(/^(.*?)\s/).nil?
296
+ next if line.match(/(.*?)\@(.*?)\@(.*)/).nil?
272
297
  name = $1
273
- version = '?'
274
-
275
- if deep
276
- local_res = self.run(sprintf('dpkg -s %s', name))
277
- version = $1 if local_res.match(/Version\:\s(.*?)$/)
298
+ version = $2
299
+ arch = $3
300
+
301
+ if res.has_key?(name)
302
+ # different architecture of an already known package
303
+ @logger.debug(sprintf('found package with already known name[%s], value[%s], new line[%s], turning into array', name, res[name], line))
304
+ new_element = { :version => version, :arch => arch }
305
+ res[name] = [ res[name], new_element ]
306
+ else
307
+ res[name] = { :version => version, :arch => arch }
278
308
  end
279
309
 
280
- res[name] = version
281
310
  end
282
311
 
283
312
  elsif os.eql?(:redhat)
284
- raw = self.run('rpm -qa')
313
+ raw = self.run('rpm -qa --qf "%{n}@%{v}@%{arch}\n"')
285
314
  raw.split("\n").each do |line|
286
- next if line.match(/(.*?)-(\d*\..*)/).nil? # ht petersen.allen
287
- #next if line.match(/(.*)-(\d+\.\d+.*)/).nil? # another alternate, but still not perfect
315
+ next if line.match(/(.*?)\@(.*?)\@(.*)/).nil?
288
316
  name = $1
289
- version = '?' # we could use $2, but we don't trust it
290
-
291
- if deep
292
- local_res = self.run(sprintf('rpm -qi %s', line))
293
- name = $1 if local_res.match(/Name\s+:\s(\S*)/)
294
- version = $1 if local_res.match(/Version\s+:\s(\S*)/)
317
+ version = $2
318
+ arch = $3
319
+
320
+ if res.has_key?(name)
321
+ # different architecture of an already known package
322
+ @logger.debug(sprintf('found package with already known name[%s], value[%s], new line[%s], turning into array', name, res[name], line))
323
+ new_element = { :version => version, :arch => arch }
324
+ res[name] = [ res[name], new_element ]
325
+ else
326
+ res[name] = { :version => version, :arch => arch }
295
327
  end
296
328
 
297
- res[name] = version
298
329
  end
299
330
 
300
331
  else
@@ -389,17 +420,20 @@ class Rouster
389
420
  # parameters
390
421
  # * [cache] - boolean controlling whether data retrieved/parsed is cached, defaults to true
391
422
  # * [humanize] - boolean controlling whether data retrieved is massaged into simplified names or returned mostly as retrieved
423
+ # * [type] - symbol indicating which service controller to query, defaults to :all
424
+ # * [seed] - test hook to seed the output of service commands
392
425
  #
393
- # supported OS
394
- # * OSX - runs `launchctl list`
395
- # * RedHat - runs `/sbin/service --status-all`
396
- # * Solaris - runs `svcs`
397
- # * Ubuntu - runs `service --status-all`
426
+ # supported OS and types
427
+ # * OSX - :launchd
428
+ # * RedHat - :systemv or :upstart
429
+ # * Solaris - :smf
430
+ # * Ubuntu - :systemv or :upstart
398
431
  #
399
432
  # notes
400
433
  # * raises InternalError if unsupported operating system
401
434
  # * OSX, Solaris and Ubuntu/Debian will only return running|stopped|unsure, the exists|installed|operational modes are RHEL/CentOS only
402
- def get_services(cache=true, humanize=true)
435
+
436
+ def get_services(cache=true, humanize=true, type=:all, seed=nil)
403
437
  if cache and ! self.deltas[:services].nil?
404
438
 
405
439
  if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:services]) > self.cache_timeout
@@ -415,132 +449,234 @@ class Rouster
415
449
  res = Hash.new()
416
450
  os = self.os_type
417
451
 
418
- allowed_modes = %w(exists installed operational running stopped unsure)
419
- failover_mode = 'unsure'
452
+ commands = {
453
+ :osx => {
454
+ :launchd => 'launchctl list',
455
+ },
456
+ :solaris => {
457
+ :smf => 'svcs -a',
458
+ },
459
+
460
+ # TODO we really need to implement something like osfamily
461
+ :ubuntu => {
462
+ :systemv => 'service --status-all 2>&1',
463
+ :upstart => 'initctl list',
464
+ },
465
+ :debian => {
466
+ :systemv => 'service --status-all 2>&1',
467
+ :upstart => 'initctl list',
468
+ },
469
+ :redhat => {
470
+ :systemv => '/sbin/service --status-all',
471
+ :upstart => 'initctl list',
472
+ },
473
+ }
474
+
475
+ if type.eql?(:all)
476
+ type = commands[os].keys
477
+ end
420
478
 
421
- if os.eql?(:osx)
479
+ type = type.class.eql?(Array) ? type : [ type ]
422
480
 
423
- raw = self.run('launchctl list')
424
- raw.split("\n").each do |line|
425
- next if line.match(/(?:\S*?)\s+(\S*?)\s+(\S*)$/).nil?
481
+ type.each do |provider|
426
482
 
427
- service = $2
428
- mode = $1
483
+ raise InternalError.new(sprintf('unable to get service information from VM operating system[%s]', os)) unless commands.has_key?(os)
484
+ raise ArgumentError.new(sprintf('unable to find command provider[%s] for [%s]', provider, os)) if commands[os][provider].nil?
429
485
 
430
- if humanize # should we do this with a .freeze instead?
431
- if mode.match(/^\d/)
432
- mode = 'running'
433
- else
434
- mode = 'stopped'
435
- end
436
- end
486
+ @logger.info(sprintf('get_services using provider [%s] on [%s]', provider, os))
437
487
 
438
- res[service] = mode
439
- end
488
+ # TODO while this is true, what if self.user is 'root'.. -- the problem is we don't have self.user, and we store this data differently depending on self.passthrough?
489
+ @logger.warn('gathering service information typically works better with sudo, which is currently not being used') unless self.uses_sudo?
440
490
 
441
- elsif os.eql?(:solaris)
491
+ # TODO come up with a better test hook -- real problem is that we can't seed 'raw' with different values per iteration
492
+ raw = seed.nil? ? self.run(commands[os][provider]) : seed
442
493
 
443
- raw = self.run('svcs -a')
444
- raw.split("\n").each do |line|
445
- next if line.match(/(.*?)\s+(?:.*?)\s+(.*?)$/).nil?
446
-
447
- service = $2
448
- mode = $1
449
-
450
- if humanize
451
- if mode.match(/^online/)
452
- mode = 'running'
453
- elsif mode.match(/^legacy_run/)
454
- mode = 'running'
455
- elsif mode.match(/^disabled/)
456
- mode = 'stopped'
457
- end
494
+ if os.eql?(:osx)
495
+
496
+ raw.split("\n").each do |line|
497
+ next if line.match(/(?:\S*?)\s+(\S*?)\s+(\S+)$/).nil?
498
+ tokens = line.split("\s")
499
+ service = tokens[-1]
500
+ mode = tokens[0]
458
501
 
459
- if service.match(/^svc:\/.*\/(.*?):.*/)
460
- # turning 'svc:/network/cswpuppetd:default' into 'cswpuppetd'
461
- service = $1
462
- elsif service.match(/^lrc:\/.*?\/.*\/(.*)/)
463
- # turning 'lrc:/etc/rcS_d/S50sk98Sol' into 'S50sk98Sol'
464
- service = $1
502
+ if humanize # should we do this with a .freeze instead?
503
+ if mode.match(/^\d/)
504
+ mode = 'running'
505
+ elsif mode.match(/-/)
506
+ mode = 'stopped'
507
+ else
508
+ next # this should handle the banner "PID Status Label"
509
+ end
465
510
  end
511
+
512
+ res[service] = mode
466
513
  end
467
514
 
468
- res[service] = mode
515
+ elsif os.eql?(:solaris)
469
516
 
470
- end
517
+ raw.split("\n").each do |line|
518
+ next if line.match(/(.*?)\s+(?:.*?)\s+(.*?)$/).nil?
471
519
 
472
- elsif os.eql?(:ubuntu) or os.eql?(:debian)
520
+ service = $2
521
+ mode = $1
473
522
 
474
- raw = self.run('service --status-all 2>&1')
475
- raw.split("\n").each do |line|
476
- next if line.match(/\[(.*?)\]\s+(.*)$/).nil?
477
- mode = $1
478
- service = $2
479
-
480
- if humanize
481
- mode = 'stopped' if mode.match('-')
482
- mode = 'running' if mode.match('\+')
483
- mode = 'unsure' if mode.match('\?')
484
- end
523
+ if humanize
524
+ if mode.match(/^online/)
525
+ mode = 'running'
526
+ elsif mode.match(/^legacy_run/)
527
+ mode = 'running'
528
+ elsif mode.match(/^disabled/)
529
+ mode = 'stopped'
530
+ end
485
531
 
486
- res[service] = mode
487
- end
532
+ if service.match(/^svc:\/.*\/(.*?):.*/)
533
+ # turning 'svc:/network/cswpuppetd:default' into 'cswpuppetd'
534
+ service = $1
535
+ elsif service.match(/^lrc:\/.*?\/.*\/(.*)/)
536
+ # turning 'lrc:/etc/rcS_d/S50sk98Sol' into 'S50sk98Sol'
537
+ service = $1
538
+ end
539
+ end
488
540
 
489
- elsif os.eql?(:redhat)
541
+ res[service] = mode
490
542
 
491
- raw = self.run('/sbin/service --status-all')
492
- raw.split("\n").each do |line|
543
+ end
493
544
 
494
- if humanize
545
+ elsif os.eql?(:ubuntu) or os.eql?(:debian)
546
+
547
+ raw.split("\n").each do |line|
548
+ if provider.eql?(:systemv)
549
+ next if line.match(/\[(.*?)\]\s+(.*)$/).nil?
550
+ mode = $1
551
+ service = $2
552
+
553
+ if humanize
554
+ mode = 'stopped' if mode.match('-')
555
+ mode = 'running' if mode.match('\+')
556
+ mode = 'unsure' if mode.match('\?')
557
+ end
558
+
559
+ res[service] = mode
560
+ elsif provider.eql?(:upstart)
561
+ if line.match(/(.*?)\s.*?(.*?),/)
562
+ # tty (/dev/tty3) start/running, process 1601
563
+ # named start/running, process 8959
564
+ service = $1
565
+ mode = $2
566
+ elsif line.match(/(.*?)\s(.*)/)
567
+ # rcS stop/waiting
568
+ service = $1
569
+ mode = $2
570
+ else
571
+ @logger.warn("unable to process upstart line[#{line}], skipping")
572
+ next
573
+ end
574
+
575
+ if humanize
576
+ mode = 'stopped' if mode.match('stop/waiting')
577
+ mode = 'running' if mode.match('start/running')
578
+ mode = 'unsure' unless mode.eql?('stopped') or mode.eql?('running')
579
+ end
580
+
581
+ res[service] = mode
582
+ end
583
+ end
495
584
 
496
- if line.match(/^(\w+?)\sis\s(.*)$/)
497
- # <service> is <state>
498
- name = $1
499
- state = $2
500
- res[name] = state
585
+ elsif os.eql?(:redhat)
586
+
587
+ raw.split("\n").each do |line|
588
+ if provider.eql?(:systemv)
589
+ if humanize
590
+ if line.match(/^(\w+?)\sis\s(.*)$/)
591
+ # <service> is <state>
592
+ name = $1
593
+ state = $2
594
+ res[name] = state
595
+
596
+ if state.match(/^not/)
597
+ # this catches 'Kdump is not operational'
598
+ res[name] = 'stopped'
599
+ end
600
+
601
+ elsif line.match(/^(\w+?)\s\(pid.*?\)\sis\s(\w+)$/)
602
+ # <service> (pid <pid> [pid]) is <state>...
603
+ res[$1] = $2
604
+ elsif line.match(/^(\w+?)\sis\s(\w+)\.*$/) # not sure this is actually needed
605
+ @logger.debug('triggered supposedly unnecessary regex')
606
+ # <service> is <state>. whatever
607
+ res[$1] = $2
608
+ elsif line.match(/razor_daemon:\s(\w+).*$/)
609
+ # razor_daemon: running [pid 11325]
610
+ # razor_daemon: no instances running
611
+ res['razor_daemon'] = $1.eql?('running') ? $1 : 'stopped'
612
+ elsif line.match(/^(\w+?)\:.*?(\w+)$/)
613
+ # <service>: whatever <state>
614
+ res[$1] = $2
615
+ elsif line.match(/^(\w+?):.*?\sis\snot\srunning\.$/)
616
+ # ip6tables: Firewall is not running.
617
+ res[$1] = 'stopped'
618
+ elsif line.match(/^(\w+?)\s.*?\s(.*)$/)
619
+ # netconsole module not loaded
620
+ state = $2
621
+ res[$1] = $2.match(/not/) ? 'stopped' : 'running'
622
+ elsif line.match(/^(\w+)\s(\w+).*$/)
623
+ # <process> <state> whatever
624
+ res[$1] = $2
625
+ else
626
+ # original regex implementation, if we didn't match anything else, failover to this
627
+ next if line.match(/^([^\s:]*).*\s(\w*)(?:\.?){3}$/).nil?
628
+ res[$1] = $2
629
+ end
630
+ else
631
+ next if line.match(/^([^\s:]*).*\s(\w*)(?:\.?){3}$/).nil?
632
+ res[$1] = $2
633
+ end
634
+ elsif provider.eql?(:upstart)
635
+
636
+ if line.match(/(.*?)\s.*?(.*?),/)
637
+ # tty (/dev/tty3) start/running, process 1601
638
+ # named start/running, process 8959
639
+ service = $1
640
+ mode = $2
641
+ elsif line.match(/(.*?)\s(.*)/)
642
+ # rcS stop/waiting
643
+ service = $1
644
+ mode = $2
645
+ else
646
+ @logger.warn("unable to process upstart line[#{line}], skipping")
647
+ next
648
+ end
501
649
 
502
- if state.match(/^not/)
503
- # this catches 'Kdump is not operational'
504
- res[name] = 'stopped'
650
+ if humanize
651
+ mode = 'stopped' if mode.match('stop/waiting')
652
+ mode = 'running' if mode.match('start/running')
653
+ mode = 'unsure' unless mode.eql?('stopped') or mode.eql?('running')
505
654
  end
506
655
 
507
- elsif line.match(/^(\w+?)\s\(pid.*?\)\sis\s(\w+)$/)
508
- # <service> (pid <pid> [pid]) is <state>...
509
- res[$1] = $2
510
- elsif line.match(/^(\w+?)\sis\s(\w+)\.*$/) # not sure this is actually needed
511
- @logger.debug('triggered supposedly unnecessary regex')
512
- # <service> is <state>. whatever
513
- res[$1] = $2
514
- elsif line.match(/^(\w+?)\:.*?(\w+)$/)
515
- # <service>: whatever <state>
516
- res[$1] = $2
517
- elsif line.match(/^(\w+)\s(\w+).*$/)
518
- # <process> <state> whatever
519
- res[$1] = $2
520
- else
521
- # original regex implementation, if we didn't match anything else, failover to this
522
- next if line.match(/^([^\s:]*).*\s(\w*)(?:\.?){3}$/).nil?
523
- res[$1] = $2
656
+ res[service] = mode unless res.has_key?(service)
657
+
658
+
524
659
  end
525
660
 
526
- else
527
- next if line.match(/^([^\s:]*).*\s(\w*)(?:\.?){3}$/).nil?
528
- res[$1] = $2
529
661
  end
530
662
 
663
+ # end of os casing
531
664
  end
532
665
 
533
- # issue #63 handling
534
- if humanize
535
- res.each_pair do |k,v|
536
- next if allowed_modes.member?(v)
537
- @logger.debug(sprintf('replacing service[%s] status of [%s] with [%s] for uniformity', k, v, failover_mode))
538
- res[k] = failover_mode
539
- end
540
- end
666
+ # end of provider processing
667
+ end
541
668
 
542
- else
543
- raise InternalError.new(sprintf('unable to get service information from VM operating system[%s]', os))
669
+ # issue #63 handling
670
+ # TODO should we consider using symbols here instead?
671
+ allowed_modes = %w(exists installed operational running stopped unsure)
672
+ failover_mode = 'unsure'
673
+
674
+ if humanize
675
+ res.each_pair do |k,v|
676
+ next if allowed_modes.member?(v)
677
+ @logger.debug(sprintf('replacing service[%s] status of [%s] with [%s] for uniformity', k, v, failover_mode))
678
+ res[k] = failover_mode
679
+ end
544
680
  end
545
681
 
546
682
  if cache