rouster 0.57 → 0.61
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 +2 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +67 -0
- data/README.md +179 -2
- data/Rakefile +8 -7
- data/Vagrantfile +24 -9
- data/examples/aws.rb +85 -0
- data/lib/rouster.rb +128 -61
- data/lib/rouster/deltas.rb +266 -130
- data/lib/rouster/puppet.rb +2 -2
- data/lib/rouster/testing.rb +28 -6
- data/lib/rouster/vagrant.rb +52 -16
- data/path_helper.rb +3 -4
- data/plugins/aws.rb +347 -0
- data/test/functional/deltas/test_get_packages.rb +50 -6
- data/test/functional/test_caching.rb +2 -2
- data/test/functional/test_new.rb +45 -4
- data/test/functional/test_passthroughs.rb +2 -2
- data/test/puppet/test_apply.rb +1 -1
- data/test/unit/test_new.rb +65 -0
- data/test/unit/testing/resources/osx-launchd +285 -0
- data/test/unit/testing/resources/rhel-systemv +40 -0
- data/test/unit/testing/resources/rhel-upstart +20 -0
- data/test/unit/testing/test_get_services.rb +151 -0
- data/test/unit/testing/test_validate_package.rb +36 -10
- metadata +11 -4
- data/test/puppet/test_roles.rb +0 -186
data/lib/rouster.rb
CHANGED
|
@@ -12,7 +12,7 @@ require 'rouster/vagrant'
|
|
|
12
12
|
class Rouster
|
|
13
13
|
|
|
14
14
|
# sporadically updated version number
|
|
15
|
-
VERSION = 0.
|
|
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 :
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
@
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
res =
|
|
431
|
-
|
|
432
|
-
|
|
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
|
##
|
data/lib/rouster/deltas.rb
CHANGED
|
@@ -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
|
|
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',
|
|
247
|
-
version
|
|
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(.*)$/).
|
|
266
|
+
next if line.match(/(.*?)\s+(.*?)\s(.*)$/).nil?
|
|
257
267
|
name = $2
|
|
268
|
+
arch = '?'
|
|
258
269
|
version = '?'
|
|
259
270
|
|
|
260
271
|
if deep
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
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(
|
|
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(
|
|
296
|
+
next if line.match(/(.*?)\@(.*?)\@(.*)/).nil?
|
|
272
297
|
name = $1
|
|
273
|
-
version =
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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(/(.*?)
|
|
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 =
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
395
|
-
# * RedHat
|
|
396
|
-
# * Solaris -
|
|
397
|
-
# * Ubuntu
|
|
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
|
-
|
|
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
|
-
|
|
419
|
-
|
|
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
|
-
|
|
479
|
+
type = type.class.eql?(Array) ? type : [ type ]
|
|
422
480
|
|
|
423
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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
|
-
|
|
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
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
515
|
+
elsif os.eql?(:solaris)
|
|
469
516
|
|
|
470
|
-
|
|
517
|
+
raw.split("\n").each do |line|
|
|
518
|
+
next if line.match(/(.*?)\s+(?:.*?)\s+(.*?)$/).nil?
|
|
471
519
|
|
|
472
|
-
|
|
520
|
+
service = $2
|
|
521
|
+
mode = $1
|
|
473
522
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
487
|
-
|
|
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
|
-
|
|
541
|
+
res[service] = mode
|
|
490
542
|
|
|
491
|
-
|
|
492
|
-
raw.split("\n").each do |line|
|
|
543
|
+
end
|
|
493
544
|
|
|
494
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
#
|
|
534
|
-
|
|
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
|
-
|
|
543
|
-
|
|
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
|