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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +19 -0
- data/Vagrantfile +4 -1
- data/lib/rouster.rb +153 -31
- data/lib/rouster/deltas.rb +224 -58
- data/lib/rouster/puppet.rb +118 -25
- data/lib/rouster/testing.rb +202 -42
- data/lib/rouster/tests.rb +23 -11
- data/path_helper.rb +3 -4
- data/rouster.gemspec +2 -1
- data/test/functional/deltas/test_get_crontab.rb +24 -1
- data/test/functional/deltas/test_get_groups.rb +74 -2
- data/test/functional/deltas/test_get_packages.rb +43 -5
- data/test/functional/deltas/test_get_ports.rb +26 -1
- data/test/functional/deltas/test_get_services.rb +37 -4
- 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 +5 -1
- data/test/functional/test_dirs.rb +25 -0
- data/test/functional/test_get.rb +10 -6
- data/test/functional/test_new.rb +10 -9
- data/test/functional/test_put.rb +8 -10
- data/test/functional/test_restart.rb +1 -2
- data/test/functional/test_run.rb +2 -3
- data/test/functional/test_validate_file.rb +30 -0
- data/test/puppet/test_apply.rb +5 -5
- data/test/puppet/test_roles.rb +16 -3
- data/test/unit/test_parse_ls_string.rb +24 -0
- data/test/unit/testing/test_validate_file.rb +39 -46
- data/test/unit/testing/test_validate_port.rb +98 -0
- metadata +33 -17
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
|
+
@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
|
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>
|
128
|
-
# * [
|
129
|
-
|
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
|
-
|
132
|
-
|
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 (
|
258
|
-
|
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
|
-
|
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 '
|
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
|
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
|
-
|
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
|
307
|
-
:hiera_config
|
308
|
-
:manifest_file
|
309
|
-
:manifest_dir
|
310
|
-
:module_dir
|
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
|
-
|
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
|
-
|
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
|
data/lib/rouster/testing.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
325
|
-
|
326
|
-
|
327
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
# * <
|
532
|
-
# * <expectation>
|
533
|
-
# * [cache]
|
534
|
-
def meets_constraint?(
|
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
|
-
|
537
|
-
|
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
|
-
|
543
|
-
|
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 = !
|
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
|