rouster 0.7 → 0.41

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -3
  3. data/README.md +7 -241
  4. data/Rakefile +18 -55
  5. data/Vagrantfile +8 -26
  6. data/lib/rouster.rb +183 -404
  7. data/lib/rouster/deltas.rb +118 -577
  8. data/lib/rouster/puppet.rb +34 -209
  9. data/lib/rouster/testing.rb +59 -366
  10. data/lib/rouster/tests.rb +19 -70
  11. data/path_helper.rb +7 -5
  12. data/rouster.gemspec +1 -3
  13. data/test/basic.rb +1 -4
  14. data/test/functional/deltas/test_get_groups.rb +2 -74
  15. data/test/functional/deltas/test_get_packages.rb +4 -86
  16. data/test/functional/deltas/test_get_ports.rb +1 -26
  17. data/test/functional/deltas/test_get_services.rb +4 -43
  18. data/test/functional/deltas/test_get_users.rb +2 -35
  19. data/test/functional/puppet/test_facter.rb +1 -41
  20. data/test/functional/puppet/test_get_puppet_star.rb +68 -0
  21. data/test/functional/test_caching.rb +1 -5
  22. data/test/functional/test_dirs.rb +0 -25
  23. data/test/functional/test_get.rb +6 -10
  24. data/test/functional/test_inspect.rb +1 -1
  25. data/test/functional/test_is_file.rb +1 -17
  26. data/test/functional/test_new.rb +22 -233
  27. data/test/functional/test_put.rb +11 -9
  28. data/test/functional/test_restart.rb +4 -1
  29. data/test/functional/test_run.rb +3 -2
  30. data/test/puppet/test_apply.rb +11 -13
  31. data/test/puppet/test_roles.rb +173 -0
  32. data/test/unit/test_new.rb +0 -88
  33. data/test/unit/test_parse_ls_string.rb +0 -67
  34. data/test/unit/testing/test_validate_file.rb +47 -39
  35. data/test/unit/testing/test_validate_package.rb +10 -36
  36. metadata +6 -46
  37. data/.reek +0 -63
  38. data/.travis.yml +0 -11
  39. data/Gemfile +0 -17
  40. data/Gemfile.lock +0 -102
  41. data/LICENSE +0 -9
  42. data/examples/aws.rb +0 -85
  43. data/examples/openstack.rb +0 -61
  44. data/examples/passthrough.rb +0 -71
  45. data/lib/rouster/vagrant.rb +0 -311
  46. data/plugins/aws.rb +0 -347
  47. data/plugins/openstack.rb +0 -136
  48. data/test/functional/deltas/test_get_crontab.rb +0 -161
  49. data/test/functional/deltas/test_get_os.rb +0 -68
  50. data/test/functional/test_is_in_file.rb +0 -40
  51. data/test/functional/test_passthroughs.rb +0 -94
  52. data/test/functional/test_validate_file.rb +0 -131
  53. data/test/unit/puppet/resources/puppet_run_with_failed_exec +0 -59
  54. data/test/unit/puppet/resources/puppet_run_with_successful_exec +0 -61
  55. data/test/unit/puppet/test_get_puppet_star.rb +0 -91
  56. data/test/unit/puppet/test_puppet_parsing.rb +0 -44
  57. data/test/unit/testing/resources/osx-launchd +0 -285
  58. data/test/unit/testing/resources/rhel-systemd +0 -46
  59. data/test/unit/testing/resources/rhel-systemv +0 -41
  60. data/test/unit/testing/resources/rhel-upstart +0 -20
  61. data/test/unit/testing/test_get_services.rb +0 -178
  62. data/test/unit/testing/test_validate_cron.rb +0 -78
  63. data/test/unit/testing/test_validate_port.rb +0 -103
@@ -5,6 +5,8 @@ require 'net/https'
5
5
  require 'socket'
6
6
  require 'uri'
7
7
 
8
+ # TODO use @cache_timeout to invalidate data cached here
9
+
8
10
  class Rouster
9
11
 
10
12
  ##
@@ -16,17 +18,8 @@ class Rouster
16
18
  # * [cache] - whether to store/return cached facter data, if available
17
19
  # * [custom_facts] - whether to include custom facts in return (uses -p argument)
18
20
  def facter(cache=true, custom_facts=true)
19
-
20
21
  if cache.true? and ! self.facts.nil?
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
-
22
+ return self.facts
30
23
  end
31
24
 
32
25
  raw = self.run(sprintf('facter %s', custom_facts.true? ? '-p' : ''))
@@ -38,35 +31,12 @@ class Rouster
38
31
  end
39
32
 
40
33
  if cache.true?
41
- @logger.debug(sprintf('caching [facter] at [%s]', Time.now.asctime))
42
34
  self.facts = res
43
- self.cache[:facter] = Time.now.to_i
44
35
  end
45
36
 
46
37
  res
47
38
  end
48
39
 
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
-
70
40
  ##
71
41
  # get_catalog
72
42
  #
@@ -83,11 +53,7 @@ class Rouster
83
53
  # post https://<puppetmaster>/catalog/<node>?facts_format=pson&facts=<pson URL encoded> == ht to patrick@puppetlabs
84
54
  certname = hostname.nil? ? self.run('hostname --fqdn').chomp : hostname
85
55
  puppetmaster = puppetmaster.nil? ? 'puppet' : puppetmaster
86
- facts = facts.nil? ? self.facter() : facts
87
-
88
- %w(fqdn hostname operatingsystem operatingsystemrelease osfamily rubyversion).each do |required|
89
- raise ArgumentError.new(sprintf('missing required fact[%s]', required)) unless facts.has_key?(required)
90
- end
56
+ facts = facts.nil? ? self.facter() : facts # TODO check for presence of certain 'required' facts/datatype?
91
57
 
92
58
  raise InternalError.new('need to finish conversion of facts to PSON')
93
59
  facts.to_pson # this does not work, but needs to
@@ -114,17 +80,8 @@ class Rouster
114
80
  # parameters
115
81
  # * [input] - string to look at, defaults to self.get_output()
116
82
  def get_puppet_errors(input=nil)
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
83
+ str = input.nil? ? self.get_output() : input
84
+ errors = str.scan(/35merr:.*/)
128
85
 
129
86
  errors.empty? ? nil : errors
130
87
  end
@@ -137,17 +94,8 @@ class Rouster
137
94
  # parameters
138
95
  # * [input] - string to look at, defaults to self.get_output()
139
96
  def get_puppet_notices(input=nil)
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
97
+ str = input.nil? ? self.get_output() : input
98
+ notices = str.scan(/36mnotice:.*/)
151
99
 
152
100
  notices.empty? ? nil : notices
153
101
  end
@@ -176,41 +124,13 @@ class Rouster
176
124
  # returns hiera results from self
177
125
  #
178
126
  # parameters
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
189
-
190
- cmd = 'hiera'
127
+ # * <key> - hiera key to look up
128
+ # * [config] - path to hiera configuration -- this is only optional if you have a hiera.yaml file in ~/vagrant
129
+ def hiera(key, config=nil)
191
130
 
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)
131
+ # TODO implement this
132
+ raise NotImplementedError.new()
199
133
 
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
-
207
- cmd << sprintf(' -c %s', config) unless config.nil?
208
- cmd << sprintf(' %s', options) unless options.nil?
209
- cmd << sprintf(' %s', key)
210
-
211
- raw = self.run(cmd)
212
-
213
- JSON.parse(raw)
214
134
  end
215
135
 
216
136
  ##
@@ -310,6 +230,7 @@ class Rouster
310
230
  end
311
231
 
312
232
  # remove all nil references
233
+ # TODO make this more rubyish
313
234
  resources.each_key do |name|
314
235
  resources[name].each_pair do |k,v|
315
236
  unless v
@@ -318,14 +239,13 @@ class Rouster
318
239
  end
319
240
  end
320
241
 
242
+
321
243
  results[:classes] = classes
322
244
  results[:resources] = resources
323
245
 
324
246
  results
325
247
  end
326
248
 
327
- # TODO: come up with better method names here.. remove_existing_certs() and remove_specific_cert() are not very descriptive
328
-
329
249
  ##
330
250
  # remove_existing_certs
331
251
  #
@@ -334,57 +254,16 @@ class Rouster
334
254
  #
335
255
  # parameters
336
256
  # * <puppetmaster> - string/partial regex of certificate names to keep
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()
340
-
341
- res = self.run('puppet cert list --all')
342
-
343
- # TODO refactor this away from the hacky_break
344
- res.each_line do |line|
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
-
354
- host = $1 if line.match(/^\+\s"(.*?)"/)
355
-
356
- hosts.push(host) unless host.nil? # only want to clear signed certs
357
- end
358
-
359
- hosts.each do |host|
360
- self.run(sprintf('puppet cert --clean %s', host))
361
- end
362
-
363
- end
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]
257
+ def remove_existing_certs (puppetmaster)
371
258
  hosts = Array.new()
372
259
 
373
260
  res = self.run('puppet cert list --all')
374
261
 
375
262
  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
-
263
+ next if line.match(/#{puppetmaster}/)
385
264
  host = $1 if line.match(/^\+\s"(.*?)"/)
386
- hosts.push(host) unless host.nil?
387
265
 
266
+ hosts.push(host)
388
267
  end
389
268
 
390
269
  hosts.each do |host|
@@ -393,22 +272,15 @@ class Rouster
393
272
 
394
273
  end
395
274
 
396
-
397
275
  ##
398
276
  # run_puppet
399
277
  #
400
278
  # ... runs puppet on self, returns nothing
401
279
  #
402
280
  # currently supports 2 methods of running puppet:
403
- # * master - runs 'puppet agent -t'
281
+ # * master - runs '/sbin/service puppet once -t'
404
282
  # * supported options
405
283
  # * expected_exitcode - string/integer/array of acceptable exit code(s)
406
- # * configtimeout - string/integer of the acceptable configtimeout value
407
- # * environment - string of the environment to use
408
- # * certname - string of the certname to use in place of the host fqdn
409
- # * pluginsync - bool value if pluginsync should be used
410
- # * server - string value of the puppetmasters fqdn / ip
411
- # * additional_options - string of various options that would be passed to puppet
412
284
  # * masterless - runs 'puppet apply <options>' after determining version of puppet running and adjusting arguments
413
285
  # * supported options
414
286
  # * expected_exitcode - string/integer/array of acceptable exit code(s)
@@ -416,80 +288,41 @@ class Rouster
416
288
  # * manifest_file - string/array of strings of paths to manifest(s) to apply
417
289
  # * manifest_dir - string/array of strings of directories containing manifest(s) to apply - is recursive
418
290
  # * module_dir - path to module directory -- currently a required parameter, is this correct?
419
- # * environment - string of the environment to use (default: production)
420
- # * certname - string of the certname to use in place of the host fqdn (default: unused)
421
- # * pluginsync - bool value if pluginsync should be used (default: true)
422
- # * additional_options - string of various options that would be passed to puppet
423
291
  #
424
292
  # parameters
425
293
  # * [mode] - method to run puppet, defaults to 'master'
426
294
  # * [opts] - hash of additional options
427
- def run_puppet(mode='master', passed_opts={})
295
+ def run_puppet(mode='master', passed_opts=nil)
428
296
 
429
297
  if mode.eql?('master')
430
298
  opts = {
431
- :expected_exitcode => 0,
432
- :configtimeout => nil,
433
- :environment => nil,
434
- :certname => nil,
435
- :server => nil,
436
- :pluginsync => false,
437
- :additional_options => nil
299
+ :expected_exitcode => 0
438
300
  }.merge!(passed_opts)
439
301
 
440
- cmd = 'puppet agent -t'
441
- cmd << sprintf(' --configtimeout %s', opts[:configtimeout]) unless opts[:configtimeout].nil?
442
- cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
443
- cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
444
- cmd << sprintf(' --server %s', opts[:server]) unless opts[:server].nil?
445
- cmd << ' --pluginsync' if opts[:pluginsync]
446
- cmd << opts[:additional_options] unless opts[:additional_options].nil?
447
-
448
- self.run(cmd, opts[:expected_exitcode])
302
+ self.run('/sbin/service puppet once -t', opts[:expected_exitcode])
449
303
 
450
304
  elsif mode.eql?('masterless')
451
305
  opts = {
452
- :expected_exitcode => 2,
453
- :hiera_config => nil,
454
- :manifest_file => nil, # can be a string or array, will 'puppet apply' each
455
- :manifest_dir => nil, # can be a string or array, will 'puppet apply' each module in the dir (recursively)
456
- :module_dir => nil,
457
- :environment => nil,
458
- :certname => nil,
459
- :pluginsync => false,
460
- :additional_options => nil
306
+ :expected_exitcode => 2,
307
+ :hiera_config => nil,
308
+ :manifest_file => nil, # can be a string or array, will 'puppet apply' each
309
+ :manifest_dir => nil, # can be a string or array, will 'puppet apply' each module in the dir (recursively)
310
+ :module_dir => nil
461
311
  }.merge!(passed_opts)
462
312
 
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
313
+ ## validate required arguments
314
+ raise InternalError.new(sprintf('invalid hiera config specified[%s]', opts[:hiera_config])) unless self.is_file?(opts[:hiera_config])
315
+ raise InternalError.new(sprintf('invalid module dir specified[%s]', opts[:module_dir])) unless self.is_dir?(opts[:module_dir])
465
316
 
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
317
+ puppet_version = self.get_puppet_version() # hiera_config specification is only supported in >3.0
477
318
 
478
319
  if opts[:manifest_file]
479
320
  opts[:manifest_file] = opts[:manifest_file].class.eql?(Array) ? opts[:manifest_file] : [opts[:manifest_file]]
480
321
  opts[:manifest_file].each do |file|
481
322
  raise InternalError.new(sprintf('invalid manifest file specified[%s]', file)) unless self.is_file?(file)
482
323
 
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'
486
- cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
487
- cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
488
- cmd << ' --pluginsync' if opts[:pluginsync]
489
- cmd << sprintf(' %s', opts[:additional_options]) unless opts[:additional_options].nil?
490
- cmd << sprintf(' %s', file)
324
+ self.run(sprintf('puppet apply %s --modulepath=%s %s', (puppet_version > '3.0') ? "--hiera_config=#{opts[:hiera_config]}" : '', opts[:module_dir], file), opts[:expected_exitcode])
491
325
 
492
- self.last_puppet_run = self.run(cmd, opts[:expected_exitcode])
493
326
  end
494
327
  end
495
328
 
@@ -502,16 +335,8 @@ class Rouster
502
335
 
503
336
  manifests.each do |m|
504
337
 
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'
508
- cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
509
- cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
510
- cmd << ' --pluginsync' if opts[:pluginsync]
511
- cmd << sprintf(' %s', opts[:additional_options]) unless opts[:additional_options].nil?
512
- cmd << sprintf(' %s', m)
338
+ self.run(sprintf('puppet apply %s --modulepath=%s %s', (puppet_version > '3.0') ? "--hiera_config=#{opts[:hiera_config]}" : '', opts[:module_dir], m), opts[:expected_exitcode])
513
339
 
514
- self.last_puppet_run = self.run(cmd, opts[:expected_exitcode])
515
340
  end
516
341
 
517
342
  end
@@ -524,4 +349,4 @@ class Rouster
524
349
 
525
350
  end
526
351
 
527
- end
352
+ end
@@ -5,103 +5,6 @@ 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
-
105
8
  ##
106
9
  # validate_file
107
10
  #
@@ -110,7 +13,6 @@ class Rouster
110
13
  # parameters
111
14
  # * <name> - full file name or relative (to ~vagrant)
112
15
  # * <expectations> - hash of expectations, see examples
113
- # * <fail_fast> - return false immediately on any failure (default is false)
114
16
  #
115
17
  # example expectations:
116
18
  # '/sys/kernel/mm/redhat_transparent_hugepage/enabled', {
@@ -144,9 +46,9 @@ class Rouster
144
46
  # * :owner
145
47
  # * :group
146
48
  # * :constrain
147
- def validate_file(name, expectations, fail_fast=false, cache=false)
49
+ def validate_file(name, expectations, cache=false)
148
50
 
149
- if expectations[:ensure].nil? and expectations[:exists].nil? and expectations[:directory].nil? and expectations[:file?].nil?
51
+ if expectations[:ensure].nil? and expectations[:exists].nil?
150
52
  expectations[:ensure] = 'file'
151
53
  end
152
54
 
@@ -154,17 +56,9 @@ class Rouster
154
56
  expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
155
57
 
156
58
  expectations[:constrain].each do |constraint|
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
-
59
+ fact, expectation = constraint.split("\s")
166
60
  unless meets_constraint?(fact, expectation)
167
- @logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
61
+ @log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
168
62
  return true
169
63
  end
170
64
  end
@@ -172,7 +66,7 @@ class Rouster
172
66
  expectations.delete(:constrain)
173
67
  end
174
68
 
175
- properties = (expectations[:ensure].eql?('file')) ? self.file(name, cache) : self.dir(name, cache)
69
+ properties = (! expectations[:ensure].eql?('file')) ? self.file(name, cache) : self.dir(name, cache)
176
70
  results = Hash.new()
177
71
  local = nil
178
72
 
@@ -184,14 +78,6 @@ class Rouster
184
78
  local = true
185
79
  elsif properties.nil?
186
80
  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
195
81
  else
196
82
  case v
197
83
  when 'dir', 'directory'
@@ -223,7 +109,7 @@ class Rouster
223
109
  if properties[:directory?]
224
110
  local = v.to_s.match(/absent|false/).nil?
225
111
  else
226
- local = ! v.to_s.match(/absent|false/).nil?
112
+ local = true
227
113
  end
228
114
  else
229
115
  local = false
@@ -234,22 +120,10 @@ class Rouster
234
120
  local = true
235
121
  begin
236
122
  self.run(sprintf("grep -c '%s' %s", regex, name))
237
- rescue => e
123
+ rescue
238
124
  local = false
239
125
  end
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?
126
+ next if local.false?
253
127
  end
254
128
  when :mode, :permissions
255
129
  if properties.nil?
@@ -282,19 +156,17 @@ class Rouster
282
156
  local = false
283
157
  end
284
158
  when :type
285
- # noop allowing parse_catalog() output to be passed directly
286
- when :target
287
- # noop allowing ensure => 'link' / 'symlink' to specify their .. target
159
+ # noop
288
160
  else
289
161
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
290
162
  end
291
163
 
292
- return false if local.eql?(false) and fail_fast.eql?(true)
293
164
  results[k] = local
294
165
  end
295
166
 
296
- @logger.info("#{name} [#{expectations}] => #{results}")
167
+ @log.info(results)
297
168
  results.find{|k,v| v.false? }.nil?
169
+
298
170
  end
299
171
 
300
172
  ##
@@ -305,7 +177,6 @@ class Rouster
305
177
  # paramaters
306
178
  # * <name> - group name
307
179
  # * <expectations> - hash of expectations, see examples
308
- # * <fail_fast> - return false immediately on any failure (default is false)
309
180
  #
310
181
  # example expectations:
311
182
  # 'root', {
@@ -327,7 +198,7 @@ class Rouster
327
198
  # * :gid
328
199
  # * :user|:users (string or array)
329
200
  # * :constrain
330
- def validate_group(name, expectations, fail_fast=false)
201
+ def validate_group(name, expectations)
331
202
  groups = self.get_groups(true)
332
203
 
333
204
  if expectations[:ensure].nil? and expectations[:exists].nil?
@@ -340,7 +211,7 @@ class Rouster
340
211
  expectations[:constrain].each do |constraint|
341
212
  fact, expectation = constraint.split("\s")
342
213
  unless meets_constraint?(fact, expectation)
343
- @logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
214
+ @log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
344
215
  return true
345
216
  end
346
217
  end
@@ -364,33 +235,25 @@ class Rouster
364
235
  local = v.to_s.match(/absent|false/).nil? ? false : true
365
236
  end
366
237
  when :gid
367
- if groups[name].is_a?(Hash) and groups[name].has_key?(:gid)
368
- local = v.to_s.eql?(groups[name][:gid].to_s)
369
- else
370
- local = false
371
- end
238
+ local = v.to_s.eql?(groups[name][:gid].to_s)
372
239
  when :user, :users
373
240
  v = v.class.eql?(Array) ? v : [v]
374
241
  v.each do |user|
375
- if groups[name].is_a?(Hash) and groups[name].has_key?(:users)
376
- local = groups[name][:users].member?(user)
377
- else
378
- local = false
379
- end
242
+ local = groups[name][:users].member?(user)
380
243
  break unless local.true? # need to make the return value smarter if we want to store data on which user failed
381
244
  end
382
245
  when :type
383
- # noop allowing parse_catalog() output to be passed directly
246
+ # noop
384
247
  else
385
248
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
386
249
  end
387
250
 
388
- return false if local.eql?(false) and fail_fast.eql?(true)
389
251
  results[k] = local
390
252
  end
391
253
 
392
- @logger.info("#{name} [#{expectations}] => #{results}")
254
+ @log.info(results.pretty_print_inspect())
393
255
  results.find{|k,v| v.false? }.nil?
256
+
394
257
  end
395
258
 
396
259
  ##
@@ -401,7 +264,6 @@ class Rouster
401
264
  # parameters
402
265
  # * <name> - package name
403
266
  # * <expectations> - hash of expectations, see examples
404
- # * <fail_fast> - return false immediately on any failure (default is false)
405
267
  #
406
268
  # example expectations:
407
269
  # 'perl-Net-SNMP', {
@@ -422,7 +284,7 @@ class Rouster
422
284
  # * :exists|ensure
423
285
  # * :version (literal or basic comparison)
424
286
  # * :constrain
425
- def validate_package(name, expectations, fail_fast=false)
287
+ def validate_package(name, expectations)
426
288
  packages = self.get_packages(true)
427
289
 
428
290
  if expectations[:ensure].nil? and expectations[:exists].nil?
@@ -435,7 +297,7 @@ class Rouster
435
297
  expectations[:constrain].each do |constraint|
436
298
  fact, expectation = constraint.split("\s")
437
299
  unless meets_constraint?(fact, expectation)
438
- @logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
300
+ @log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
439
301
  return true
440
302
  end
441
303
  end
@@ -459,148 +321,26 @@ class Rouster
459
321
  local = v.to_s.match(/absent|false/).nil? ? false : true
460
322
  end
461
323
  when :version
462
- # TODO support determination based on multiple versions of the same package installed (?)
463
- if packages.has_key?(name)
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
477
- end
324
+ if v.split("\s").size > 1
325
+ ## generic comparator functionality
326
+ comp, expectation = v.split("\s")
327
+ local = generic_comparator(packages[name], comp, expectation)
478
328
  else
479
- local = false
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
329
+ local = ! v.to_s.match(/#{packages[name]}/).nil?
494
330
  end
495
331
  when :type
496
- # noop allowing parse_catalog() output to be passed directly
332
+ # noop
497
333
  else
498
334
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
499
335
  end
500
336
 
501
- return false if local.eql?(false) and fail_fast.eql?(true)
502
337
  results[k] = local
503
338
  end
504
339
 
505
340
  # TODO figure out a good way to allow access to the entire hash, not just boolean -- for now just print at an info level
341
+ @log.info(results)
506
342
 
507
- @logger.info("#{name} [#{expectations}] => #{results}")
508
- results.find{|k,v| v.false? }.nil?
509
- end
510
-
511
- # given a port nnumber and a hash of expectations, returns true|false whether port meets expectations
512
- #
513
- # parameters
514
- # * <number> - port number
515
- # * <expectations> - hash of expectations, see examples
516
- #
517
- # example expectations:
518
- # '22', {
519
- # :ensure => 'active',
520
- # :protocol => 'tcp',
521
- # :address => '0.0.0.0'
522
- # },
523
- #
524
- # '1234', {
525
- # :ensure => 'open',
526
- # :address => '*',
527
- # :constrain => 'is_virtual false'
528
- # }
529
- #
530
- # supported keys:
531
- # * :exists|ensure|state
532
- # * :address
533
- # * :protocol|proto
534
- # * :constrain
535
- def validate_port(number, expectations, fail_fast=false)
536
- number = number.to_s
537
- ports = self.get_ports(true)
538
-
539
- if expectations[:ensure].nil? and expectations[:exists].nil? and expectations[:state].nil?
540
- expectations[:ensure] = 'present'
541
- end
542
-
543
- if expectations[:protocol].nil? and expectations[:proto].nil?
544
- expectations[:protocol] = 'tcp'
545
- elsif ! expectations[:proto].nil?
546
- expectations[:protocol] = expectations[:proto]
547
- end
548
-
549
- if expectations.has_key?(:constrain)
550
- expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
551
-
552
- expectations[:constrain].each do |constraint|
553
- fact, expectation = constraint.split("\s")
554
- unless meets_constraint?(fact, expectation)
555
- @logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
556
- return true
557
- end
558
- end
559
-
560
- expectations.delete(:constrain)
561
- end
562
-
563
- results = Hash.new()
564
- local = nil
565
-
566
- expectations.each do |k,v|
567
- case k
568
- when :ensure, :exists, :state
569
- if v.to_s.match(/absent|false|open/)
570
- local = ports[expectations[:protocol]][number].nil?
571
- else
572
- local = ! ports[expectations[:protocol]][number].nil?
573
- end
574
- when :protocol, :proto
575
- # TODO rewrite this in a less hacky way
576
- if expectations[:ensure].to_s.match(/absent|false|open/) or expectations[:exists].to_s.match(/absent|false|open/) or expectations[:state].to_s.match(/absent|false|open/)
577
- local = true
578
- else
579
- local = ports[v].has_key?(number)
580
- end
581
-
582
- when :address
583
- lr = Array.new
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
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
595
- else
596
- raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
597
- end
598
-
599
- return false if local.eql?(false) and fail_fast.eql?(true)
600
- results[k] = local
601
- end
602
-
603
- @logger.info("#{name} [#{expectations}] => #{results}")
343
+ # TODO should we implement a fail fast method? at least an option
604
344
  results.find{|k,v| v.false? }.nil?
605
345
  end
606
346
 
@@ -612,7 +352,6 @@ class Rouster
612
352
  # parameters
613
353
  # * <name> - service name
614
354
  # * <expectations> - hash of expectations, see examples
615
- # * <fail_fast> - return false immediately on any failure (default is false)
616
355
  #
617
356
  # example expectations:
618
357
  # 'ntp', {
@@ -628,7 +367,7 @@ class Rouster
628
367
  # * :exists|:ensure
629
368
  # * :state,:status
630
369
  # * :constrain
631
- def validate_service(name, expectations, fail_fast=false)
370
+ def validate_service(name, expectations)
632
371
  services = self.get_services(true)
633
372
 
634
373
  if expectations[:ensure].nil? and expectations[:exists].nil?
@@ -641,7 +380,7 @@ class Rouster
641
380
  expectations[:constrain].each do |constraint|
642
381
  fact, expectation = constraint.split("\s")
643
382
  unless meets_constraint?(fact, expectation)
644
- @logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
383
+ @log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
645
384
  return true
646
385
  end
647
386
  end
@@ -665,23 +404,19 @@ class Rouster
665
404
  local = v.to_s.match(/absent|false/).nil? ? false : true
666
405
  end
667
406
  when :state, :status
668
- if services.has_key?(name)
669
- local = ! v.match(/#{services[name]}/).nil?
670
- else
671
- local = false
672
- end
407
+ local = ! v.match(/#{services[name]}/).nil?
673
408
  when :type
674
- # noop allowing parse_catalog() output to be passed directly
409
+ # noop
675
410
  else
676
411
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
677
412
  end
678
413
 
679
- return false if local.eql?(false) and fail_fast.eql?(true)
680
414
  results[k] = local
681
415
  end
682
416
 
683
- @logger.info("#{name} [#{expectations}] => #{results}")
417
+ @log.info(results.pretty_print_inspect())
684
418
  results.find{|k,v| v.false? }.nil?
419
+
685
420
  end
686
421
 
687
422
  ##
@@ -692,7 +427,6 @@ class Rouster
692
427
  # parameters
693
428
  # * <name> - user name
694
429
  # * <expectations> - hash of expectations, see examples
695
- # * <fail_fast> - return false immediately on any failure (default is false)
696
430
  #
697
431
  # example expectations:
698
432
  # 'root' => {
@@ -718,7 +452,7 @@ class Rouster
718
452
  # * :uid
719
453
  # * :gid
720
454
  # * :constrain
721
- def validate_user(name, expectations, fail_fast=false)
455
+ def validate_user(name, expectations)
722
456
  users = self.get_users(true)
723
457
 
724
458
  if expectations[:ensure].nil? and expectations[:exists].nil?
@@ -731,7 +465,7 @@ class Rouster
731
465
  expectations[:constrain].each do |constraint|
732
466
  fact, expectation = constraint.split("\s")
733
467
  unless meets_constraint?(fact, expectation)
734
- @logger.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
468
+ @log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
735
469
  return true
736
470
  end
737
471
  end
@@ -761,47 +495,27 @@ class Rouster
761
495
  break unless local.true?
762
496
  end
763
497
  when :gid
764
- if users[name].is_a?(Hash) and users[name].has_key?(:gid)
765
- local = v.to_i.eql?(users[name][:gid].to_i)
766
- else
767
- local = false
768
- end
498
+ local = v.to_i.eql?(users[name][:gid].to_i)
769
499
  when :home
770
- if users[name].is_a?(Hash) and users[name].has_key?(:home)
771
- local = ! v.match(/#{users[name][:home]}/).nil?
772
- else
773
- local = false
774
- end
500
+ local = ! v.match(/#{users[name][:home]}/).nil?
775
501
  when :home_exists
776
- if users[name].is_a?(Hash) and users[name].has_key?(:home_exists)
777
- local = ! v.to_s.match(/#{users[name][:home_exists].to_s}/).nil?
778
- else
779
- local = false
780
- end
502
+ local = ! v.to_s.match(/#{users[name][:home_exists].to_s}/).nil?
781
503
  when :shell
782
- if users[name].is_a?(Hash) and users[name].has_key?(:shell)
783
- local = ! v.match(/#{users[name][:shell]}/).nil?
784
- else
785
- local = false
786
- end
504
+ local = ! v.match(/#{users[name][:shell]}/).nil?
787
505
  when :uid
788
- if users[name].is_a?(Hash) and users[name].has_key?(:uid)
789
- local = v.to_i.eql?(users[name][:uid].to_i)
790
- else
791
- local = false
792
- end
506
+ local = v.to_i.eql?(users[name][:uid].to_i)
793
507
  when :type
794
- # noop allowing parse_catalog() output to be passed directly
508
+ # noop
795
509
  else
796
510
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
797
511
  end
798
512
 
799
- return false if local.eql?(false) and fail_fast.eql?(true)
800
513
  results[k] = local
801
514
  end
802
515
 
803
- @logger.info("#{name} [#{expectations}] => #{results}")
516
+ @log.info(results.pretty_print_inspect())
804
517
  results.find{|k,v| v.false? }.nil?
518
+
805
519
  end
806
520
 
807
521
  ## internal methods
@@ -814,59 +528,38 @@ class Rouster
814
528
  # gets facts from node, and if fact expectation regex matches actual fact, returns true
815
529
  #
816
530
  # parameters
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')
531
+ # * <fact> - fact
532
+ # * <expectation>
533
+ # * [cache]
534
+ def meets_constraint?(fact, expectation, cache=true)
535
+
536
+ unless self.respond_to?('facter')
537
+ # if we haven't loaded puppet.rb, we won't have access to facts
538
+ @log.warn('using constraints without loading [rouster/puppet] will not work, forcing no-op')
827
539
  return false
828
540
  end
829
541
 
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
542
+ expectation = expectation.to_s
543
+ facts = self.facter(cache)
840
544
 
841
545
  res = nil
842
-
843
546
  if expectation.split("\s").size > 1
844
547
  ## generic comparator functionality
845
548
  comp, expectation = expectation.split("\s")
846
- res = generic_comparator(actual, comp, expectation)
549
+
550
+ res = generic_comparator(facts[fact], comp, expectation)
551
+
847
552
  else
848
- res = ! actual.to_s.match(/#{expectation}/).nil?
553
+ res = ! expectation.match(/#{facts[fact]}/).nil?
554
+ @log.debug(sprintf('meets_constraint?(%s, %s): %s', fact, expectation, res.nil?))
849
555
  end
850
556
 
851
- @logger.debug(sprintf('meets_constraint?(%s, %s): %s', key, expectation, res.nil?))
852
557
  res
853
558
  end
854
559
 
855
- ##
856
- # generic_comparator
857
- #
858
- # powers the 3 argument form of constraint (i.e. 'is_virtual != true', '<package_version> > 3.0', etc)
859
- #
860
- # should really be an eval{} of some sort (or would be in the perl world)
861
- #
862
- # parameters
863
- # * <comparand1> - left side of the comparison
864
- # * <comparator> - comparison to make
865
- # * <comparand2> - right side of the comparison
866
560
  def generic_comparator(comparand1, comparator, comparand2)
867
561
 
868
562
  # 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
870
563
  case comparator
871
564
  when '!='
872
565
  # ugh