rouster 0.53 → 0.57
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|