rouster 0.42 → 0.53

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.
@@ -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
- return self.facts
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
+ @log.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
+ @log.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,7 +38,9 @@ class Rouster
31
38
  end
32
39
 
33
40
  if cache.true?
41
+ @log.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
@@ -53,7 +62,11 @@ class Rouster
53
62
  # post https://<puppetmaster>/catalog/<node>?facts_format=pson&facts=<pson URL encoded> == ht to patrick@puppetlabs
54
63
  certname = hostname.nil? ? self.run('hostname --fqdn').chomp : hostname
55
64
  puppetmaster = puppetmaster.nil? ? 'puppet' : puppetmaster
56
- facts = facts.nil? ? self.facter() : facts # TODO check for presence of certain 'required' facts/datatype?
65
+ facts = facts.nil? ? self.facter() : facts
66
+
67
+ %w(fqdn hostname operatingsystem operatingsystemrelease osfamily rubyversion).each do |required|
68
+ raise ArgumentError.new(sprintf('missing required fact[%s]', required)) unless facts.has_key?(required)
69
+ end
57
70
 
58
71
  raise InternalError.new('need to finish conversion of facts to PSON')
59
72
  facts.to_pson # this does not work, but needs to
@@ -124,13 +137,41 @@ class Rouster
124
137
  # returns hiera results from self
125
138
  #
126
139
  # parameters
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)
140
+ # * <key> - hiera key to look up
141
+ # * [facts] - hash of facts to be used in hiera lookup (technically optional, but most useful hiera lookups are based on facts)
142
+ # * [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
143
+ # * [options] - any additional parameters to be passed to hiera directly
144
+ #
145
+ # note
146
+ # * if no facts are provided, facter() will be called - to really run hiera without facts, send an empty hash
147
+ # * 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
148
+ def hiera(key, facts=nil, config='/etc/puppet/hiera.yaml', options=nil)
149
+ # TODO implement caching? where do we keep it? self.hiera{}? or self.deltas{} -- leaning towards #1
150
+
151
+ cmd = 'hiera'
152
+
153
+ if facts.nil?
154
+ @log.info('no facts provided, calling facter() automatically')
155
+ facts = self.facter()
156
+ end
157
+
158
+ if facts.keys.size > 0
159
+ scope_file = sprintf('/tmp/rouster-hiera_scope.%s.%s.json', $$, Time.now.to_i)
130
160
 
131
- # TODO implement this
132
- raise NotImplementedError.new()
161
+ File.write(scope_file, facts.to_json)
162
+ self.put(scope_file, scope_file)
163
+ File.delete(scope_file)
133
164
 
165
+ cmd << sprintf(' -j %s', scope_file)
166
+ end
167
+
168
+ cmd << sprintf(' -c %s', config) unless config.nil?
169
+ cmd << sprintf(' %s', options) unless options.nil?
170
+ cmd << sprintf(' %s', key)
171
+
172
+ raw = self.run(cmd)
173
+
174
+ JSON.parse(raw)
134
175
  end
135
176
 
136
177
  ##
@@ -230,7 +271,6 @@ class Rouster
230
271
  end
231
272
 
232
273
  # remove all nil references
233
- # TODO make this more rubyish
234
274
  resources.each_key do |name|
235
275
  resources[name].each_pair do |k,v|
236
276
  unless v
@@ -246,6 +286,8 @@ class Rouster
246
286
  results
247
287
  end
248
288
 
289
+ # TODO: come up with better method names here.. remove_existing_certs() and remove_my_cert() are not very descriptive
290
+
249
291
  ##
250
292
  # remove_existing_certs
251
293
  #
@@ -254,16 +296,27 @@ class Rouster
254
296
  #
255
297
  # parameters
256
298
  # * <puppetmaster> - string/partial regex of certificate names to keep
257
- def remove_existing_certs (puppetmaster)
258
- hosts = Array.new()
299
+ def remove_existing_certs (except)
300
+ except = except.kind_of?(Array) ? except : [except] # need to move from <>.class.eql? to <>.kind_of? in a number of places
301
+ hosts = Array.new()
259
302
 
260
303
  res = self.run('puppet cert list --all')
261
304
 
305
+ # TODO refactor this away from the hacky_break
262
306
  res.each_line do |line|
263
- next if line.match(/#{puppetmaster}/)
307
+ hacky_break = false
308
+
309
+ except.each do |exception|
310
+ hacky_break = line.match(/#{exception}/)
311
+
312
+ next if hacky_break
313
+ end
314
+
315
+ next unless hacky_break
316
+
264
317
  host = $1 if line.match(/^\+\s"(.*?)"/)
265
318
 
266
- hosts.push(host)
319
+ hosts.push(host) unless host.nil? # only want to clear signed certs
267
320
  end
268
321
 
269
322
  hosts.each do |host|
@@ -278,9 +331,15 @@ class Rouster
278
331
  # ... runs puppet on self, returns nothing
279
332
  #
280
333
  # currently supports 2 methods of running puppet:
281
- # * master - runs '/sbin/service puppet once -t'
334
+ # * master - runs 'puppet agent -t'
282
335
  # * supported options
283
336
  # * expected_exitcode - string/integer/array of acceptable exit code(s)
337
+ # * configtimeout - string/integer of the acceptable configtimeout value
338
+ # * environment - string of the environment to use
339
+ # * certname - string of the certname to use in place of the host fqdn
340
+ # * pluginsync - bool value if pluginsync should be used
341
+ # * server - string value of the puppetmasters fqdn / ip
342
+ # * additional_options - string of various options that would be passed to puppet
284
343
  # * masterless - runs 'puppet apply <options>' after determining version of puppet running and adjusting arguments
285
344
  # * supported options
286
345
  # * expected_exitcode - string/integer/array of acceptable exit code(s)
@@ -288,6 +347,10 @@ class Rouster
288
347
  # * manifest_file - string/array of strings of paths to manifest(s) to apply
289
348
  # * manifest_dir - string/array of strings of directories containing manifest(s) to apply - is recursive
290
349
  # * module_dir - path to module directory -- currently a required parameter, is this correct?
350
+ # * environment - string of the environment to use (default: production)
351
+ # * certname - string of the certname to use in place of the host fqdn (default: unused)
352
+ # * pluginsync - bool value if pluginsync should be used (default: true)
353
+ # * additional_options - string of various options that would be passed to puppet
291
354
  #
292
355
  # parameters
293
356
  # * [mode] - method to run puppet, defaults to 'master'
@@ -296,18 +359,36 @@ class Rouster
296
359
 
297
360
  if mode.eql?('master')
298
361
  opts = {
299
- :expected_exitcode => 0
362
+ :expected_exitcode => 0,
363
+ :configtimeout => nil,
364
+ :environment => nil,
365
+ :certname => nil,
366
+ :server => nil,
367
+ :pluginsync => false,
368
+ :additional_options => nil
300
369
  }.merge!(passed_opts)
301
370
 
302
- self.run('/sbin/service puppet once -t', opts[:expected_exitcode])
371
+ cmd = 'puppet agent -t'
372
+ cmd << sprintf(' --configtimeout %s', opts[:configtimeout]) unless opts[:configtimeout].nil?
373
+ cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
374
+ cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
375
+ cmd << sprintf(' --server %s', opts[:server]) unless opts[:server].nil?
376
+ cmd << ' --pluginsync' if opts[:pluginsync]
377
+ cmd << opts[:additional_options] unless opts[:additional_options].nil?
378
+
379
+ self.run(cmd, opts[:expected_exitcode])
303
380
 
304
381
  elsif mode.eql?('masterless')
305
382
  opts = {
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
383
+ :expected_exitcode => 2,
384
+ :hiera_config => nil,
385
+ :manifest_file => nil, # can be a string or array, will 'puppet apply' each
386
+ :manifest_dir => nil, # can be a string or array, will 'puppet apply' each module in the dir (recursively)
387
+ :module_dir => nil,
388
+ :environment => nil,
389
+ :certname => nil,
390
+ :pluginsync => false,
391
+ :additional_options => nil
311
392
  }.merge!(passed_opts)
312
393
 
313
394
  ## validate required arguments
@@ -321,8 +402,14 @@ class Rouster
321
402
  opts[:manifest_file].each do |file|
322
403
  raise InternalError.new(sprintf('invalid manifest file specified[%s]', file)) unless self.is_file?(file)
323
404
 
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])
405
+ cmd = sprintf('puppet apply %s --modulepath=%s', (puppet_version > '3.0') ? "--hiera_config=#{opts[:hiera_config]}" : '', opts[:module_dir])
406
+ cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
407
+ cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
408
+ cmd << ' --pluginsync' if opts[:pluginsync]
409
+ cmd << opts[:additional_options] unless opts[:additional_options].nil?
410
+ cmd << sprintf(' %s', file)
325
411
 
412
+ self.run(cmd, opts[:expected_exitcode])
326
413
  end
327
414
  end
328
415
 
@@ -335,8 +422,14 @@ class Rouster
335
422
 
336
423
  manifests.each do |m|
337
424
 
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])
425
+ cmd = sprintf('puppet apply %s --modulepath=%s', (puppet_version > '3.0') ? "--hiera_config=#{opts[:hiera_config]}" : '', opts[:module_dir])
426
+ cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
427
+ cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
428
+ cmd << ' --pluginsync' if opts[:pluginsync]
429
+ cmd << opts[:additional_options] unless opts[:additional_options].nil?
430
+ cmd << sprintf(' %s', m)
339
431
 
432
+ self.run(cmd, opts[:expected_exitcode])
340
433
  end
341
434
 
342
435
  end
@@ -349,4 +442,4 @@ class Rouster
349
442
 
350
443
  end
351
444
 
352
- end
445
+ end
@@ -5,6 +5,11 @@ require 'rouster/deltas'
5
5
 
6
6
  class Rouster
7
7
 
8
+ ##
9
+ # validate_cron
10
+ #
11
+ # TODO how do we actually want to implement this? since cron jobs don't have names, what does the user specify instead?
12
+
8
13
  ##
9
14
  # validate_file
10
15
  #
@@ -13,6 +18,7 @@ class Rouster
13
18
  # parameters
14
19
  # * <name> - full file name or relative (to ~vagrant)
15
20
  # * <expectations> - hash of expectations, see examples
21
+ # * <fail_fast> - return false immediately on any failure (default is false)
16
22
  #
17
23
  # example expectations:
18
24
  # '/sys/kernel/mm/redhat_transparent_hugepage/enabled', {
@@ -46,7 +52,7 @@ class Rouster
46
52
  # * :owner
47
53
  # * :group
48
54
  # * :constrain
49
- def validate_file(name, expectations, cache=false)
55
+ def validate_file(name, expectations, fail_fast=false, cache=false)
50
56
 
51
57
  if expectations[:ensure].nil? and expectations[:exists].nil? and expectations[:directory].nil? and expectations[:file?].nil?
52
58
  expectations[:ensure] = 'file'
@@ -56,7 +62,15 @@ class Rouster
56
62
  expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
57
63
 
58
64
  expectations[:constrain].each do |constraint|
59
- fact, expectation = constraint.split("\s")
65
+ valid = constraint.match(/^(\S+?)\s(.*)$/)
66
+
67
+ if valid.nil?
68
+ raise InternalError.new(sprintf('invalid constraint[%s] specified', constraint))
69
+ end
70
+
71
+ fact = $1
72
+ expectation = $2
73
+
60
74
  unless meets_constraint?(fact, expectation)
61
75
  @log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
62
76
  return true
@@ -156,11 +170,12 @@ class Rouster
156
170
  local = false
157
171
  end
158
172
  when :type
159
- # noop
173
+ # noop allowing parse_catalog() output to be passed directly
160
174
  else
161
175
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
162
176
  end
163
177
 
178
+ return false if local.eql?(false) and fail_fast.eql?(true)
164
179
  results[k] = local
165
180
  end
166
181
 
@@ -177,6 +192,7 @@ class Rouster
177
192
  # paramaters
178
193
  # * <name> - group name
179
194
  # * <expectations> - hash of expectations, see examples
195
+ # * <fail_fast> - return false immediately on any failure (default is false)
180
196
  #
181
197
  # example expectations:
182
198
  # 'root', {
@@ -198,7 +214,7 @@ class Rouster
198
214
  # * :gid
199
215
  # * :user|:users (string or array)
200
216
  # * :constrain
201
- def validate_group(name, expectations)
217
+ def validate_group(name, expectations, fail_fast=false)
202
218
  groups = self.get_groups(true)
203
219
 
204
220
  if expectations[:ensure].nil? and expectations[:exists].nil?
@@ -235,25 +251,33 @@ class Rouster
235
251
  local = v.to_s.match(/absent|false/).nil? ? false : true
236
252
  end
237
253
  when :gid
238
- local = v.to_s.eql?(groups[name][:gid].to_s)
254
+ if groups[name].is_a?(Hash) and groups[name].has_key?(:gid)
255
+ local = v.to_s.eql?(groups[name][:gid].to_s)
256
+ else
257
+ local = false
258
+ end
239
259
  when :user, :users
240
260
  v = v.class.eql?(Array) ? v : [v]
241
261
  v.each do |user|
242
- local = groups[name][:users].member?(user)
262
+ if groups[name].is_a?(Hash) and groups[name].has_key?(:users)
263
+ local = groups[name][:users].member?(user)
264
+ else
265
+ local = false
266
+ end
243
267
  break unless local.true? # need to make the return value smarter if we want to store data on which user failed
244
268
  end
245
269
  when :type
246
- # noop
270
+ # noop allowing parse_catalog() output to be passed directly
247
271
  else
248
272
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
249
273
  end
250
274
 
275
+ return false if local.eql?(false) and fail_fast.eql?(true)
251
276
  results[k] = local
252
277
  end
253
278
 
254
- @log.info(results.pretty_print_inspect())
279
+ @log.info(results)
255
280
  results.find{|k,v| v.false? }.nil?
256
-
257
281
  end
258
282
 
259
283
  ##
@@ -264,6 +288,7 @@ class Rouster
264
288
  # parameters
265
289
  # * <name> - package name
266
290
  # * <expectations> - hash of expectations, see examples
291
+ # * <fail_fast> - return false immediately on any failure (default is false)
267
292
  #
268
293
  # example expectations:
269
294
  # 'perl-Net-SNMP', {
@@ -284,7 +309,7 @@ class Rouster
284
309
  # * :exists|ensure
285
310
  # * :version (literal or basic comparison)
286
311
  # * :constrain
287
- def validate_package(name, expectations)
312
+ def validate_package(name, expectations, fail_fast=false)
288
313
  packages = self.get_packages(true)
289
314
 
290
315
  if expectations[:ensure].nil? and expectations[:exists].nil?
@@ -321,26 +346,123 @@ class Rouster
321
346
  local = v.to_s.match(/absent|false/).nil? ? false : true
322
347
  end
323
348
  when :version
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)
349
+ if packages.has_key?(name)
350
+ if v.split("\s").size > 1
351
+ ## generic comparator functionality
352
+ comp, expectation = v.split("\s")
353
+ local = generic_comparator(packages[name], comp, expectation)
354
+ else
355
+ local = ! v.to_s.match(/#{packages[name]}/).nil?
356
+ end
328
357
  else
329
- local = ! v.to_s.match(/#{packages[name]}/).nil?
358
+ local = false
330
359
  end
331
360
  when :type
332
- # noop
361
+ # noop allowing parse_catalog() output to be passed directly
333
362
  else
334
363
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
335
364
  end
336
365
 
366
+ return false if local.eql?(false) and fail_fast.eql?(true)
337
367
  results[k] = local
338
368
  end
339
369
 
340
370
  # 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
371
  @log.info(results)
342
372
 
343
- # TODO should we implement a fail fast method? at least an option
373
+ results.find{|k,v| v.false? }.nil?
374
+ end
375
+
376
+ # given a port nnumber and a hash of expectations, returns true|false whether port meets expectations
377
+ #
378
+ # parameters
379
+ # * <number> - port number
380
+ # * <expectations> - hash of expectations, see examples
381
+ #
382
+ # example expectations:
383
+ # '22', {
384
+ # :ensure => 'active',
385
+ # :protocol => 'tcp',
386
+ # :address => '0.0.0.0'
387
+ # },
388
+ #
389
+ # '1234', {
390
+ # :ensure => 'open',
391
+ # :address => '*',
392
+ # :constrain => 'is_virtual false'
393
+ # }
394
+ #
395
+ # supported keys:
396
+ # * :exists|ensure|state
397
+ # * :address
398
+ # * :protocol|proto
399
+ # * :constrain
400
+ def validate_port(number, expectations, fail_fast=false)
401
+ number = number.to_s
402
+ ports = self.get_ports(true)
403
+
404
+ if expectations[:ensure].nil? and expectations[:exists].nil? and expectations[:state].nil?
405
+ expectations[:ensure] = 'present'
406
+ end
407
+
408
+ if expectations[:protocol].nil? and expectations[:proto].nil?
409
+ expectations[:protocol] = 'tcp'
410
+ elsif ! expectations[:proto].nil?
411
+ expectations[:protocol] = expectations[:proto]
412
+ end
413
+
414
+ if expectations.has_key?(:constrain)
415
+ expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
416
+
417
+ expectations[:constrain].each do |constraint|
418
+ fact, expectation = constraint.split("\s")
419
+ unless meets_constraint?(fact, expectation)
420
+ @log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
421
+ return true
422
+ end
423
+ end
424
+
425
+ expectations.delete(:constrain)
426
+ end
427
+
428
+ results = Hash.new()
429
+ local = nil
430
+
431
+ expectations.each do |k,v|
432
+ case k
433
+ when :ensure, :exists, :state
434
+ if v.to_s.match(/absent|false|open/)
435
+ local = ports[expectations[:protocol]][number].nil?
436
+ else
437
+ local = ! ports[expectations[:protocol]][number].nil?
438
+ end
439
+ when :protocol, :proto
440
+ # TODO rewrite this in a less hacky way
441
+ 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/)
442
+ local = true
443
+ else
444
+ local = ports[v].has_key?(number)
445
+ end
446
+
447
+ when :address
448
+ lr = Array.new
449
+ addresses = ports[expectations[:protocol]][number][:address]
450
+ addresses.each_key do |address|
451
+ lr.push(address.eql?(v.to_s))
452
+ end
453
+
454
+ local = ! lr.find{|e| e.true? }.nil? # this feels jankity
455
+
456
+ else
457
+ raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
458
+ end
459
+
460
+ return false if local.eql?(false) and fail_fast.eql?(true)
461
+ results[k] = local
462
+ end
463
+
464
+ @log.info(results)
465
+
344
466
  results.find{|k,v| v.false? }.nil?
345
467
  end
346
468
 
@@ -352,6 +474,7 @@ class Rouster
352
474
  # parameters
353
475
  # * <name> - service name
354
476
  # * <expectations> - hash of expectations, see examples
477
+ # * <fail_fast> - return false immediately on any failure (default is false)
355
478
  #
356
479
  # example expectations:
357
480
  # 'ntp', {
@@ -367,7 +490,7 @@ class Rouster
367
490
  # * :exists|:ensure
368
491
  # * :state,:status
369
492
  # * :constrain
370
- def validate_service(name, expectations)
493
+ def validate_service(name, expectations, fail_fast=false)
371
494
  services = self.get_services(true)
372
495
 
373
496
  if expectations[:ensure].nil? and expectations[:exists].nil?
@@ -404,17 +527,22 @@ class Rouster
404
527
  local = v.to_s.match(/absent|false/).nil? ? false : true
405
528
  end
406
529
  when :state, :status
407
- local = ! v.match(/#{services[name]}/).nil?
530
+ if services.has_key?(name)
531
+ local = ! v.match(/#{services[name]}/).nil?
532
+ else
533
+ local = false
534
+ end
408
535
  when :type
409
- # noop
536
+ # noop allowing parse_catalog() output to be passed directly
410
537
  else
411
538
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
412
539
  end
413
540
 
541
+ return false if local.eql?(false) and fail_fast.eql?(true)
414
542
  results[k] = local
415
543
  end
416
544
 
417
- @log.info(results.pretty_print_inspect())
545
+ @log.info(results)
418
546
  results.find{|k,v| v.false? }.nil?
419
547
 
420
548
  end
@@ -427,6 +555,7 @@ class Rouster
427
555
  # parameters
428
556
  # * <name> - user name
429
557
  # * <expectations> - hash of expectations, see examples
558
+ # * <fail_fast> - return false immediately on any failure (default is false)
430
559
  #
431
560
  # example expectations:
432
561
  # 'root' => {
@@ -452,7 +581,7 @@ class Rouster
452
581
  # * :uid
453
582
  # * :gid
454
583
  # * :constrain
455
- def validate_user(name, expectations)
584
+ def validate_user(name, expectations, fail_fast=false)
456
585
  users = self.get_users(true)
457
586
 
458
587
  if expectations[:ensure].nil? and expectations[:exists].nil?
@@ -495,25 +624,46 @@ class Rouster
495
624
  break unless local.true?
496
625
  end
497
626
  when :gid
498
- local = v.to_i.eql?(users[name][:gid].to_i)
627
+ if users[name].is_a?(Hash) and users[name].has_key?(:gid)
628
+ local = v.to_i.eql?(users[name][:gid].to_i)
629
+ else
630
+ local = false
631
+ end
499
632
  when :home
500
- local = ! v.match(/#{users[name][:home]}/).nil?
633
+ if users[name].is_a?(Hash) and users[name].has_key?(:home)
634
+ local = ! v.match(/#{users[name][:home]}/).nil?
635
+ else
636
+ local = false
637
+ end
501
638
  when :home_exists
502
- local = ! v.to_s.match(/#{users[name][:home_exists].to_s}/).nil?
639
+ if users[name].is_a?(Hash) and users[name].has_key?(:home_exists)
640
+ local = ! v.to_s.match(/#{users[name][:home_exists].to_s}/).nil?
641
+ else
642
+ local = false
643
+ end
503
644
  when :shell
504
- local = ! v.match(/#{users[name][:shell]}/).nil?
645
+ if users[name].is_a?(Hash) and users[name].has_key?(:shell)
646
+ local = ! v.match(/#{users[name][:shell]}/).nil?
647
+ else
648
+ local = false
649
+ end
505
650
  when :uid
506
- local = v.to_i.eql?(users[name][:uid].to_i)
651
+ if users[name].is_a?(Hash) and users[name].has_key?(:uid)
652
+ local = v.to_i.eql?(users[name][:uid].to_i)
653
+ else
654
+ local = false
655
+ end
507
656
  when :type
508
- # noop
657
+ # noop allowing parse_catalog() output to be passed directly
509
658
  else
510
659
  raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
511
660
  end
512
661
 
662
+ return false if local.eql?(false) and fail_fast.eql?(true)
513
663
  results[k] = local
514
664
  end
515
665
 
516
- @log.info(results.pretty_print_inspect())
666
+ @log.info(results)
517
667
  results.find{|k,v| v.false? }.nil?
518
668
 
519
669
  end
@@ -528,32 +678,41 @@ class Rouster
528
678
  # gets facts from node, and if fact expectation regex matches actual fact, returns true
529
679
  #
530
680
  # parameters
531
- # * <fact> - fact
532
- # * <expectation>
533
- # * [cache]
534
- def meets_constraint?(fact, expectation, cache=true)
681
+ # * <key> - fact/hiera key to look up (actual value)
682
+ # * <expectation> -
683
+ # * [cache] - boolean controlling whether facter lookups are cached
684
+ def meets_constraint?(key, expectation, cache=true)
535
685
 
536
- unless self.respond_to?('facter')
537
- # if we haven't loaded puppet.rb, we won't have access to facts
686
+ expectation = expectation.to_s
687
+
688
+ unless self.respond_to?('facter') or self.respond_to?('hiera')
689
+ # if we haven't loaded puppet.rb, we won't have access to facts/hiera lookups
538
690
  @log.warn('using constraints without loading [rouster/puppet] will not work, forcing no-op')
539
691
  return false
540
692
  end
541
693
 
542
- expectation = expectation.to_s
543
- facts = self.facter(cache)
694
+ facts = self.facter(cache)
695
+ actual = nil
696
+
697
+ if facts[key]
698
+ actual = facts[key]
699
+ else
700
+ # value is not a fact, lets try to find it in hiera
701
+ # TODO how to handle the fact that this will really only work on the puppetmaster
702
+ actual = self.hiera(key, facts)
703
+ end
544
704
 
545
705
  res = nil
706
+
546
707
  if expectation.split("\s").size > 1
547
708
  ## generic comparator functionality
548
709
  comp, expectation = expectation.split("\s")
549
-
550
- res = generic_comparator(facts[fact], comp, expectation)
551
-
710
+ res = generic_comparator(actual, comp, expectation)
552
711
  else
553
- res = ! expectation.match(/#{facts[fact]}/).nil?
554
- @log.debug(sprintf('meets_constraint?(%s, %s): %s', fact, expectation, res.nil?))
712
+ res = ! actual.to_s.match(/#{expectation}/).nil?
555
713
  end
556
714
 
715
+ @log.debug(sprintf('meets_constraint?(%s, %s): %s', key, expectation, res.nil?))
557
716
  res
558
717
  end
559
718
 
@@ -571,6 +730,7 @@ class Rouster
571
730
  def generic_comparator(comparand1, comparator, comparand2)
572
731
 
573
732
  # TODO rewrite this as an eval so we don't have to support everything..
733
+ # 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
574
734
  case comparator
575
735
  when '!='
576
736
  # ugh