rouster 0.5 → 0.7
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 +4 -1
- data/.reek +63 -0
- data/.travis.yml +11 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +102 -0
- data/README.md +233 -7
- data/Rakefile +52 -34
- data/Vagrantfile +26 -8
- data/examples/aws.rb +85 -0
- data/examples/openstack.rb +61 -0
- data/examples/passthrough.rb +71 -0
- data/lib/rouster.rb +380 -262
- data/lib/rouster/deltas.rb +470 -138
- data/lib/rouster/puppet.rb +155 -26
- data/lib/rouster/testing.rb +205 -46
- data/lib/rouster/tests.rb +40 -11
- data/lib/rouster/vagrant.rb +311 -0
- data/path_helper.rb +3 -4
- data/plugins/aws.rb +347 -0
- data/plugins/openstack.rb +136 -0
- data/test/basic.rb +4 -1
- data/test/functional/deltas/test_get_crontab.rb +64 -2
- data/test/functional/deltas/test_get_groups.rb +74 -2
- data/test/functional/deltas/test_get_os.rb +68 -0
- data/test/functional/deltas/test_get_packages.rb +73 -6
- data/test/functional/deltas/test_get_ports.rb +26 -1
- data/test/functional/deltas/test_get_services.rb +43 -5
- data/test/functional/deltas/test_get_users.rb +35 -2
- data/test/functional/puppet/test_facter.rb +41 -1
- data/test/functional/test_caching.rb +2 -2
- data/test/functional/test_inspect.rb +1 -1
- data/test/functional/test_is_file.rb +17 -1
- data/test/functional/test_is_in_file.rb +40 -0
- data/test/functional/test_new.rb +233 -22
- data/test/functional/test_passthroughs.rb +94 -0
- data/test/functional/test_put.rb +2 -2
- data/test/functional/test_validate_file.rb +104 -3
- data/test/puppet/test_apply.rb +8 -6
- data/test/unit/puppet/resources/puppet_run_with_failed_exec +59 -0
- data/test/unit/puppet/resources/puppet_run_with_successful_exec +61 -0
- data/test/unit/puppet/test_get_puppet_star.rb +27 -4
- data/test/unit/puppet/test_puppet_parsing.rb +44 -0
- data/test/unit/test_new.rb +88 -0
- data/test/unit/test_parse_ls_string.rb +67 -0
- data/test/unit/testing/resources/osx-launchd +285 -0
- data/test/unit/testing/resources/rhel-systemd +46 -0
- data/test/unit/testing/resources/rhel-systemv +41 -0
- data/test/unit/testing/resources/rhel-upstart +20 -0
- data/test/unit/testing/test_get_services.rb +178 -0
- data/test/unit/testing/test_validate_cron.rb +78 -0
- data/test/unit/testing/test_validate_package.rb +36 -10
- data/test/unit/testing/test_validate_port.rb +5 -0
- metadata +42 -21
- data/test/puppet/test_roles.rb +0 -186
data/lib/rouster/puppet.rb
CHANGED
|
@@ -5,8 +5,6 @@ require 'net/https'
|
|
|
5
5
|
require 'socket'
|
|
6
6
|
require 'uri'
|
|
7
7
|
|
|
8
|
-
# TODO use @cache_timeout to invalidate data cached here
|
|
9
|
-
|
|
10
8
|
class Rouster
|
|
11
9
|
|
|
12
10
|
##
|
|
@@ -18,8 +16,17 @@ class Rouster
|
|
|
18
16
|
# * [cache] - whether to store/return cached facter data, if available
|
|
19
17
|
# * [custom_facts] - whether to include custom facts in return (uses -p argument)
|
|
20
18
|
def facter(cache=true, custom_facts=true)
|
|
19
|
+
|
|
21
20
|
if cache.true? and ! self.facts.nil?
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
if self.cache_timeout and self.cache_timeout.is_a?(Integer) and (Time.now.to_i - self.cache[:facter]) > self.cache_timeout
|
|
23
|
+
@logger.debug(sprintf('invalidating [facter] cache, was [%s] old, allowed [%s]', (Time.now.to_i - self.cache[:facter]), self.cache_timeout))
|
|
24
|
+
self.facts = nil
|
|
25
|
+
else
|
|
26
|
+
@logger.debug(sprintf('using cached [facter] from [%s]', self.cache[:facter]))
|
|
27
|
+
return self.facts
|
|
28
|
+
end
|
|
29
|
+
|
|
23
30
|
end
|
|
24
31
|
|
|
25
32
|
raw = self.run(sprintf('facter %s', custom_facts.true? ? '-p' : ''))
|
|
@@ -31,12 +38,35 @@ class Rouster
|
|
|
31
38
|
end
|
|
32
39
|
|
|
33
40
|
if cache.true?
|
|
41
|
+
@logger.debug(sprintf('caching [facter] at [%s]', Time.now.asctime))
|
|
34
42
|
self.facts = res
|
|
43
|
+
self.cache[:facter] = Time.now.to_i
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
res
|
|
38
47
|
end
|
|
39
48
|
|
|
49
|
+
##
|
|
50
|
+
# did_exec_fire?
|
|
51
|
+
#
|
|
52
|
+
# given the name of an Exec resource, parse the output from the most recent puppet run
|
|
53
|
+
# and return true/false based on whether the exec in question was fired
|
|
54
|
+
def did_exec_fire?(resource_name, puppet_run = self.last_puppet_run)
|
|
55
|
+
# Notice: /Stage[main]//Exec[foo]/returns: executed successfully
|
|
56
|
+
# Error: /Stage[main]//Exec[bar]/returns: change from notrun to 0 failed: Could not find command '/bin/bar'
|
|
57
|
+
matchers = [
|
|
58
|
+
'Notice: /Stage\[.*\]//Exec\[%s\]/returns: executed successfully',
|
|
59
|
+
'Error: /Stage\[.*\]//Exec\[%s\]/returns: change from notrun to 0 failed'
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
matchers.each do |m|
|
|
63
|
+
matcher = sprintf(m, resource_name)
|
|
64
|
+
return true if puppet_run.match(matcher)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
40
70
|
##
|
|
41
71
|
# get_catalog
|
|
42
72
|
#
|
|
@@ -84,8 +114,17 @@ class Rouster
|
|
|
84
114
|
# parameters
|
|
85
115
|
# * [input] - string to look at, defaults to self.get_output()
|
|
86
116
|
def get_puppet_errors(input=nil)
|
|
87
|
-
str
|
|
88
|
-
errors
|
|
117
|
+
str = input.nil? ? self.get_output() : input
|
|
118
|
+
errors = nil
|
|
119
|
+
errors_27 = str.scan(/35merr:.*/)
|
|
120
|
+
errors_30 = str.scan(/Error:.*/)
|
|
121
|
+
|
|
122
|
+
# TODO this is a little less than efficient, don't scan for 3.0 if you found 2.7
|
|
123
|
+
if errors_27.size > 0
|
|
124
|
+
errors = errors_27
|
|
125
|
+
else
|
|
126
|
+
errors = errors_30
|
|
127
|
+
end
|
|
89
128
|
|
|
90
129
|
errors.empty? ? nil : errors
|
|
91
130
|
end
|
|
@@ -98,8 +137,17 @@ class Rouster
|
|
|
98
137
|
# parameters
|
|
99
138
|
# * [input] - string to look at, defaults to self.get_output()
|
|
100
139
|
def get_puppet_notices(input=nil)
|
|
101
|
-
str
|
|
102
|
-
notices
|
|
140
|
+
str = input.nil? ? self.get_output() : input
|
|
141
|
+
notices = nil
|
|
142
|
+
notices_27 = str.scan(/36mnotice:.*/) # not sure when this stopped working
|
|
143
|
+
notices_30 = str.scan(/Notice:.*/)
|
|
144
|
+
|
|
145
|
+
# TODO this is a little less than efficient, don't scan for 3.0 if you found 2.7
|
|
146
|
+
if notices_27.size > 0
|
|
147
|
+
notices = notices_27
|
|
148
|
+
else
|
|
149
|
+
notices = notices_30
|
|
150
|
+
end
|
|
103
151
|
|
|
104
152
|
notices.empty? ? nil : notices
|
|
105
153
|
end
|
|
@@ -128,16 +176,41 @@ class Rouster
|
|
|
128
176
|
# returns hiera results from self
|
|
129
177
|
#
|
|
130
178
|
# parameters
|
|
131
|
-
# * <key>
|
|
132
|
-
# * [
|
|
133
|
-
|
|
179
|
+
# * <key> - hiera key to look up
|
|
180
|
+
# * [facts] - hash of facts to be used in hiera lookup (technically optional, but most useful hiera lookups are based on facts)
|
|
181
|
+
# * [config] - path to hiera configuration -- this is only optional if you have a hiera.yaml file in ~/vagrant, default option is correct for most puppet installations
|
|
182
|
+
# * [options] - any additional parameters to be passed to hiera directly
|
|
183
|
+
#
|
|
184
|
+
# note
|
|
185
|
+
# * if no facts are provided, facter() will be called - to really run hiera without facts, send an empty hash
|
|
186
|
+
# * this method is mostly useful on your puppet master, as your agents won't likely have /etc/puppet/hiera.yaml - to get data on another node, specify it's facts and call hiera on your ppm
|
|
187
|
+
def hiera(key, facts=nil, config='/etc/puppet/hiera.yaml', options=nil)
|
|
188
|
+
# TODO implement caching? where do we keep it? self.hiera{}? or self.deltas{} -- leaning towards #1
|
|
134
189
|
|
|
135
190
|
cmd = 'hiera'
|
|
191
|
+
|
|
192
|
+
if facts.nil?
|
|
193
|
+
@logger.info('no facts provided, calling facter() automatically')
|
|
194
|
+
facts = self.facter()
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
if facts.keys.size > 0
|
|
198
|
+
scope_file = sprintf('/tmp/rouster-hiera_scope.%s.%s.json', $$, Time.now.to_i)
|
|
199
|
+
|
|
200
|
+
File.write(scope_file, facts.to_json)
|
|
201
|
+
self.put(scope_file, scope_file)
|
|
202
|
+
File.delete(scope_file)
|
|
203
|
+
|
|
204
|
+
cmd << sprintf(' -j %s', scope_file)
|
|
205
|
+
end
|
|
206
|
+
|
|
136
207
|
cmd << sprintf(' -c %s', config) unless config.nil?
|
|
137
208
|
cmd << sprintf(' %s', options) unless options.nil?
|
|
138
209
|
cmd << sprintf(' %s', key)
|
|
139
210
|
|
|
140
|
-
self.run(cmd)
|
|
211
|
+
raw = self.run(cmd)
|
|
212
|
+
|
|
213
|
+
JSON.parse(raw)
|
|
141
214
|
end
|
|
142
215
|
|
|
143
216
|
##
|
|
@@ -245,13 +318,14 @@ class Rouster
|
|
|
245
318
|
end
|
|
246
319
|
end
|
|
247
320
|
|
|
248
|
-
|
|
249
321
|
results[:classes] = classes
|
|
250
322
|
results[:resources] = resources
|
|
251
323
|
|
|
252
324
|
results
|
|
253
325
|
end
|
|
254
326
|
|
|
327
|
+
# TODO: come up with better method names here.. remove_existing_certs() and remove_specific_cert() are not very descriptive
|
|
328
|
+
|
|
255
329
|
##
|
|
256
330
|
# remove_existing_certs
|
|
257
331
|
#
|
|
@@ -260,13 +334,23 @@ class Rouster
|
|
|
260
334
|
#
|
|
261
335
|
# parameters
|
|
262
336
|
# * <puppetmaster> - string/partial regex of certificate names to keep
|
|
263
|
-
def remove_existing_certs (
|
|
264
|
-
|
|
337
|
+
def remove_existing_certs (except)
|
|
338
|
+
except = except.kind_of?(Array) ? except : [except] # need to move from <>.class.eql? to <>.kind_of? in a number of places
|
|
339
|
+
hosts = Array.new()
|
|
265
340
|
|
|
266
341
|
res = self.run('puppet cert list --all')
|
|
267
342
|
|
|
343
|
+
# TODO refactor this away from the hacky_break
|
|
268
344
|
res.each_line do |line|
|
|
269
|
-
|
|
345
|
+
hacky_break = false
|
|
346
|
+
|
|
347
|
+
except.each do |exception|
|
|
348
|
+
next if hacky_break
|
|
349
|
+
hacky_break = line.match(/#{exception}/)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
next if hacky_break
|
|
353
|
+
|
|
270
354
|
host = $1 if line.match(/^\+\s"(.*?)"/)
|
|
271
355
|
|
|
272
356
|
hosts.push(host) unless host.nil? # only want to clear signed certs
|
|
@@ -278,6 +362,38 @@ class Rouster
|
|
|
278
362
|
|
|
279
363
|
end
|
|
280
364
|
|
|
365
|
+
##
|
|
366
|
+
# remove_specific_cert
|
|
367
|
+
#
|
|
368
|
+
# ... 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
|
|
369
|
+
def remove_specific_cert (targets)
|
|
370
|
+
targets = targets.kind_of?(Array) ? targets : [targets]
|
|
371
|
+
hosts = Array.new()
|
|
372
|
+
|
|
373
|
+
res = self.run('puppet cert list --all')
|
|
374
|
+
|
|
375
|
+
res.each_line do |line|
|
|
376
|
+
hacky_break = true
|
|
377
|
+
|
|
378
|
+
targets.each do |target|
|
|
379
|
+
next unless hacky_break
|
|
380
|
+
hacky_break = line.match(/#{target}/)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
next unless hacky_break
|
|
384
|
+
|
|
385
|
+
host = $1 if line.match(/^\+\s"(.*?)"/)
|
|
386
|
+
hosts.push(host) unless host.nil?
|
|
387
|
+
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
hosts.each do |host|
|
|
391
|
+
self.run(sprintf('puppet cert --clean %s', host))
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
|
|
281
397
|
##
|
|
282
398
|
# run_puppet
|
|
283
399
|
#
|
|
@@ -308,7 +424,7 @@ class Rouster
|
|
|
308
424
|
# parameters
|
|
309
425
|
# * [mode] - method to run puppet, defaults to 'master'
|
|
310
426
|
# * [opts] - hash of additional options
|
|
311
|
-
def run_puppet(mode='master', passed_opts=
|
|
427
|
+
def run_puppet(mode='master', passed_opts={})
|
|
312
428
|
|
|
313
429
|
if mode.eql?('master')
|
|
314
430
|
opts = {
|
|
@@ -344,25 +460,36 @@ class Rouster
|
|
|
344
460
|
:additional_options => nil
|
|
345
461
|
}.merge!(passed_opts)
|
|
346
462
|
|
|
347
|
-
## validate
|
|
348
|
-
|
|
349
|
-
raise InternalError.new(sprintf('invalid module dir specified[%s]', opts[:module_dir])) unless self.is_dir?(opts[:module_dir])
|
|
463
|
+
## validate arguments -- can do better here (:manifest_dir, :manifest_file)
|
|
464
|
+
puppet_version = self.get_puppet_version() # hiera_config specification is only supported in >3.0, but NOT required anywhere
|
|
350
465
|
|
|
351
|
-
|
|
466
|
+
if opts[:hiera_config]
|
|
467
|
+
if puppet_version > '3.0'
|
|
468
|
+
raise InternalError.new(sprintf('invalid hiera config specified[%s]', opts[:hiera_config])) unless self.is_file?(opts[:hiera_config])
|
|
469
|
+
else
|
|
470
|
+
@logger.error(sprintf('puppet version[%s] does not support --hiera_config, ignoring', puppet_version))
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
if opts[:module_dir]
|
|
475
|
+
raise InternalError.new(sprintf('invalid module dir specified[%s]', opts[:module_dir])) unless self.is_dir?(opts[:module_dir])
|
|
476
|
+
end
|
|
352
477
|
|
|
353
478
|
if opts[:manifest_file]
|
|
354
479
|
opts[:manifest_file] = opts[:manifest_file].class.eql?(Array) ? opts[:manifest_file] : [opts[:manifest_file]]
|
|
355
480
|
opts[:manifest_file].each do |file|
|
|
356
481
|
raise InternalError.new(sprintf('invalid manifest file specified[%s]', file)) unless self.is_file?(file)
|
|
357
482
|
|
|
358
|
-
cmd =
|
|
483
|
+
cmd = 'puppet apply --detailed-exitcodes'
|
|
484
|
+
cmd << sprintf(' --modulepath=%s', opts[:module_dir]) unless opts[:module_dir].nil?
|
|
485
|
+
cmd << sprintf(' --hiera_config=%s', opts[:hiera_config]) unless opts[:hiera_config].nil? or puppet_version < '3.0'
|
|
359
486
|
cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
|
|
360
487
|
cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
|
|
361
488
|
cmd << ' --pluginsync' if opts[:pluginsync]
|
|
362
|
-
cmd << opts[:additional_options] unless opts[:additional_options].nil?
|
|
489
|
+
cmd << sprintf(' %s', opts[:additional_options]) unless opts[:additional_options].nil?
|
|
363
490
|
cmd << sprintf(' %s', file)
|
|
364
491
|
|
|
365
|
-
self.run(cmd, opts[:expected_exitcode])
|
|
492
|
+
self.last_puppet_run = self.run(cmd, opts[:expected_exitcode])
|
|
366
493
|
end
|
|
367
494
|
end
|
|
368
495
|
|
|
@@ -375,14 +502,16 @@ class Rouster
|
|
|
375
502
|
|
|
376
503
|
manifests.each do |m|
|
|
377
504
|
|
|
378
|
-
cmd =
|
|
505
|
+
cmd = 'puppet apply --detailed-exitcodes'
|
|
506
|
+
cmd << sprintf(' --modulepath=%s', opts[:module_dir]) unless opts[:module_dir].nil?
|
|
507
|
+
cmd << sprintf(' --hiera_config=%s', opts[:hiera_config]) unless opts[:hiera_config].nil? or puppet_version < '3.0'
|
|
379
508
|
cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
|
|
380
509
|
cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
|
|
381
510
|
cmd << ' --pluginsync' if opts[:pluginsync]
|
|
382
|
-
cmd << opts[:additional_options] unless opts[:additional_options].nil?
|
|
511
|
+
cmd << sprintf(' %s', opts[:additional_options]) unless opts[:additional_options].nil?
|
|
383
512
|
cmd << sprintf(' %s', m)
|
|
384
513
|
|
|
385
|
-
self.run(cmd, opts[:expected_exitcode])
|
|
514
|
+
self.last_puppet_run = self.run(cmd, opts[:expected_exitcode])
|
|
386
515
|
end
|
|
387
516
|
|
|
388
517
|
end
|
data/lib/rouster/testing.rb
CHANGED
|
@@ -5,6 +5,103 @@ require 'rouster/deltas'
|
|
|
5
5
|
|
|
6
6
|
class Rouster
|
|
7
7
|
|
|
8
|
+
##
|
|
9
|
+
# validate_cron
|
|
10
|
+
#
|
|
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
|
|
104
|
+
|
|
8
105
|
##
|
|
9
106
|
# validate_file
|
|
10
107
|
#
|
|
@@ -57,9 +154,17 @@ class Rouster
|
|
|
57
154
|
expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
|
|
58
155
|
|
|
59
156
|
expectations[:constrain].each do |constraint|
|
|
60
|
-
|
|
157
|
+
valid = constraint.match(/^(\S+?)\s(.*)$/)
|
|
158
|
+
|
|
159
|
+
if valid.nil?
|
|
160
|
+
raise InternalError.new(sprintf('invalid constraint[%s] specified', constraint))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
fact = $1
|
|
164
|
+
expectation = $2
|
|
165
|
+
|
|
61
166
|
unless meets_constraint?(fact, expectation)
|
|
62
|
-
@
|
|
167
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
|
63
168
|
return true
|
|
64
169
|
end
|
|
65
170
|
end
|
|
@@ -79,6 +184,14 @@ class Rouster
|
|
|
79
184
|
local = true
|
|
80
185
|
elsif properties.nil?
|
|
81
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
|
|
82
195
|
else
|
|
83
196
|
case v
|
|
84
197
|
when 'dir', 'directory'
|
|
@@ -121,10 +234,22 @@ class Rouster
|
|
|
121
234
|
local = true
|
|
122
235
|
begin
|
|
123
236
|
self.run(sprintf("grep -c '%s' %s", regex, name))
|
|
124
|
-
rescue
|
|
237
|
+
rescue => e
|
|
125
238
|
local = false
|
|
126
239
|
end
|
|
127
|
-
|
|
240
|
+
break if local.false?
|
|
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 => e
|
|
250
|
+
local = true
|
|
251
|
+
end
|
|
252
|
+
break if local.false?
|
|
128
253
|
end
|
|
129
254
|
when :mode, :permissions
|
|
130
255
|
if properties.nil?
|
|
@@ -158,6 +283,8 @@ class Rouster
|
|
|
158
283
|
end
|
|
159
284
|
when :type
|
|
160
285
|
# noop allowing parse_catalog() output to be passed directly
|
|
286
|
+
when :target
|
|
287
|
+
# noop allowing ensure => 'link' / 'symlink' to specify their .. target
|
|
161
288
|
else
|
|
162
289
|
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
|
163
290
|
end
|
|
@@ -166,9 +293,8 @@ class Rouster
|
|
|
166
293
|
results[k] = local
|
|
167
294
|
end
|
|
168
295
|
|
|
169
|
-
@
|
|
296
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
|
170
297
|
results.find{|k,v| v.false? }.nil?
|
|
171
|
-
|
|
172
298
|
end
|
|
173
299
|
|
|
174
300
|
##
|
|
@@ -214,7 +340,7 @@ class Rouster
|
|
|
214
340
|
expectations[:constrain].each do |constraint|
|
|
215
341
|
fact, expectation = constraint.split("\s")
|
|
216
342
|
unless meets_constraint?(fact, expectation)
|
|
217
|
-
@
|
|
343
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
|
218
344
|
return true
|
|
219
345
|
end
|
|
220
346
|
end
|
|
@@ -263,7 +389,7 @@ class Rouster
|
|
|
263
389
|
results[k] = local
|
|
264
390
|
end
|
|
265
391
|
|
|
266
|
-
@
|
|
392
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
|
267
393
|
results.find{|k,v| v.false? }.nil?
|
|
268
394
|
end
|
|
269
395
|
|
|
@@ -309,7 +435,7 @@ class Rouster
|
|
|
309
435
|
expectations[:constrain].each do |constraint|
|
|
310
436
|
fact, expectation = constraint.split("\s")
|
|
311
437
|
unless meets_constraint?(fact, expectation)
|
|
312
|
-
@
|
|
438
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
|
313
439
|
return true
|
|
314
440
|
end
|
|
315
441
|
end
|
|
@@ -333,17 +459,39 @@ class Rouster
|
|
|
333
459
|
local = v.to_s.match(/absent|false/).nil? ? false : true
|
|
334
460
|
end
|
|
335
461
|
when :version
|
|
462
|
+
# TODO support determination based on multiple versions of the same package installed (?)
|
|
336
463
|
if packages.has_key?(name)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
464
|
+
|
|
465
|
+
lps = packages[name].is_a?(Array) ? packages[name] : [ packages[name] ]
|
|
466
|
+
|
|
467
|
+
lps.each do |lp|
|
|
468
|
+
if v.split("\s").size > 1
|
|
469
|
+
## generic comparator functionality
|
|
470
|
+
comp, expectation = v.split("\s")
|
|
471
|
+
local = generic_comparator(lp[:version], comp, expectation)
|
|
472
|
+
break unless local.eql?(true)
|
|
473
|
+
else
|
|
474
|
+
local = ! v.to_s.match(/#{lp[:version]}/).nil?
|
|
475
|
+
break unless local.eql?(true)
|
|
476
|
+
end
|
|
343
477
|
end
|
|
344
478
|
else
|
|
345
479
|
local = false
|
|
346
480
|
end
|
|
481
|
+
when :arch, :architecture
|
|
482
|
+
if packages.has_key?(name)
|
|
483
|
+
archs = []
|
|
484
|
+
lps = packages[name].is_a?(Array) ? packages[name] : [ packages[name] ]
|
|
485
|
+
lps.each { |p| archs << p[:arch] }
|
|
486
|
+
if v.is_a?(Array)
|
|
487
|
+
v.each do |arch|
|
|
488
|
+
local = archs.member?(arch)
|
|
489
|
+
break unless local.eql?(true) # fail fast - if we are looking for an arch that DNE, bail out
|
|
490
|
+
end
|
|
491
|
+
else
|
|
492
|
+
local = archs.member?(v)
|
|
493
|
+
end
|
|
494
|
+
end
|
|
347
495
|
when :type
|
|
348
496
|
# noop allowing parse_catalog() output to be passed directly
|
|
349
497
|
else
|
|
@@ -355,8 +503,8 @@ class Rouster
|
|
|
355
503
|
end
|
|
356
504
|
|
|
357
505
|
# TODO figure out a good way to allow access to the entire hash, not just boolean -- for now just print at an info level
|
|
358
|
-
@log.info(results)
|
|
359
506
|
|
|
507
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
|
360
508
|
results.find{|k,v| v.false? }.nil?
|
|
361
509
|
end
|
|
362
510
|
|
|
@@ -404,7 +552,7 @@ class Rouster
|
|
|
404
552
|
expectations[:constrain].each do |constraint|
|
|
405
553
|
fact, expectation = constraint.split("\s")
|
|
406
554
|
unless meets_constraint?(fact, expectation)
|
|
407
|
-
@
|
|
555
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
|
408
556
|
return true
|
|
409
557
|
end
|
|
410
558
|
end
|
|
@@ -433,13 +581,17 @@ class Rouster
|
|
|
433
581
|
|
|
434
582
|
when :address
|
|
435
583
|
lr = Array.new
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
local = ! lr.find{|e| e.true? }.nil? # this feels jankity
|
|
584
|
+
if ports[expectations[:protocol]][number]
|
|
585
|
+
addresses = ports[expectations[:protocol]][number][:address]
|
|
586
|
+
addresses.each_key do |address|
|
|
587
|
+
lr.push(address.eql?(v.to_s))
|
|
588
|
+
end
|
|
442
589
|
|
|
590
|
+
local = ! lr.find{|e| e.true? }.nil? # this feels jankity
|
|
591
|
+
else
|
|
592
|
+
# this port isn't open in the first place, won't match any addresses we expect to see it on
|
|
593
|
+
local = false
|
|
594
|
+
end
|
|
443
595
|
else
|
|
444
596
|
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
|
445
597
|
end
|
|
@@ -448,8 +600,7 @@ class Rouster
|
|
|
448
600
|
results[k] = local
|
|
449
601
|
end
|
|
450
602
|
|
|
451
|
-
@
|
|
452
|
-
|
|
603
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
|
453
604
|
results.find{|k,v| v.false? }.nil?
|
|
454
605
|
end
|
|
455
606
|
|
|
@@ -490,7 +641,7 @@ class Rouster
|
|
|
490
641
|
expectations[:constrain].each do |constraint|
|
|
491
642
|
fact, expectation = constraint.split("\s")
|
|
492
643
|
unless meets_constraint?(fact, expectation)
|
|
493
|
-
@
|
|
644
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
|
494
645
|
return true
|
|
495
646
|
end
|
|
496
647
|
end
|
|
@@ -529,9 +680,8 @@ class Rouster
|
|
|
529
680
|
results[k] = local
|
|
530
681
|
end
|
|
531
682
|
|
|
532
|
-
@
|
|
683
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
|
533
684
|
results.find{|k,v| v.false? }.nil?
|
|
534
|
-
|
|
535
685
|
end
|
|
536
686
|
|
|
537
687
|
##
|
|
@@ -581,7 +731,7 @@ class Rouster
|
|
|
581
731
|
expectations[:constrain].each do |constraint|
|
|
582
732
|
fact, expectation = constraint.split("\s")
|
|
583
733
|
unless meets_constraint?(fact, expectation)
|
|
584
|
-
@
|
|
734
|
+
@logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
|
585
735
|
return true
|
|
586
736
|
end
|
|
587
737
|
end
|
|
@@ -650,9 +800,8 @@ class Rouster
|
|
|
650
800
|
results[k] = local
|
|
651
801
|
end
|
|
652
802
|
|
|
653
|
-
@
|
|
803
|
+
@logger.info("#{name} [#{expectations}] => #{results}")
|
|
654
804
|
results.find{|k,v| v.false? }.nil?
|
|
655
|
-
|
|
656
805
|
end
|
|
657
806
|
|
|
658
807
|
## internal methods
|
|
@@ -665,32 +814,41 @@ class Rouster
|
|
|
665
814
|
# gets facts from node, and if fact expectation regex matches actual fact, returns true
|
|
666
815
|
#
|
|
667
816
|
# parameters
|
|
668
|
-
# * <
|
|
669
|
-
# * <expectation>
|
|
670
|
-
# * [cache]
|
|
671
|
-
def meets_constraint?(
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
817
|
+
# * <key> - fact/hiera key to look up (actual value)
|
|
818
|
+
# * <expectation> -
|
|
819
|
+
# * [cache] - boolean controlling whether facter lookups are cached
|
|
820
|
+
def meets_constraint?(key, expectation, cache=true)
|
|
821
|
+
|
|
822
|
+
expectation = expectation.to_s
|
|
823
|
+
|
|
824
|
+
unless self.respond_to?('facter') or self.respond_to?('hiera')
|
|
825
|
+
# if we haven't loaded puppet.rb, we won't have access to facts/hiera lookups
|
|
826
|
+
@logger.warn('using constraints without loading [rouster/puppet] will not work, forcing no-op')
|
|
676
827
|
return false
|
|
677
828
|
end
|
|
678
829
|
|
|
679
|
-
|
|
680
|
-
|
|
830
|
+
facts = self.facter(cache)
|
|
831
|
+
actual = nil
|
|
832
|
+
|
|
833
|
+
if facts[key]
|
|
834
|
+
actual = facts[key]
|
|
835
|
+
else
|
|
836
|
+
# value is not a fact, lets try to find it in hiera
|
|
837
|
+
# TODO how to handle the fact that this will really only work on the puppetmaster
|
|
838
|
+
actual = self.hiera(key, facts)
|
|
839
|
+
end
|
|
681
840
|
|
|
682
841
|
res = nil
|
|
842
|
+
|
|
683
843
|
if expectation.split("\s").size > 1
|
|
684
844
|
## generic comparator functionality
|
|
685
845
|
comp, expectation = expectation.split("\s")
|
|
686
|
-
|
|
687
|
-
res = generic_comparator(facts[fact], comp, expectation)
|
|
688
|
-
|
|
846
|
+
res = generic_comparator(actual, comp, expectation)
|
|
689
847
|
else
|
|
690
|
-
res = !
|
|
691
|
-
@log.debug(sprintf('meets_constraint?(%s, %s): %s', fact, expectation, res.nil?))
|
|
848
|
+
res = ! actual.to_s.match(/#{expectation}/).nil?
|
|
692
849
|
end
|
|
693
850
|
|
|
851
|
+
@logger.debug(sprintf('meets_constraint?(%s, %s): %s', key, expectation, res.nil?))
|
|
694
852
|
res
|
|
695
853
|
end
|
|
696
854
|
|
|
@@ -708,6 +866,7 @@ class Rouster
|
|
|
708
866
|
def generic_comparator(comparand1, comparator, comparand2)
|
|
709
867
|
|
|
710
868
|
# TODO rewrite this as an eval so we don't have to support everything..
|
|
869
|
+
# TODO come up with mechanism to determine when is it appropriate to call .to_i vs. otherwise -- comparisons will mainly be numerical (?), but need to support text matching too
|
|
711
870
|
case comparator
|
|
712
871
|
when '!='
|
|
713
872
|
# ugh
|