rouster 0.53 → 0.57
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/README.md +5 -2
- data/examples/passthrough.rb +71 -0
- data/lib/rouster.rb +194 -242
- data/lib/rouster/deltas.rb +44 -39
- data/lib/rouster/puppet.rb +82 -21
- data/lib/rouster/testing.rb +129 -19
- data/lib/rouster/tests.rb +34 -6
- data/lib/rouster/vagrant.rb +242 -0
- data/test/basic.rb +4 -1
- data/test/functional/deltas/test_get_crontab.rb +22 -1
- data/test/functional/deltas/test_get_services.rb +6 -0
- data/test/functional/test_inspect.rb +1 -1
- data/test/functional/test_is_file.rb +17 -1
- data/test/functional/test_new.rb +185 -16
- data/test/functional/test_passthroughs.rb +94 -0
- data/test/functional/test_put.rb +2 -2
- data/test/functional/test_validate_file.rb +55 -3
- data/test/puppet/test_apply.rb +7 -5
- data/test/unit/puppet/test_get_puppet_star.rb +27 -4
- data/test/unit/test_new.rb +23 -0
- data/test/unit/test_parse_ls_string.rb +24 -0
- data/test/unit/testing/test_validate_cron.rb +78 -0
- metadata +24 -20
data/lib/rouster/deltas.rb
CHANGED
@@ -12,13 +12,12 @@ class Rouster
|
|
12
12
|
# runs `crontab -l <user>` and parses output, returns hash:
|
13
13
|
# {
|
14
14
|
# user => {
|
15
|
-
#
|
15
|
+
# command => {
|
16
16
|
# :minute => minute,
|
17
17
|
# :hour => hour,
|
18
18
|
# :dom => dom, # day of month
|
19
19
|
# :mon => mon, # month
|
20
20
|
# :dow => dow, # day of week
|
21
|
-
# :command => command,
|
22
21
|
# }
|
23
22
|
# }
|
24
23
|
# }
|
@@ -33,15 +32,15 @@ class Rouster
|
|
33
32
|
if cache and self.deltas[:crontab].class.eql?(Hash)
|
34
33
|
|
35
34
|
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:crontab]) > self.cache_timeout
|
36
|
-
@
|
35
|
+
@logger.debug(sprintf('invalidating [crontab] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:crontab]), self.cache_timeout))
|
37
36
|
self.deltas.delete(:crontab)
|
38
37
|
end
|
39
38
|
|
40
39
|
if self.deltas.has_key?(:crontab) and self.deltas[:crontab].has_key?(user)
|
41
|
-
@
|
40
|
+
@logger.debug(sprintf('using cached [crontab] from [%s]', self.cache[:crontab]))
|
42
41
|
return self.deltas[:crontab][user]
|
43
42
|
elsif self.deltas.has_key?(:crontab) and user.eql?('*')
|
44
|
-
@
|
43
|
+
@logger.debug(sprintf('using cached [crontab] from [%s]', self.cache[:crontab]))
|
45
44
|
return self.deltas[:crontab]
|
46
45
|
else
|
47
46
|
# noop fallthrough to gather data to cache
|
@@ -49,7 +48,6 @@ class Rouster
|
|
49
48
|
|
50
49
|
end
|
51
50
|
|
52
|
-
i = 0
|
53
51
|
res = Hash.new
|
54
52
|
users = nil
|
55
53
|
|
@@ -69,25 +67,30 @@ class Rouster
|
|
69
67
|
end
|
70
68
|
|
71
69
|
raw.split("\n").each do |line|
|
72
|
-
next if line.match(
|
70
|
+
next if line.match(/^#|^\s+$/)
|
73
71
|
elements = line.split("\s")
|
74
72
|
|
73
|
+
command = elements[5..elements.size].join(' ')
|
74
|
+
|
75
75
|
res[u] ||= Hash.new
|
76
|
-
res[u][i] ||= Hash.new
|
77
|
-
|
78
|
-
res[u][i][:minute] = elements[0]
|
79
|
-
res[u][i][:hour] = elements[1]
|
80
|
-
res[u][i][:dom] = elements[2]
|
81
|
-
res[u][i][:mon] = elements[3]
|
82
|
-
res[u][i][:dow] = elements[4]
|
83
|
-
res[u][i][:command] = elements[5..elements.size].join(' ')
|
84
|
-
end
|
85
76
|
|
86
|
-
|
77
|
+
if res[u][command].class.eql?(Hash)
|
78
|
+
unique = elements.join('')
|
79
|
+
command = sprintf('%s-duplicate.%s', command, unique)
|
80
|
+
@logger.info(sprintf('duplicate crontab command found, adding hash key[%s]', command))
|
81
|
+
end
|
82
|
+
|
83
|
+
res[u][command] = Hash.new
|
84
|
+
res[u][command][:minute] = elements[0]
|
85
|
+
res[u][command][:hour] = elements[1]
|
86
|
+
res[u][command][:dom] = elements[2]
|
87
|
+
res[u][command][:mon] = elements[3]
|
88
|
+
res[u][command][:dow] = elements[4]
|
89
|
+
end
|
87
90
|
end
|
88
91
|
|
89
92
|
if cache
|
90
|
-
@
|
93
|
+
@logger.debug(sprintf('caching [crontab] at [%s]', Time.now.asctime))
|
91
94
|
|
92
95
|
if ! user.eql?('*')
|
93
96
|
self.deltas[:crontab] ||= Hash.new
|
@@ -124,10 +127,10 @@ class Rouster
|
|
124
127
|
if cache and ! self.deltas[:groups].nil?
|
125
128
|
|
126
129
|
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:groups]) > self.cache_timeout
|
127
|
-
@
|
130
|
+
@logger.debug(sprintf('invalidating [groups] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:groups]), self.cache_timeout))
|
128
131
|
self.deltas.delete(:groups)
|
129
132
|
else
|
130
|
-
@
|
133
|
+
@logger.debug(sprintf('using cached [groups] from [%s]', self.cache[:groups]))
|
131
134
|
return self.deltas[:groups]
|
132
135
|
end
|
133
136
|
|
@@ -166,7 +169,7 @@ class Rouster
|
|
166
169
|
gid = users[user][:gid]
|
167
170
|
|
168
171
|
unless known_valid_gids.member?(gid)
|
169
|
-
@
|
172
|
+
@logger.warn(sprintf('found user[%s] with unknown GID[%s], known GIDs[%s]', user, gid, known_valid_gids))
|
170
173
|
next
|
171
174
|
end
|
172
175
|
|
@@ -186,7 +189,7 @@ class Rouster
|
|
186
189
|
end
|
187
190
|
|
188
191
|
if cache
|
189
|
-
@
|
192
|
+
@logger.debug(sprintf('caching [groups] at [%s]', Time.now.asctime))
|
190
193
|
self.deltas[:groups] = groups
|
191
194
|
self.cache[:groups] = Time.now.to_i
|
192
195
|
end
|
@@ -219,10 +222,10 @@ class Rouster
|
|
219
222
|
if cache and ! self.deltas[:packages].nil?
|
220
223
|
|
221
224
|
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:packages]) > self.cache_timeout
|
222
|
-
@
|
225
|
+
@logger.debug(sprintf('invalidating [packages] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:packages]), self.cache_timeout))
|
223
226
|
self.deltas.delete(:packages)
|
224
227
|
else
|
225
|
-
@
|
228
|
+
@logger.debug(sprintf('using cached [packages] from [%s]', self.cache[:packages]))
|
226
229
|
return self.deltas[:packages]
|
227
230
|
end
|
228
231
|
|
@@ -299,7 +302,7 @@ class Rouster
|
|
299
302
|
end
|
300
303
|
|
301
304
|
if cache
|
302
|
-
@
|
305
|
+
@logger.debug(sprintf('caching [packages] at [%s]', Time.now.asctime))
|
303
306
|
self.deltas[:packages] = res
|
304
307
|
self.cache[:packages] = Time.now.to_i
|
305
308
|
end
|
@@ -332,10 +335,10 @@ class Rouster
|
|
332
335
|
|
333
336
|
if cache and ! self.deltas[:ports].nil?
|
334
337
|
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:ports]) > self.cache_timeout
|
335
|
-
@
|
338
|
+
@logger.debug(sprintf('invalidating [ports] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:ports]), self.cache_timeout))
|
336
339
|
self.deltas.delete(:ports)
|
337
340
|
else
|
338
|
-
@
|
341
|
+
@logger.debug(sprintf('using cached [ports] from [%s]', self.cache[:ports]))
|
339
342
|
return self.deltas[:ports]
|
340
343
|
end
|
341
344
|
end
|
@@ -367,7 +370,7 @@ class Rouster
|
|
367
370
|
end
|
368
371
|
|
369
372
|
if cache
|
370
|
-
@
|
373
|
+
@logger.debug(sprintf('caching [ports] at [%s]', Time.now.asctime))
|
371
374
|
self.deltas[:ports] = res
|
372
375
|
self.cache[:ports] = Time.now.to_i
|
373
376
|
end
|
@@ -400,10 +403,10 @@ class Rouster
|
|
400
403
|
if cache and ! self.deltas[:services].nil?
|
401
404
|
|
402
405
|
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:services]) > self.cache_timeout
|
403
|
-
@
|
406
|
+
@logger.debug(sprintf('invalidating [services] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:services]), self.cache_timeout))
|
404
407
|
self.deltas.delete(:services)
|
405
408
|
else
|
406
|
-
@
|
409
|
+
@logger.debug(sprintf('using cached [services] from [%s]', self.cache[:services]))
|
407
410
|
return self.deltas[:services]
|
408
411
|
end
|
409
412
|
|
@@ -492,18 +495,20 @@ class Rouster
|
|
492
495
|
|
493
496
|
if line.match(/^(\w+?)\sis\s(.*)$/)
|
494
497
|
# <service> is <state>
|
495
|
-
|
498
|
+
name = $1
|
499
|
+
state = $2
|
500
|
+
res[name] = state
|
496
501
|
|
497
|
-
if
|
502
|
+
if state.match(/^not/)
|
498
503
|
# this catches 'Kdump is not operational'
|
499
|
-
res[
|
504
|
+
res[name] = 'stopped'
|
500
505
|
end
|
501
506
|
|
502
507
|
elsif line.match(/^(\w+?)\s\(pid.*?\)\sis\s(\w+)$/)
|
503
508
|
# <service> (pid <pid> [pid]) is <state>...
|
504
509
|
res[$1] = $2
|
505
510
|
elsif line.match(/^(\w+?)\sis\s(\w+)\.*$/) # not sure this is actually needed
|
506
|
-
@
|
511
|
+
@logger.debug('triggered supposedly unnecessary regex')
|
507
512
|
# <service> is <state>. whatever
|
508
513
|
res[$1] = $2
|
509
514
|
elsif line.match(/^(\w+?)\:.*?(\w+)$/)
|
@@ -529,7 +534,7 @@ class Rouster
|
|
529
534
|
if humanize
|
530
535
|
res.each_pair do |k,v|
|
531
536
|
next if allowed_modes.member?(v)
|
532
|
-
@
|
537
|
+
@logger.debug(sprintf('replacing service[%s] status of [%s] with [%s] for uniformity', k, v, failover_mode))
|
533
538
|
res[k] = failover_mode
|
534
539
|
end
|
535
540
|
end
|
@@ -539,7 +544,7 @@ class Rouster
|
|
539
544
|
end
|
540
545
|
|
541
546
|
if cache
|
542
|
-
@
|
547
|
+
@logger.debug(sprintf('caching [services] at [%s]', Time.now.asctime))
|
543
548
|
self.deltas[:services] = res
|
544
549
|
self.cache[:services] = Time.now.to_i
|
545
550
|
end
|
@@ -566,10 +571,10 @@ class Rouster
|
|
566
571
|
if cache and ! self.deltas[:users].nil?
|
567
572
|
|
568
573
|
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:users]) > self.cache_timeout
|
569
|
-
@
|
574
|
+
@logger.debug(sprintf('invalidating [users] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:users]), self.cache_timeout))
|
570
575
|
self.deltas.delete(:users)
|
571
576
|
else
|
572
|
-
@
|
577
|
+
@logger.debug(sprintf('using cached [users] from [%s]', self.cache[:users]))
|
573
578
|
return self.deltas[:users]
|
574
579
|
end
|
575
580
|
|
@@ -594,7 +599,7 @@ class Rouster
|
|
594
599
|
end
|
595
600
|
|
596
601
|
if cache
|
597
|
-
@
|
602
|
+
@logger.debug(sprintf('caching [users] at [%s]', Time.now.asctime))
|
598
603
|
self.deltas[:users] = res
|
599
604
|
self.cache[:users] = Time.now.to_i
|
600
605
|
end
|
data/lib/rouster/puppet.rb
CHANGED
@@ -20,10 +20,10 @@ class Rouster
|
|
20
20
|
if cache.true? and ! self.facts.nil?
|
21
21
|
|
22
22
|
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:facter]) > self.cache_timeout
|
23
|
-
@
|
23
|
+
@logger.debug(sprintf('invalidating [facter] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:facter]), self.cache_timeout))
|
24
24
|
self.facts = nil
|
25
25
|
else
|
26
|
-
@
|
26
|
+
@logger.debug(sprintf('using cached [facter] from [%s]', self.cache[:facter]))
|
27
27
|
return self.facts
|
28
28
|
end
|
29
29
|
|
@@ -38,7 +38,7 @@ class Rouster
|
|
38
38
|
end
|
39
39
|
|
40
40
|
if cache.true?
|
41
|
-
@
|
41
|
+
@logger.debug(sprintf('caching [facter] at [%s]', Time.now.asctime))
|
42
42
|
self.facts = res
|
43
43
|
self.cache[:facter] = Time.now.to_i
|
44
44
|
end
|
@@ -93,8 +93,17 @@ class Rouster
|
|
93
93
|
# parameters
|
94
94
|
# * [input] - string to look at, defaults to self.get_output()
|
95
95
|
def get_puppet_errors(input=nil)
|
96
|
-
str
|
97
|
-
errors
|
96
|
+
str = input.nil? ? self.get_output() : input
|
97
|
+
errors = nil
|
98
|
+
errors_27 = str.scan(/35merr:.*/)
|
99
|
+
errors_30 = str.scan(/Error:.*/)
|
100
|
+
|
101
|
+
# TODO this is a little less than efficient, don't scan for 3.0 if you found 2.7
|
102
|
+
if errors_27.size > 0
|
103
|
+
errors = errors_27
|
104
|
+
else
|
105
|
+
errors = errors_30
|
106
|
+
end
|
98
107
|
|
99
108
|
errors.empty? ? nil : errors
|
100
109
|
end
|
@@ -107,8 +116,17 @@ class Rouster
|
|
107
116
|
# parameters
|
108
117
|
# * [input] - string to look at, defaults to self.get_output()
|
109
118
|
def get_puppet_notices(input=nil)
|
110
|
-
str
|
111
|
-
notices
|
119
|
+
str = input.nil? ? self.get_output() : input
|
120
|
+
notices = nil
|
121
|
+
notices_27 = str.scan(/36mnotice:.*/) # not sure when this stopped working
|
122
|
+
notices_30 = str.scan(/Notice:.*/)
|
123
|
+
|
124
|
+
# TODO this is a little less than efficient, don't scan for 3.0 if you found 2.7
|
125
|
+
if notices_27.size > 0
|
126
|
+
notices = notices_27
|
127
|
+
else
|
128
|
+
notices = notices_30
|
129
|
+
end
|
112
130
|
|
113
131
|
notices.empty? ? nil : notices
|
114
132
|
end
|
@@ -151,7 +169,7 @@ class Rouster
|
|
151
169
|
cmd = 'hiera'
|
152
170
|
|
153
171
|
if facts.nil?
|
154
|
-
@
|
172
|
+
@logger.info('no facts provided, calling facter() automatically')
|
155
173
|
facts = self.facter()
|
156
174
|
end
|
157
175
|
|
@@ -279,14 +297,13 @@ class Rouster
|
|
279
297
|
end
|
280
298
|
end
|
281
299
|
|
282
|
-
|
283
300
|
results[:classes] = classes
|
284
301
|
results[:resources] = resources
|
285
302
|
|
286
303
|
results
|
287
304
|
end
|
288
305
|
|
289
|
-
# TODO: come up with better method names here.. remove_existing_certs() and
|
306
|
+
# TODO: come up with better method names here.. remove_existing_certs() and remove_specific_cert() are not very descriptive
|
290
307
|
|
291
308
|
##
|
292
309
|
# remove_existing_certs
|
@@ -307,16 +324,46 @@ class Rouster
|
|
307
324
|
hacky_break = false
|
308
325
|
|
309
326
|
except.each do |exception|
|
327
|
+
next if hacky_break
|
310
328
|
hacky_break = line.match(/#{exception}/)
|
329
|
+
end
|
311
330
|
|
312
|
-
|
331
|
+
next if hacky_break
|
332
|
+
|
333
|
+
host = $1 if line.match(/^\+\s"(.*?)"/)
|
334
|
+
|
335
|
+
hosts.push(host) unless host.nil? # only want to clear signed certs
|
336
|
+
end
|
337
|
+
|
338
|
+
hosts.each do |host|
|
339
|
+
self.run(sprintf('puppet cert --clean %s', host))
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
|
344
|
+
##
|
345
|
+
# remove_specific_cert
|
346
|
+
#
|
347
|
+
# ... removes a specific (or several specific) certificates, effectively the reverse of remove_existing_certs() - and again, really only useful when called on a puppet master
|
348
|
+
def remove_specific_cert (targets)
|
349
|
+
targets = targets.kind_of?(Array) ? targets : [targets]
|
350
|
+
hosts = Array.new()
|
351
|
+
|
352
|
+
res = self.run('puppet cert list --all')
|
353
|
+
|
354
|
+
res.each_line do |line|
|
355
|
+
hacky_break = true
|
356
|
+
|
357
|
+
targets.each do |target|
|
358
|
+
next unless hacky_break
|
359
|
+
hacky_break = line.match(/#{target}/)
|
313
360
|
end
|
314
361
|
|
315
362
|
next unless hacky_break
|
316
363
|
|
317
364
|
host = $1 if line.match(/^\+\s"(.*?)"/)
|
365
|
+
hosts.push(host) unless host.nil?
|
318
366
|
|
319
|
-
hosts.push(host) unless host.nil? # only want to clear signed certs
|
320
367
|
end
|
321
368
|
|
322
369
|
hosts.each do |host|
|
@@ -325,6 +372,7 @@ class Rouster
|
|
325
372
|
|
326
373
|
end
|
327
374
|
|
375
|
+
|
328
376
|
##
|
329
377
|
# run_puppet
|
330
378
|
#
|
@@ -355,7 +403,7 @@ class Rouster
|
|
355
403
|
# parameters
|
356
404
|
# * [mode] - method to run puppet, defaults to 'master'
|
357
405
|
# * [opts] - hash of additional options
|
358
|
-
def run_puppet(mode='master', passed_opts=
|
406
|
+
def run_puppet(mode='master', passed_opts={})
|
359
407
|
|
360
408
|
if mode.eql?('master')
|
361
409
|
opts = {
|
@@ -391,22 +439,33 @@ class Rouster
|
|
391
439
|
:additional_options => nil
|
392
440
|
}.merge!(passed_opts)
|
393
441
|
|
394
|
-
## validate
|
395
|
-
|
396
|
-
|
442
|
+
## validate arguments -- can do better here (:manifest_dir, :manifest_file)
|
443
|
+
puppet_version = self.get_puppet_version() # hiera_config specification is only supported in >3.0, but NOT required anywhere
|
444
|
+
|
445
|
+
if opts[:hiera_config]
|
446
|
+
if puppet_version > '3.0'
|
447
|
+
raise InternalError.new(sprintf('invalid hiera config specified[%s]', opts[:hiera_config])) unless self.is_file?(opts[:hiera_config])
|
448
|
+
else
|
449
|
+
@logger.error(sprintf('puppet version[%s] does not support --hiera_config, ignoring', puppet_version))
|
450
|
+
end
|
451
|
+
end
|
397
452
|
|
398
|
-
|
453
|
+
if opts[:module_dir]
|
454
|
+
raise InternalError.new(sprintf('invalid module dir specified[%s]', opts[:module_dir])) unless self.is_dir?(opts[:module_dir])
|
455
|
+
end
|
399
456
|
|
400
457
|
if opts[:manifest_file]
|
401
458
|
opts[:manifest_file] = opts[:manifest_file].class.eql?(Array) ? opts[:manifest_file] : [opts[:manifest_file]]
|
402
459
|
opts[:manifest_file].each do |file|
|
403
460
|
raise InternalError.new(sprintf('invalid manifest file specified[%s]', file)) unless self.is_file?(file)
|
404
461
|
|
405
|
-
cmd =
|
462
|
+
cmd = 'puppet apply'
|
463
|
+
cmd << sprintf(' --modulepath=%s', opts[:module_dir]) unless opts[:module_dir].nil?
|
464
|
+
cmd << sprintf(' --hiera_config=%s', opts[:hiera_config]) unless opts[:hiera_config].nil? or puppet_version < '3.0'
|
406
465
|
cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
|
407
466
|
cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
|
408
467
|
cmd << ' --pluginsync' if opts[:pluginsync]
|
409
|
-
cmd << opts[:additional_options] unless opts[:additional_options].nil?
|
468
|
+
cmd << sprintf(' %s', opts[:additional_options]) unless opts[:additional_options].nil?
|
410
469
|
cmd << sprintf(' %s', file)
|
411
470
|
|
412
471
|
self.run(cmd, opts[:expected_exitcode])
|
@@ -422,11 +481,13 @@ class Rouster
|
|
422
481
|
|
423
482
|
manifests.each do |m|
|
424
483
|
|
425
|
-
cmd =
|
484
|
+
cmd = 'puppet apply'
|
485
|
+
cmd << sprintf(' --modulepath=%s', opts[:module_dir]) unless opts[:module_dir].nil?
|
486
|
+
cmd << sprintf(' --hiera_config=%s', opts[:hiera_config]) unless opts[:hiera_config].nil? or puppet_version < '3.0'
|
426
487
|
cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
|
427
488
|
cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
|
428
489
|
cmd << ' --pluginsync' if opts[:pluginsync]
|
429
|
-
cmd << opts[:additional_options] unless opts[:additional_options].nil?
|
490
|
+
cmd << sprintf(' %s', opts[:additional_options]) unless opts[:additional_options].nil?
|
430
491
|
cmd << sprintf(' %s', m)
|
431
492
|
|
432
493
|
self.run(cmd, opts[:expected_exitcode])
|
data/lib/rouster/testing.rb
CHANGED
@@ -8,7 +8,99 @@ class Rouster
|
|
8
8
|
##
|
9
9
|
# validate_cron
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# given the name of the user who owns crontab, the cron's command to execute and a hash of expectations, returns true|false whether cron matches expectations
|
12
|
+
#
|
13
|
+
# parameters
|
14
|
+
# * <user> - name of user who owns crontab
|
15
|
+
# * <name> - the cron's command to execute
|
16
|
+
# * <expectations> - hash of expectations, see examples
|
17
|
+
# * <fail_fast> - return false immediately on any failure (default is false)
|
18
|
+
#
|
19
|
+
# example expectations:
|
20
|
+
# 'username',
|
21
|
+
# '/home/username/test.pl', {
|
22
|
+
# :ensure => 'present',
|
23
|
+
# :minute => 1,
|
24
|
+
# :hour => 0,
|
25
|
+
# :dom => '*',
|
26
|
+
# :mon => '*',
|
27
|
+
# :dow => '*',
|
28
|
+
# }
|
29
|
+
#
|
30
|
+
# 'root',
|
31
|
+
# 'printf > /var/log/apache/error_log', {
|
32
|
+
# :minute => 59,
|
33
|
+
# :hour => [8, 12],
|
34
|
+
# :dom => '*',
|
35
|
+
# :mon => '*',
|
36
|
+
# :dow => '*',
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# supported keys:
|
40
|
+
# * :exists|:ensure -- defaults to present if not specified
|
41
|
+
# * :minute
|
42
|
+
# * :hour
|
43
|
+
# * :dom -- day of month
|
44
|
+
# * :mon -- month
|
45
|
+
# * :dow -- day of week
|
46
|
+
# * :constrain
|
47
|
+
def validate_cron(user, name, expectations, fail_fast=false)
|
48
|
+
if user.nil?
|
49
|
+
raise InternalError.new('no user specified constraint')
|
50
|
+
end
|
51
|
+
|
52
|
+
crontabs = self.get_crontab(user)
|
53
|
+
|
54
|
+
if expectations[:ensure].nil? and expectations[:exists].nil?
|
55
|
+
expectations[:ensure] = 'present'
|
56
|
+
end
|
57
|
+
|
58
|
+
if expectations.has_key?(:constrain)
|
59
|
+
expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
|
60
|
+
|
61
|
+
expectations[:constrain].each do |constraint|
|
62
|
+
fact, expectation = constraint.split("\s")
|
63
|
+
unless meets_constraint?(fact, expectation)
|
64
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
expectations.delete(:constrain)
|
70
|
+
end
|
71
|
+
|
72
|
+
results = Hash.new()
|
73
|
+
local = nil
|
74
|
+
|
75
|
+
expectations.each do |k,v|
|
76
|
+
case k
|
77
|
+
when :ensure, :exists
|
78
|
+
if crontabs.has_key?(name)
|
79
|
+
if v.to_s.match(/absent|false/).nil?
|
80
|
+
local = true
|
81
|
+
else
|
82
|
+
local = false
|
83
|
+
end
|
84
|
+
else
|
85
|
+
local = v.to_s.match(/absent|false/).nil? ? false : true
|
86
|
+
end
|
87
|
+
when :minute, :hour, :dom, :mon, :dow
|
88
|
+
if crontabs.has_key?(name) and crontabs[name].has_key?(k) and crontabs[name][k].to_s.eql?(v.to_s)
|
89
|
+
local = true
|
90
|
+
else
|
91
|
+
local = false
|
92
|
+
end
|
93
|
+
else
|
94
|
+
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
95
|
+
end
|
96
|
+
|
97
|
+
return false if local.eql?(false) and fail_fast.eql?(true)
|
98
|
+
results[k] = local
|
99
|
+
end
|
100
|
+
|
101
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
102
|
+
results.find{|k,v| v.false? }.nil?
|
103
|
+
end
|
12
104
|
|
13
105
|
##
|
14
106
|
# validate_file
|
@@ -72,7 +164,7 @@ class Rouster
|
|
72
164
|
expectation = $2
|
73
165
|
|
74
166
|
unless meets_constraint?(fact, expectation)
|
75
|
-
@
|
167
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
76
168
|
return true
|
77
169
|
end
|
78
170
|
end
|
@@ -92,6 +184,14 @@ class Rouster
|
|
92
184
|
local = true
|
93
185
|
elsif properties.nil?
|
94
186
|
local = false
|
187
|
+
elsif v.to_s.match(/symlink|link/)
|
188
|
+
if expectations[:target].nil?
|
189
|
+
# don't validate the link path, just check whether we're a link
|
190
|
+
local = properties[:symlink?]
|
191
|
+
else
|
192
|
+
# validate the link path
|
193
|
+
local = properties[:target].eql?(expectations[:target])
|
194
|
+
end
|
95
195
|
else
|
96
196
|
case v
|
97
197
|
when 'dir', 'directory'
|
@@ -139,6 +239,18 @@ class Rouster
|
|
139
239
|
end
|
140
240
|
next if local.false?
|
141
241
|
end
|
242
|
+
when :notcontains, :doesntcontain # TODO determine the appropriate attribute title here
|
243
|
+
v = v.class.eql?(Array) ? v : [v]
|
244
|
+
v.each do |regex|
|
245
|
+
local = true
|
246
|
+
begin
|
247
|
+
self.run(sprintf("grep -c '%s' %s", regex, name))
|
248
|
+
local = false
|
249
|
+
rescue
|
250
|
+
local = true
|
251
|
+
end
|
252
|
+
next if local.false?
|
253
|
+
end
|
142
254
|
when :mode, :permissions
|
143
255
|
if properties.nil?
|
144
256
|
local = false
|
@@ -171,6 +283,8 @@ class Rouster
|
|
171
283
|
end
|
172
284
|
when :type
|
173
285
|
# noop allowing parse_catalog() output to be passed directly
|
286
|
+
when :target
|
287
|
+
# noop allowing ensure => 'link' / 'symlink' to specify their .. target
|
174
288
|
else
|
175
289
|
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
176
290
|
end
|
@@ -179,9 +293,8 @@ class Rouster
|
|
179
293
|
results[k] = local
|
180
294
|
end
|
181
295
|
|
182
|
-
@
|
296
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
183
297
|
results.find{|k,v| v.false? }.nil?
|
184
|
-
|
185
298
|
end
|
186
299
|
|
187
300
|
##
|
@@ -227,7 +340,7 @@ class Rouster
|
|
227
340
|
expectations[:constrain].each do |constraint|
|
228
341
|
fact, expectation = constraint.split("\s")
|
229
342
|
unless meets_constraint?(fact, expectation)
|
230
|
-
@
|
343
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
231
344
|
return true
|
232
345
|
end
|
233
346
|
end
|
@@ -276,7 +389,7 @@ class Rouster
|
|
276
389
|
results[k] = local
|
277
390
|
end
|
278
391
|
|
279
|
-
@
|
392
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
280
393
|
results.find{|k,v| v.false? }.nil?
|
281
394
|
end
|
282
395
|
|
@@ -322,7 +435,7 @@ class Rouster
|
|
322
435
|
expectations[:constrain].each do |constraint|
|
323
436
|
fact, expectation = constraint.split("\s")
|
324
437
|
unless meets_constraint?(fact, expectation)
|
325
|
-
@
|
438
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
326
439
|
return true
|
327
440
|
end
|
328
441
|
end
|
@@ -368,8 +481,8 @@ class Rouster
|
|
368
481
|
end
|
369
482
|
|
370
483
|
# TODO figure out a good way to allow access to the entire hash, not just boolean -- for now just print at an info level
|
371
|
-
@log.info(results)
|
372
484
|
|
485
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
373
486
|
results.find{|k,v| v.false? }.nil?
|
374
487
|
end
|
375
488
|
|
@@ -417,7 +530,7 @@ class Rouster
|
|
417
530
|
expectations[:constrain].each do |constraint|
|
418
531
|
fact, expectation = constraint.split("\s")
|
419
532
|
unless meets_constraint?(fact, expectation)
|
420
|
-
@
|
533
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
421
534
|
return true
|
422
535
|
end
|
423
536
|
end
|
@@ -461,8 +574,7 @@ class Rouster
|
|
461
574
|
results[k] = local
|
462
575
|
end
|
463
576
|
|
464
|
-
@
|
465
|
-
|
577
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
466
578
|
results.find{|k,v| v.false? }.nil?
|
467
579
|
end
|
468
580
|
|
@@ -503,7 +615,7 @@ class Rouster
|
|
503
615
|
expectations[:constrain].each do |constraint|
|
504
616
|
fact, expectation = constraint.split("\s")
|
505
617
|
unless meets_constraint?(fact, expectation)
|
506
|
-
@
|
618
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
507
619
|
return true
|
508
620
|
end
|
509
621
|
end
|
@@ -542,9 +654,8 @@ class Rouster
|
|
542
654
|
results[k] = local
|
543
655
|
end
|
544
656
|
|
545
|
-
@
|
657
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
546
658
|
results.find{|k,v| v.false? }.nil?
|
547
|
-
|
548
659
|
end
|
549
660
|
|
550
661
|
##
|
@@ -594,7 +705,7 @@ class Rouster
|
|
594
705
|
expectations[:constrain].each do |constraint|
|
595
706
|
fact, expectation = constraint.split("\s")
|
596
707
|
unless meets_constraint?(fact, expectation)
|
597
|
-
@
|
708
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
598
709
|
return true
|
599
710
|
end
|
600
711
|
end
|
@@ -663,9 +774,8 @@ class Rouster
|
|
663
774
|
results[k] = local
|
664
775
|
end
|
665
776
|
|
666
|
-
@
|
777
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
667
778
|
results.find{|k,v| v.false? }.nil?
|
668
|
-
|
669
779
|
end
|
670
780
|
|
671
781
|
## internal methods
|
@@ -687,7 +797,7 @@ class Rouster
|
|
687
797
|
|
688
798
|
unless self.respond_to?('facter') or self.respond_to?('hiera')
|
689
799
|
# if we haven't loaded puppet.rb, we won't have access to facts/hiera lookups
|
690
|
-
@
|
800
|
+
@logger.warn('using constraints without loading [rouster/puppet] will not work, forcing no-op')
|
691
801
|
return false
|
692
802
|
end
|
693
803
|
|
@@ -712,7 +822,7 @@ class Rouster
|
|
712
822
|
res = ! actual.to_s.match(/#{expectation}/).nil?
|
713
823
|
end
|
714
824
|
|
715
|
-
@
|
825
|
+
@logger.debug(sprintf('meets_constraint?(%s, %s): %s', key, expectation, res.nil?))
|
716
826
|
res
|
717
827
|
end
|
718
828
|
|