rouster 0.5
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 +7 -0
- data/.gitignore +6 -0
- data/LICENSE +9 -0
- data/README.md +175 -0
- data/Rakefile +65 -0
- data/Vagrantfile +23 -0
- data/examples/bootstrap.rb +113 -0
- data/examples/demo.rb +71 -0
- data/examples/error.rb +30 -0
- data/lib/rouster.rb +737 -0
- data/lib/rouster/deltas.rb +481 -0
- data/lib/rouster/puppet.rb +398 -0
- data/lib/rouster/testing.rb +743 -0
- data/lib/rouster/tests.rb +596 -0
- data/path_helper.rb +21 -0
- data/rouster.gemspec +30 -0
- data/test/basic.rb +10 -0
- data/test/functional/deltas/test_get_crontab.rb +99 -0
- data/test/functional/deltas/test_get_groups.rb +48 -0
- data/test/functional/deltas/test_get_packages.rb +71 -0
- data/test/functional/deltas/test_get_ports.rb +119 -0
- data/test/functional/deltas/test_get_services.rb +43 -0
- data/test/functional/deltas/test_get_users.rb +45 -0
- data/test/functional/puppet/test_facter.rb +59 -0
- data/test/functional/test_caching.rb +124 -0
- data/test/functional/test_destroy.rb +51 -0
- data/test/functional/test_dirs.rb +88 -0
- data/test/functional/test_files.rb +64 -0
- data/test/functional/test_get.rb +76 -0
- data/test/functional/test_inspect.rb +31 -0
- data/test/functional/test_is_dir.rb +118 -0
- data/test/functional/test_is_file.rb +119 -0
- data/test/functional/test_new.rb +92 -0
- data/test/functional/test_put.rb +81 -0
- data/test/functional/test_rebuild.rb +49 -0
- data/test/functional/test_restart.rb +44 -0
- data/test/functional/test_run.rb +77 -0
- data/test/functional/test_status.rb +38 -0
- data/test/functional/test_suspend.rb +31 -0
- data/test/functional/test_up.rb +27 -0
- data/test/functional/test_validate_file.rb +30 -0
- data/test/puppet/manifests/default.pp +9 -0
- data/test/puppet/manifests/hiera.yaml +12 -0
- data/test/puppet/manifests/hieradata/common.json +3 -0
- data/test/puppet/manifests/hieradata/vagrant.json +3 -0
- data/test/puppet/manifests/manifest.pp +78 -0
- data/test/puppet/modules/role/manifests/ui.pp +5 -0
- data/test/puppet/test_apply.rb +149 -0
- data/test/puppet/test_roles.rb +186 -0
- data/test/tunnel_vs_scp.rb +41 -0
- data/test/unit/puppet/test_get_puppet_star.rb +68 -0
- data/test/unit/test_generate_unique_mac.rb +43 -0
- data/test/unit/test_new.rb +31 -0
- data/test/unit/test_parse_ls_string.rb +334 -0
- data/test/unit/test_traverse_up.rb +43 -0
- data/test/unit/testing/test_meets_constraint.rb +55 -0
- data/test/unit/testing/test_validate_file.rb +112 -0
- data/test/unit/testing/test_validate_group.rb +72 -0
- data/test/unit/testing/test_validate_package.rb +69 -0
- data/test/unit/testing/test_validate_port.rb +98 -0
- data/test/unit/testing/test_validate_service.rb +73 -0
- data/test/unit/testing/test_validate_user.rb +92 -0
- metadata +203 -0
@@ -0,0 +1,398 @@
|
|
1
|
+
require sprintf('%s/../../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'net/https'
|
5
|
+
require 'socket'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
# TODO use @cache_timeout to invalidate data cached here
|
9
|
+
|
10
|
+
class Rouster
|
11
|
+
|
12
|
+
##
|
13
|
+
# facter
|
14
|
+
#
|
15
|
+
# runs facter, returns parsed hash of { fact1 => value1, factN => valueN }
|
16
|
+
#
|
17
|
+
# parameters
|
18
|
+
# * [cache] - whether to store/return cached facter data, if available
|
19
|
+
# * [custom_facts] - whether to include custom facts in return (uses -p argument)
|
20
|
+
def facter(cache=true, custom_facts=true)
|
21
|
+
if cache.true? and ! self.facts.nil?
|
22
|
+
return self.facts
|
23
|
+
end
|
24
|
+
|
25
|
+
raw = self.run(sprintf('facter %s', custom_facts.true? ? '-p' : ''))
|
26
|
+
res = Hash.new()
|
27
|
+
|
28
|
+
raw.split("\n").each do |line|
|
29
|
+
next unless line.match(/(\S*?)\s\=\>\s(.*)/)
|
30
|
+
res[$1] = $2
|
31
|
+
end
|
32
|
+
|
33
|
+
if cache.true?
|
34
|
+
self.facts = res
|
35
|
+
end
|
36
|
+
|
37
|
+
res
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# get_catalog
|
42
|
+
#
|
43
|
+
# not completely implemented method to get a compiled catalog about a node (based on its facts) from a puppetmaster
|
44
|
+
#
|
45
|
+
# original implementation used the catalog face, which does not actually work. switched to an API call, but still need to convert facts into PSON
|
46
|
+
#
|
47
|
+
# parameters
|
48
|
+
# * [hostname] - hostname of node to return catalog for, if not specified, will use `hostname --fqdn`
|
49
|
+
# * [puppetmaster] - hostname of puppetmaster to use in API call, defaults to 'puppet'
|
50
|
+
# * [facts] - hash of facts to pass to puppetmaster
|
51
|
+
# * [puppetmaster_port] - port to talk to the puppetmaster on, defaults to 8140
|
52
|
+
def get_catalog(hostname=nil, puppetmaster=nil, facts=nil, puppetmaster_port=8140)
|
53
|
+
# post https://<puppetmaster>/catalog/<node>?facts_format=pson&facts=<pson URL encoded> == ht to patrick@puppetlabs
|
54
|
+
certname = hostname.nil? ? self.run('hostname --fqdn').chomp : hostname
|
55
|
+
puppetmaster = puppetmaster.nil? ? 'puppet' : puppetmaster
|
56
|
+
facts = facts.nil? ? self.facter() : facts
|
57
|
+
|
58
|
+
%w(fqdn hostname operatingsystem operatingsystemrelease osfamily rubyversion).each do |required|
|
59
|
+
raise ArgumentError.new(sprintf('missing required fact[%s]', required)) unless facts.has_key?(required)
|
60
|
+
end
|
61
|
+
|
62
|
+
raise InternalError.new('need to finish conversion of facts to PSON')
|
63
|
+
facts.to_pson # this does not work, but needs to
|
64
|
+
|
65
|
+
json = nil
|
66
|
+
url = sprintf('https://%s:%s/catalog/%s?facts_format=pson&facts=%s', puppetmaster, puppetmaster_port, certname, facts)
|
67
|
+
uri = URI.parse(url)
|
68
|
+
|
69
|
+
begin
|
70
|
+
res = Net::HTTP.get(uri)
|
71
|
+
json = res.to_json
|
72
|
+
rescue => e
|
73
|
+
raise ExternalError.new("calling[#{url}] led to exception[#{e}")
|
74
|
+
end
|
75
|
+
|
76
|
+
json
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# get_puppet_errors
|
81
|
+
#
|
82
|
+
# parses input for puppet errors, returns array of strings
|
83
|
+
#
|
84
|
+
# parameters
|
85
|
+
# * [input] - string to look at, defaults to self.get_output()
|
86
|
+
def get_puppet_errors(input=nil)
|
87
|
+
str = input.nil? ? self.get_output() : input
|
88
|
+
errors = str.scan(/35merr:.*/)
|
89
|
+
|
90
|
+
errors.empty? ? nil : errors
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# get_puppet_notices
|
95
|
+
#
|
96
|
+
# parses input for puppet notices, returns array of strings
|
97
|
+
#
|
98
|
+
# parameters
|
99
|
+
# * [input] - string to look at, defaults to self.get_output()
|
100
|
+
def get_puppet_notices(input=nil)
|
101
|
+
str = input.nil? ? self.get_output() : input
|
102
|
+
notices = str.scan(/36mnotice:.*/)
|
103
|
+
|
104
|
+
notices.empty? ? nil : notices
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# get_puppet_version
|
109
|
+
#
|
110
|
+
# executes `puppet --version` and returns parsed version string or nil
|
111
|
+
def get_puppet_version
|
112
|
+
version = nil
|
113
|
+
installed = self.is_in_path?('puppet')
|
114
|
+
|
115
|
+
if installed
|
116
|
+
raw = self.run('puppet --version')
|
117
|
+
version = raw.match(/([\d\.]*)\s/) ? $1 : nil
|
118
|
+
else
|
119
|
+
version = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
version
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# hiera
|
127
|
+
#
|
128
|
+
# returns hiera results from self
|
129
|
+
#
|
130
|
+
# parameters
|
131
|
+
# * <key> - hiera key to look up
|
132
|
+
# * [config] - path to hiera configuration -- this is only optional if you have a hiera.yaml file in ~/vagrant
|
133
|
+
def hiera(key, config=nil, options=nil)
|
134
|
+
|
135
|
+
cmd = 'hiera'
|
136
|
+
cmd << sprintf(' -c %s', config) unless config.nil?
|
137
|
+
cmd << sprintf(' %s', options) unless options.nil?
|
138
|
+
cmd << sprintf(' %s', key)
|
139
|
+
|
140
|
+
self.run(cmd)
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# parse_catalog
|
145
|
+
#
|
146
|
+
# looks at the ['data']['resources'] keys in catalog for Files, Groups, Packages, Services and Users, returns hash of expectations compatible with validate_*
|
147
|
+
#
|
148
|
+
# this is a very lightly tested implementation, please open issues as necessary
|
149
|
+
#
|
150
|
+
# parameters
|
151
|
+
# * <catalog> - JSON string or Hash representation of catalog, typically from get_catalog()
|
152
|
+
def parse_catalog(catalog)
|
153
|
+
classes = nil
|
154
|
+
resources = nil
|
155
|
+
results = Hash.new()
|
156
|
+
|
157
|
+
if catalog.is_a?(String)
|
158
|
+
begin
|
159
|
+
JSON.parse!(catalog)
|
160
|
+
rescue
|
161
|
+
raise InternalError.new(sprintf('unable to parse catalog[%s] as JSON', catalog))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
unless catalog.has_key?('data') and catalog['data'].has_key?('classes')
|
166
|
+
raise InternalError.new(sprintf('catalog does not contain a classes key[%s]', catalog))
|
167
|
+
end
|
168
|
+
|
169
|
+
unless catalog.has_key?('data') and catalog['data'].has_key?('resources')
|
170
|
+
raise InternalError.new(sprintf('catalog does not contain a resources key[%s]', catalog))
|
171
|
+
end
|
172
|
+
|
173
|
+
raw_resources = catalog['data']['resources']
|
174
|
+
|
175
|
+
raw_resources.each do |r|
|
176
|
+
# samples of eacb type of resource is available at
|
177
|
+
# https://github.com/chorankates/rouster/issues/20#issuecomment-18635576
|
178
|
+
#
|
179
|
+
# we can do a lot better here
|
180
|
+
type = r['type']
|
181
|
+
case type
|
182
|
+
when 'Class'
|
183
|
+
classes.push(r['title'])
|
184
|
+
when 'File'
|
185
|
+
name = r['title']
|
186
|
+
resources[name] = Hash.new()
|
187
|
+
|
188
|
+
resources[name][:type] = :file
|
189
|
+
resources[name][:directory] = false
|
190
|
+
resources[name][:ensure] = r['ensure'] ||= 'present'
|
191
|
+
resources[name][:file] = true
|
192
|
+
resources[name][:group] = r['parameters'].has_key?('group') ? r['parameters']['group'] : nil
|
193
|
+
resources[name][:mode] = r['parameters'].has_key?('mode') ? r['parameters']['mode'] : nil
|
194
|
+
resources[name][:owner] = r['parameters'].has_key?('owner') ? r['parameters']['owner'] : nil
|
195
|
+
resources[name][:contains] = r.has_key?('content') ? r['content'] : nil
|
196
|
+
|
197
|
+
when 'Group'
|
198
|
+
name = r['title']
|
199
|
+
resources[name] = Hash.new()
|
200
|
+
|
201
|
+
resources[name][:type] = :group
|
202
|
+
resources[name][:ensure] = r['ensure'] ||= 'present'
|
203
|
+
resources[name][:gid] = r['parameters'].has_key?('gid') ? r['parameters']['gid'] : nil
|
204
|
+
|
205
|
+
when 'Package'
|
206
|
+
name = r['title']
|
207
|
+
resources[name] = Hash.new()
|
208
|
+
|
209
|
+
resources[name][:type] = :package
|
210
|
+
resources[name][:ensure] = r['ensure'] ||= 'present'
|
211
|
+
resources[name][:version] = r['ensure'] =~ /\d/ ? r['ensure'] : nil
|
212
|
+
|
213
|
+
when 'Service'
|
214
|
+
name = r['title']
|
215
|
+
resources[name] = Hash.new()
|
216
|
+
|
217
|
+
resources[name][:type] = :service
|
218
|
+
resources[name][:ensure] = r['ensure'] ||= 'present'
|
219
|
+
resources[name][:state] = r['ensure']
|
220
|
+
|
221
|
+
when 'User'
|
222
|
+
name = r['title']
|
223
|
+
resources[name] = Hash.new()
|
224
|
+
|
225
|
+
resources[name][:type] = :user
|
226
|
+
resources[name][:ensure] = r['ensure'] ||= 'present'
|
227
|
+
resources[name][:home] = r['parameters'].has_key?('home') ? r['parameters']['home'] : nil
|
228
|
+
resources[name][:gid] = r['parameters'].has_key?('gid') ? r['parameters']['gid'] : nil
|
229
|
+
resources[name][:group] = r['parameters'].has_key?('groups') ? r['parameters']['groups'] : nil
|
230
|
+
resources[name][:shell] = r['parameters'].has_key?('shell') ? r['parameters']['shell'] : nil
|
231
|
+
resources[name][:uid] = r['parameters'].has_key?('uid') ? r['parameters']['uid'] : nil
|
232
|
+
|
233
|
+
else
|
234
|
+
raise NotImplementedError.new(sprintf('parsing support for [%s] is incomplete', type))
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
# remove all nil references
|
240
|
+
resources.each_key do |name|
|
241
|
+
resources[name].each_pair do |k,v|
|
242
|
+
unless v
|
243
|
+
resources[name].delete(k)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
results[:classes] = classes
|
250
|
+
results[:resources] = resources
|
251
|
+
|
252
|
+
results
|
253
|
+
end
|
254
|
+
|
255
|
+
##
|
256
|
+
# remove_existing_certs
|
257
|
+
#
|
258
|
+
# ... removes existing certificates - really only useful when called on a puppetmaster
|
259
|
+
# useful in testing environments where you want to destroy/rebuild agents without rebuilding the puppetmaster every time (think autosign)
|
260
|
+
#
|
261
|
+
# parameters
|
262
|
+
# * <puppetmaster> - string/partial regex of certificate names to keep
|
263
|
+
def remove_existing_certs (puppetmaster)
|
264
|
+
hosts = Array.new()
|
265
|
+
|
266
|
+
res = self.run('puppet cert list --all')
|
267
|
+
|
268
|
+
res.each_line do |line|
|
269
|
+
next if line.match(/#{puppetmaster}/)
|
270
|
+
host = $1 if line.match(/^\+\s"(.*?)"/)
|
271
|
+
|
272
|
+
hosts.push(host) unless host.nil? # only want to clear signed certs
|
273
|
+
end
|
274
|
+
|
275
|
+
hosts.each do |host|
|
276
|
+
self.run(sprintf('puppet cert --clean %s', host))
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
##
|
282
|
+
# run_puppet
|
283
|
+
#
|
284
|
+
# ... runs puppet on self, returns nothing
|
285
|
+
#
|
286
|
+
# currently supports 2 methods of running puppet:
|
287
|
+
# * master - runs 'puppet agent -t'
|
288
|
+
# * supported options
|
289
|
+
# * expected_exitcode - string/integer/array of acceptable exit code(s)
|
290
|
+
# * configtimeout - string/integer of the acceptable configtimeout value
|
291
|
+
# * environment - string of the environment to use
|
292
|
+
# * certname - string of the certname to use in place of the host fqdn
|
293
|
+
# * pluginsync - bool value if pluginsync should be used
|
294
|
+
# * server - string value of the puppetmasters fqdn / ip
|
295
|
+
# * additional_options - string of various options that would be passed to puppet
|
296
|
+
# * masterless - runs 'puppet apply <options>' after determining version of puppet running and adjusting arguments
|
297
|
+
# * supported options
|
298
|
+
# * expected_exitcode - string/integer/array of acceptable exit code(s)
|
299
|
+
# * hiera_config - path to hiera configuration -- only supported by Puppet 3.0+
|
300
|
+
# * manifest_file - string/array of strings of paths to manifest(s) to apply
|
301
|
+
# * manifest_dir - string/array of strings of directories containing manifest(s) to apply - is recursive
|
302
|
+
# * module_dir - path to module directory -- currently a required parameter, is this correct?
|
303
|
+
# * environment - string of the environment to use (default: production)
|
304
|
+
# * certname - string of the certname to use in place of the host fqdn (default: unused)
|
305
|
+
# * pluginsync - bool value if pluginsync should be used (default: true)
|
306
|
+
# * additional_options - string of various options that would be passed to puppet
|
307
|
+
#
|
308
|
+
# parameters
|
309
|
+
# * [mode] - method to run puppet, defaults to 'master'
|
310
|
+
# * [opts] - hash of additional options
|
311
|
+
def run_puppet(mode='master', passed_opts=nil)
|
312
|
+
|
313
|
+
if mode.eql?('master')
|
314
|
+
opts = {
|
315
|
+
:expected_exitcode => 0,
|
316
|
+
:configtimeout => nil,
|
317
|
+
:environment => nil,
|
318
|
+
:certname => nil,
|
319
|
+
:server => nil,
|
320
|
+
:pluginsync => false,
|
321
|
+
:additional_options => nil
|
322
|
+
}.merge!(passed_opts)
|
323
|
+
|
324
|
+
cmd = 'puppet agent -t'
|
325
|
+
cmd << sprintf(' --configtimeout %s', opts[:configtimeout]) unless opts[:configtimeout].nil?
|
326
|
+
cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
|
327
|
+
cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
|
328
|
+
cmd << sprintf(' --server %s', opts[:server]) unless opts[:server].nil?
|
329
|
+
cmd << ' --pluginsync' if opts[:pluginsync]
|
330
|
+
cmd << opts[:additional_options] unless opts[:additional_options].nil?
|
331
|
+
|
332
|
+
self.run(cmd, opts[:expected_exitcode])
|
333
|
+
|
334
|
+
elsif mode.eql?('masterless')
|
335
|
+
opts = {
|
336
|
+
:expected_exitcode => 2,
|
337
|
+
:hiera_config => nil,
|
338
|
+
:manifest_file => nil, # can be a string or array, will 'puppet apply' each
|
339
|
+
:manifest_dir => nil, # can be a string or array, will 'puppet apply' each module in the dir (recursively)
|
340
|
+
:module_dir => nil,
|
341
|
+
:environment => nil,
|
342
|
+
:certname => nil,
|
343
|
+
:pluginsync => false,
|
344
|
+
:additional_options => nil
|
345
|
+
}.merge!(passed_opts)
|
346
|
+
|
347
|
+
## validate required arguments
|
348
|
+
raise InternalError.new(sprintf('invalid hiera config specified[%s]', opts[:hiera_config])) unless self.is_file?(opts[:hiera_config])
|
349
|
+
raise InternalError.new(sprintf('invalid module dir specified[%s]', opts[:module_dir])) unless self.is_dir?(opts[:module_dir])
|
350
|
+
|
351
|
+
puppet_version = self.get_puppet_version() # hiera_config specification is only supported in >3.0
|
352
|
+
|
353
|
+
if opts[:manifest_file]
|
354
|
+
opts[:manifest_file] = opts[:manifest_file].class.eql?(Array) ? opts[:manifest_file] : [opts[:manifest_file]]
|
355
|
+
opts[:manifest_file].each do |file|
|
356
|
+
raise InternalError.new(sprintf('invalid manifest file specified[%s]', file)) unless self.is_file?(file)
|
357
|
+
|
358
|
+
cmd = sprintf('puppet apply %s --modulepath=%s', (puppet_version > '3.0') ? "--hiera_config=#{opts[:hiera_config]}" : '', opts[:module_dir])
|
359
|
+
cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
|
360
|
+
cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
|
361
|
+
cmd << ' --pluginsync' if opts[:pluginsync]
|
362
|
+
cmd << opts[:additional_options] unless opts[:additional_options].nil?
|
363
|
+
cmd << sprintf(' %s', file)
|
364
|
+
|
365
|
+
self.run(cmd, opts[:expected_exitcode])
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
if opts[:manifest_dir]
|
370
|
+
opts[:manifest_dir] = opts[:manifest_dir].class.eql?(Array) ? opts[:manifest_dir] : [opts[:manifest_dir]]
|
371
|
+
opts[:manifest_dir].each do |dir|
|
372
|
+
raise InternalError.new(sprintf('invalid manifest dir specified[%s]', dir)) unless self.is_dir?(dir)
|
373
|
+
|
374
|
+
manifests = self.files(dir, '*.pp', true)
|
375
|
+
|
376
|
+
manifests.each do |m|
|
377
|
+
|
378
|
+
cmd = sprintf('puppet apply %s --modulepath=%s', (puppet_version > '3.0') ? "--hiera_config=#{opts[:hiera_config]}" : '', opts[:module_dir])
|
379
|
+
cmd << sprintf(' --environment %s', opts[:environment]) unless opts[:environment].nil?
|
380
|
+
cmd << sprintf(' --certname %s', opts[:certname]) unless opts[:certname].nil?
|
381
|
+
cmd << ' --pluginsync' if opts[:pluginsync]
|
382
|
+
cmd << opts[:additional_options] unless opts[:additional_options].nil?
|
383
|
+
cmd << sprintf(' %s', m)
|
384
|
+
|
385
|
+
self.run(cmd, opts[:expected_exitcode])
|
386
|
+
end
|
387
|
+
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
else
|
392
|
+
raise InternalError.new(sprintf('unknown mode [%s]', mode))
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
@@ -0,0 +1,743 @@
|
|
1
|
+
require sprintf('%s/../../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
|
2
|
+
require 'rouster/deltas'
|
3
|
+
|
4
|
+
# TODO better document keys :constrain and :version
|
5
|
+
|
6
|
+
class Rouster
|
7
|
+
|
8
|
+
##
|
9
|
+
# validate_file
|
10
|
+
#
|
11
|
+
# given a filename and a hash of expectations, returns true|false whether file matches expectations
|
12
|
+
#
|
13
|
+
# parameters
|
14
|
+
# * <name> - full file name or relative (to ~vagrant)
|
15
|
+
# * <expectations> - hash of expectations, see examples
|
16
|
+
# * <fail_fast> - return false immediately on any failure (default is false)
|
17
|
+
#
|
18
|
+
# example expectations:
|
19
|
+
# '/sys/kernel/mm/redhat_transparent_hugepage/enabled', {
|
20
|
+
# :contains => 'never',
|
21
|
+
# },
|
22
|
+
#
|
23
|
+
# '/etc/fstab', {
|
24
|
+
# :contains => '/dev/fioa*/iodata*xfs',
|
25
|
+
# :constrain => 'is_virtual false' # syntax is '<fact> <expected>', file is only tested if <expected> matches <actual>
|
26
|
+
# :exists => 'file',
|
27
|
+
# :mode => '0644'
|
28
|
+
# },
|
29
|
+
#
|
30
|
+
# '/etc/hosts', {
|
31
|
+
# :constrain => ['! is_virtual true', 'is_virtual false'],
|
32
|
+
# :mode => '0644'
|
33
|
+
# }
|
34
|
+
#
|
35
|
+
# '/etc/nrpe.cfg', {
|
36
|
+
# :ensure => 'file',
|
37
|
+
# :contains => ['dont_blame_nrpe=1', 'allowed_hosts=' ]
|
38
|
+
# }
|
39
|
+
#
|
40
|
+
# supported keys:
|
41
|
+
# * :exists|:ensure -- defaults to file if not specified
|
42
|
+
# * :file
|
43
|
+
# * :directory
|
44
|
+
# * :contains (string or array)
|
45
|
+
# * :mode/:permissions
|
46
|
+
# * :size
|
47
|
+
# * :owner
|
48
|
+
# * :group
|
49
|
+
# * :constrain
|
50
|
+
def validate_file(name, expectations, fail_fast=false, cache=false)
|
51
|
+
|
52
|
+
if expectations[:ensure].nil? and expectations[:exists].nil? and expectations[:directory].nil? and expectations[:file?].nil?
|
53
|
+
expectations[:ensure] = 'file'
|
54
|
+
end
|
55
|
+
|
56
|
+
if expectations.has_key?(:constrain)
|
57
|
+
expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
|
58
|
+
|
59
|
+
expectations[:constrain].each do |constraint|
|
60
|
+
fact, expectation = constraint.split("\s")
|
61
|
+
unless meets_constraint?(fact, expectation)
|
62
|
+
@log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
63
|
+
return true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
expectations.delete(:constrain)
|
68
|
+
end
|
69
|
+
|
70
|
+
properties = (expectations[:ensure].eql?('file')) ? self.file(name, cache) : self.dir(name, cache)
|
71
|
+
results = Hash.new()
|
72
|
+
local = nil
|
73
|
+
|
74
|
+
expectations.each do |k,v|
|
75
|
+
|
76
|
+
case k
|
77
|
+
when :ensure, :exists
|
78
|
+
if properties.nil? and v.to_s.match(/absent|false/)
|
79
|
+
local = true
|
80
|
+
elsif properties.nil?
|
81
|
+
local = false
|
82
|
+
else
|
83
|
+
case v
|
84
|
+
when 'dir', 'directory'
|
85
|
+
local = properties[:directory?]
|
86
|
+
else
|
87
|
+
local = properties[:file?]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
when :file
|
91
|
+
if properties.nil?
|
92
|
+
if v.to_s.match(/absent|false/)
|
93
|
+
local = true
|
94
|
+
else
|
95
|
+
local = false
|
96
|
+
end
|
97
|
+
elsif properties[:file?].true?
|
98
|
+
local = ! v.to_s.match(/absent|false/)
|
99
|
+
else
|
100
|
+
false
|
101
|
+
end
|
102
|
+
when :dir, :directory
|
103
|
+
if properties.nil?
|
104
|
+
if v.to_s.match(/absent|false/)
|
105
|
+
local = true
|
106
|
+
else
|
107
|
+
local = false
|
108
|
+
end
|
109
|
+
elsif properties.has_key?(:directory?)
|
110
|
+
if properties[:directory?]
|
111
|
+
local = v.to_s.match(/absent|false/).nil?
|
112
|
+
else
|
113
|
+
local = ! v.to_s.match(/absent|false/).nil?
|
114
|
+
end
|
115
|
+
else
|
116
|
+
local = false
|
117
|
+
end
|
118
|
+
when :contains
|
119
|
+
v = v.class.eql?(Array) ? v : [v]
|
120
|
+
v.each do |regex|
|
121
|
+
local = true
|
122
|
+
begin
|
123
|
+
self.run(sprintf("grep -c '%s' %s", regex, name))
|
124
|
+
rescue
|
125
|
+
local = false
|
126
|
+
end
|
127
|
+
next if local.false?
|
128
|
+
end
|
129
|
+
when :mode, :permissions
|
130
|
+
if properties.nil?
|
131
|
+
local = false
|
132
|
+
elsif v.to_s.match(/#{properties[:mode].to_s}/)
|
133
|
+
local = true
|
134
|
+
else
|
135
|
+
local = false
|
136
|
+
end
|
137
|
+
when :size
|
138
|
+
if properties.nil?
|
139
|
+
local = false
|
140
|
+
else
|
141
|
+
local = v.to_i.eql?(properties[:size].to_i)
|
142
|
+
end
|
143
|
+
when :owner
|
144
|
+
if properties.nil?
|
145
|
+
local = false
|
146
|
+
elsif v.to_s.match(/#{properties[:owner].to_s}/)
|
147
|
+
local = true
|
148
|
+
else
|
149
|
+
local = false
|
150
|
+
end
|
151
|
+
when :group
|
152
|
+
if properties.nil?
|
153
|
+
local = false
|
154
|
+
elsif v.match(/#{properties[:group]}/)
|
155
|
+
local = true
|
156
|
+
else
|
157
|
+
local = false
|
158
|
+
end
|
159
|
+
when :type
|
160
|
+
# noop allowing parse_catalog() output to be passed directly
|
161
|
+
else
|
162
|
+
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
163
|
+
end
|
164
|
+
|
165
|
+
return false if local.eql?(false) and fail_fast.eql?(true)
|
166
|
+
results[k] = local
|
167
|
+
end
|
168
|
+
|
169
|
+
@log.info(results)
|
170
|
+
results.find{|k,v| v.false? }.nil?
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# validate_group
|
176
|
+
#
|
177
|
+
# given a group and a hash of expectations, returns true|false whether group matches expectations
|
178
|
+
#
|
179
|
+
# paramaters
|
180
|
+
# * <name> - group name
|
181
|
+
# * <expectations> - hash of expectations, see examples
|
182
|
+
# * <fail_fast> - return false immediately on any failure (default is false)
|
183
|
+
#
|
184
|
+
# example expectations:
|
185
|
+
# 'root', {
|
186
|
+
# # if ensure is not specified, 'present' is implied
|
187
|
+
# :gid => 0,
|
188
|
+
# :user => 'root'
|
189
|
+
# }
|
190
|
+
# 'sys', {
|
191
|
+
# :ensure => 'present',
|
192
|
+
# :user => ['root', 'bin', 'daemon']
|
193
|
+
# },
|
194
|
+
#
|
195
|
+
# 'fizz', {
|
196
|
+
# :exists => false
|
197
|
+
# },
|
198
|
+
#
|
199
|
+
# supported keys:
|
200
|
+
# * :exists|:ensure
|
201
|
+
# * :gid
|
202
|
+
# * :user|:users (string or array)
|
203
|
+
# * :constrain
|
204
|
+
def validate_group(name, expectations, fail_fast=false)
|
205
|
+
groups = self.get_groups(true)
|
206
|
+
|
207
|
+
if expectations[:ensure].nil? and expectations[:exists].nil?
|
208
|
+
expectations[:ensure] = 'present'
|
209
|
+
end
|
210
|
+
|
211
|
+
if expectations.has_key?(:constrain)
|
212
|
+
expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
|
213
|
+
|
214
|
+
expectations[:constrain].each do |constraint|
|
215
|
+
fact, expectation = constraint.split("\s")
|
216
|
+
unless meets_constraint?(fact, expectation)
|
217
|
+
@log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
218
|
+
return true
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
expectations.delete(:constrain)
|
223
|
+
end
|
224
|
+
|
225
|
+
results = Hash.new()
|
226
|
+
local = nil
|
227
|
+
|
228
|
+
expectations.each do |k,v|
|
229
|
+
case k
|
230
|
+
when :ensure, :exists
|
231
|
+
if groups.has_key?(name)
|
232
|
+
if v.to_s.match(/absent|false/).nil?
|
233
|
+
local = true
|
234
|
+
else
|
235
|
+
local = false
|
236
|
+
end
|
237
|
+
else
|
238
|
+
local = v.to_s.match(/absent|false/).nil? ? false : true
|
239
|
+
end
|
240
|
+
when :gid
|
241
|
+
if groups[name].is_a?(Hash) and groups[name].has_key?(:gid)
|
242
|
+
local = v.to_s.eql?(groups[name][:gid].to_s)
|
243
|
+
else
|
244
|
+
local = false
|
245
|
+
end
|
246
|
+
when :user, :users
|
247
|
+
v = v.class.eql?(Array) ? v : [v]
|
248
|
+
v.each do |user|
|
249
|
+
if groups[name].is_a?(Hash) and groups[name].has_key?(:users)
|
250
|
+
local = groups[name][:users].member?(user)
|
251
|
+
else
|
252
|
+
local = false
|
253
|
+
end
|
254
|
+
break unless local.true? # need to make the return value smarter if we want to store data on which user failed
|
255
|
+
end
|
256
|
+
when :type
|
257
|
+
# noop allowing parse_catalog() output to be passed directly
|
258
|
+
else
|
259
|
+
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
260
|
+
end
|
261
|
+
|
262
|
+
return false if local.eql?(false) and fail_fast.eql?(true)
|
263
|
+
results[k] = local
|
264
|
+
end
|
265
|
+
|
266
|
+
@log.info(results)
|
267
|
+
results.find{|k,v| v.false? }.nil?
|
268
|
+
end
|
269
|
+
|
270
|
+
##
|
271
|
+
# validate_package
|
272
|
+
#
|
273
|
+
# given a package name and a hash of expectations, returns true|false whether package meets expectations
|
274
|
+
#
|
275
|
+
# parameters
|
276
|
+
# * <name> - package name
|
277
|
+
# * <expectations> - hash of expectations, see examples
|
278
|
+
# * <fail_fast> - return false immediately on any failure (default is false)
|
279
|
+
#
|
280
|
+
# example expectations:
|
281
|
+
# 'perl-Net-SNMP', {
|
282
|
+
# :ensure => 'absent'
|
283
|
+
# },
|
284
|
+
#
|
285
|
+
# 'pixman', {
|
286
|
+
# :ensure => 'present',
|
287
|
+
# :version => '1.0',
|
288
|
+
# },
|
289
|
+
#
|
290
|
+
# 'rrdtool', {
|
291
|
+
# # if ensure is not specified, 'present' is implied
|
292
|
+
# :version => '> 2.1',
|
293
|
+
# :constrain => 'is_virtual false',
|
294
|
+
# },
|
295
|
+
# supported keys:
|
296
|
+
# * :exists|ensure
|
297
|
+
# * :version (literal or basic comparison)
|
298
|
+
# * :constrain
|
299
|
+
def validate_package(name, expectations, fail_fast=false)
|
300
|
+
packages = self.get_packages(true)
|
301
|
+
|
302
|
+
if expectations[:ensure].nil? and expectations[:exists].nil?
|
303
|
+
expectations[:ensure] = 'present'
|
304
|
+
end
|
305
|
+
|
306
|
+
if expectations.has_key?(:constrain)
|
307
|
+
expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
|
308
|
+
|
309
|
+
expectations[:constrain].each do |constraint|
|
310
|
+
fact, expectation = constraint.split("\s")
|
311
|
+
unless meets_constraint?(fact, expectation)
|
312
|
+
@log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
313
|
+
return true
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
expectations.delete(:constrain)
|
318
|
+
end
|
319
|
+
|
320
|
+
results = Hash.new()
|
321
|
+
local = nil
|
322
|
+
|
323
|
+
expectations.each do |k,v|
|
324
|
+
case k
|
325
|
+
when :ensure, :exists
|
326
|
+
if packages.has_key?(name)
|
327
|
+
if v.to_s.match(/absent|false/).nil?
|
328
|
+
local = true
|
329
|
+
else
|
330
|
+
local = false
|
331
|
+
end
|
332
|
+
else
|
333
|
+
local = v.to_s.match(/absent|false/).nil? ? false : true
|
334
|
+
end
|
335
|
+
when :version
|
336
|
+
if packages.has_key?(name)
|
337
|
+
if v.split("\s").size > 1
|
338
|
+
## generic comparator functionality
|
339
|
+
comp, expectation = v.split("\s")
|
340
|
+
local = generic_comparator(packages[name], comp, expectation)
|
341
|
+
else
|
342
|
+
local = ! v.to_s.match(/#{packages[name]}/).nil?
|
343
|
+
end
|
344
|
+
else
|
345
|
+
local = false
|
346
|
+
end
|
347
|
+
when :type
|
348
|
+
# noop allowing parse_catalog() output to be passed directly
|
349
|
+
else
|
350
|
+
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
351
|
+
end
|
352
|
+
|
353
|
+
return false if local.eql?(false) and fail_fast.eql?(true)
|
354
|
+
results[k] = local
|
355
|
+
end
|
356
|
+
|
357
|
+
# TODO figure out a good way to allow access to the entire hash, not just boolean -- for now just print at an info level
|
358
|
+
@log.info(results)
|
359
|
+
|
360
|
+
results.find{|k,v| v.false? }.nil?
|
361
|
+
end
|
362
|
+
|
363
|
+
# given a port nnumber and a hash of expectations, returns true|false whether port meets expectations
|
364
|
+
#
|
365
|
+
# parameters
|
366
|
+
# * <number> - port number
|
367
|
+
# * <expectations> - hash of expectations, see examples
|
368
|
+
#
|
369
|
+
# example expectations:
|
370
|
+
# '22', {
|
371
|
+
# :ensure => 'active',
|
372
|
+
# :protocol => 'tcp',
|
373
|
+
# :address => '0.0.0.0'
|
374
|
+
# },
|
375
|
+
#
|
376
|
+
# '1234', {
|
377
|
+
# :ensure => 'open',
|
378
|
+
# :address => '*',
|
379
|
+
# :constrain => 'is_virtual false'
|
380
|
+
# }
|
381
|
+
#
|
382
|
+
# supported keys:
|
383
|
+
# * :exists|ensure|state
|
384
|
+
# * :address
|
385
|
+
# * :protocol|proto
|
386
|
+
# * :constrain
|
387
|
+
def validate_port(number, expectations, fail_fast=false)
|
388
|
+
number = number.to_s
|
389
|
+
ports = self.get_ports(true)
|
390
|
+
|
391
|
+
if expectations[:ensure].nil? and expectations[:exists].nil? and expectations[:state].nil?
|
392
|
+
expectations[:ensure] = 'present'
|
393
|
+
end
|
394
|
+
|
395
|
+
if expectations[:protocol].nil? and expectations[:proto].nil?
|
396
|
+
expectations[:protocol] = 'tcp'
|
397
|
+
elsif ! expectations[:proto].nil?
|
398
|
+
expectations[:protocol] = expectations[:proto]
|
399
|
+
end
|
400
|
+
|
401
|
+
if expectations.has_key?(:constrain)
|
402
|
+
expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
|
403
|
+
|
404
|
+
expectations[:constrain].each do |constraint|
|
405
|
+
fact, expectation = constraint.split("\s")
|
406
|
+
unless meets_constraint?(fact, expectation)
|
407
|
+
@log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
408
|
+
return true
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
expectations.delete(:constrain)
|
413
|
+
end
|
414
|
+
|
415
|
+
results = Hash.new()
|
416
|
+
local = nil
|
417
|
+
|
418
|
+
expectations.each do |k,v|
|
419
|
+
case k
|
420
|
+
when :ensure, :exists, :state
|
421
|
+
if v.to_s.match(/absent|false|open/)
|
422
|
+
local = ports[expectations[:protocol]][number].nil?
|
423
|
+
else
|
424
|
+
local = ! ports[expectations[:protocol]][number].nil?
|
425
|
+
end
|
426
|
+
when :protocol, :proto
|
427
|
+
# TODO rewrite this in a less hacky way
|
428
|
+
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/)
|
429
|
+
local = true
|
430
|
+
else
|
431
|
+
local = ports[v].has_key?(number)
|
432
|
+
end
|
433
|
+
|
434
|
+
when :address
|
435
|
+
lr = Array.new
|
436
|
+
addresses = ports[expectations[:protocol]][number][:address]
|
437
|
+
addresses.each_key do |address|
|
438
|
+
lr.push(address.eql?(v.to_s))
|
439
|
+
end
|
440
|
+
|
441
|
+
local = ! lr.find{|e| e.true? }.nil? # this feels jankity
|
442
|
+
|
443
|
+
else
|
444
|
+
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
445
|
+
end
|
446
|
+
|
447
|
+
return false if local.eql?(false) and fail_fast.eql?(true)
|
448
|
+
results[k] = local
|
449
|
+
end
|
450
|
+
|
451
|
+
@log.info(results)
|
452
|
+
|
453
|
+
results.find{|k,v| v.false? }.nil?
|
454
|
+
end
|
455
|
+
|
456
|
+
##
|
457
|
+
# validate_service
|
458
|
+
#
|
459
|
+
# given a service name and a hash of expectations, returns true|false whether package meets expectations
|
460
|
+
#
|
461
|
+
# parameters
|
462
|
+
# * <name> - service name
|
463
|
+
# * <expectations> - hash of expectations, see examples
|
464
|
+
# * <fail_fast> - return false immediately on any failure (default is false)
|
465
|
+
#
|
466
|
+
# example expectations:
|
467
|
+
# 'ntp', {
|
468
|
+
# :ensure => 'present',
|
469
|
+
# :state => 'started'
|
470
|
+
# },
|
471
|
+
#
|
472
|
+
# 'ypbind', {
|
473
|
+
# :state => 'stopped',
|
474
|
+
# }
|
475
|
+
#
|
476
|
+
# supported keys:
|
477
|
+
# * :exists|:ensure
|
478
|
+
# * :state,:status
|
479
|
+
# * :constrain
|
480
|
+
def validate_service(name, expectations, fail_fast=false)
|
481
|
+
services = self.get_services(true)
|
482
|
+
|
483
|
+
if expectations[:ensure].nil? and expectations[:exists].nil?
|
484
|
+
expectations[:ensure] = 'present'
|
485
|
+
end
|
486
|
+
|
487
|
+
if expectations.has_key?(:constrain)
|
488
|
+
expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
|
489
|
+
|
490
|
+
expectations[:constrain].each do |constraint|
|
491
|
+
fact, expectation = constraint.split("\s")
|
492
|
+
unless meets_constraint?(fact, expectation)
|
493
|
+
@log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
494
|
+
return true
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
expectations.delete(:constrain)
|
499
|
+
end
|
500
|
+
|
501
|
+
results = Hash.new()
|
502
|
+
local = nil
|
503
|
+
|
504
|
+
expectations.each do |k,v|
|
505
|
+
case k
|
506
|
+
when :ensure, :exists
|
507
|
+
if services.has_key?(name)
|
508
|
+
if v.to_s.match(/absent|false/)
|
509
|
+
local = false
|
510
|
+
else
|
511
|
+
local = true
|
512
|
+
end
|
513
|
+
else
|
514
|
+
local = v.to_s.match(/absent|false/).nil? ? false : true
|
515
|
+
end
|
516
|
+
when :state, :status
|
517
|
+
if services.has_key?(name)
|
518
|
+
local = ! v.match(/#{services[name]}/).nil?
|
519
|
+
else
|
520
|
+
local = false
|
521
|
+
end
|
522
|
+
when :type
|
523
|
+
# noop allowing parse_catalog() output to be passed directly
|
524
|
+
else
|
525
|
+
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
526
|
+
end
|
527
|
+
|
528
|
+
return false if local.eql?(false) and fail_fast.eql?(true)
|
529
|
+
results[k] = local
|
530
|
+
end
|
531
|
+
|
532
|
+
@log.info(results)
|
533
|
+
results.find{|k,v| v.false? }.nil?
|
534
|
+
|
535
|
+
end
|
536
|
+
|
537
|
+
##
|
538
|
+
# validate_user
|
539
|
+
#
|
540
|
+
# given a user name and a hash of expectations, returns true|false whether user meets expectations
|
541
|
+
#
|
542
|
+
# parameters
|
543
|
+
# * <name> - user name
|
544
|
+
# * <expectations> - hash of expectations, see examples
|
545
|
+
# * <fail_fast> - return false immediately on any failure (default is false)
|
546
|
+
#
|
547
|
+
# example expectations:
|
548
|
+
# 'root' => {
|
549
|
+
# :uid => 0
|
550
|
+
# },
|
551
|
+
#
|
552
|
+
# 'ftp' => {
|
553
|
+
# :exists => true,
|
554
|
+
# :home => '/var/ftp',
|
555
|
+
# :shell => 'nologin'
|
556
|
+
# },
|
557
|
+
#
|
558
|
+
# 'developer' => {
|
559
|
+
# :exists => 'false',
|
560
|
+
# :constrain => 'environment != production'
|
561
|
+
# }
|
562
|
+
#
|
563
|
+
# supported keys:
|
564
|
+
# * :exists|ensure
|
565
|
+
# * :home
|
566
|
+
# * :group
|
567
|
+
# * :shell
|
568
|
+
# * :uid
|
569
|
+
# * :gid
|
570
|
+
# * :constrain
|
571
|
+
def validate_user(name, expectations, fail_fast=false)
|
572
|
+
users = self.get_users(true)
|
573
|
+
|
574
|
+
if expectations[:ensure].nil? and expectations[:exists].nil?
|
575
|
+
expectations[:ensure] = 'present'
|
576
|
+
end
|
577
|
+
|
578
|
+
if expectations.has_key?(:constrain)
|
579
|
+
expectations[:constrain] = expectations[:constrain].class.eql?(Array) ? expectations[:constrain] : [expectations[:constrain]]
|
580
|
+
|
581
|
+
expectations[:constrain].each do |constraint|
|
582
|
+
fact, expectation = constraint.split("\s")
|
583
|
+
unless meets_constraint?(fact, expectation)
|
584
|
+
@log.info(sprintf('returning true for expectation [%s], did not meet constraint[%s/%s]', name, fact, expectation))
|
585
|
+
return true
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
expectations.delete(:constrain)
|
590
|
+
end
|
591
|
+
|
592
|
+
results = Hash.new()
|
593
|
+
local = nil
|
594
|
+
|
595
|
+
expectations.each do |k,v|
|
596
|
+
case k
|
597
|
+
when :ensure, :exists
|
598
|
+
if users.has_key?(name)
|
599
|
+
if v.to_s.match(/absent|false/).nil?
|
600
|
+
local = true
|
601
|
+
else
|
602
|
+
local = false
|
603
|
+
end
|
604
|
+
else
|
605
|
+
local = v.to_s.match(/absent|false/).nil? ? false : true
|
606
|
+
end
|
607
|
+
when :group
|
608
|
+
v = v.class.eql?(Array) ? v : [v]
|
609
|
+
v.each do |group|
|
610
|
+
local = is_user_in_group?(name, group)
|
611
|
+
break unless local.true?
|
612
|
+
end
|
613
|
+
when :gid
|
614
|
+
if users[name].is_a?(Hash) and users[name].has_key?(:gid)
|
615
|
+
local = v.to_i.eql?(users[name][:gid].to_i)
|
616
|
+
else
|
617
|
+
local = false
|
618
|
+
end
|
619
|
+
when :home
|
620
|
+
if users[name].is_a?(Hash) and users[name].has_key?(:home)
|
621
|
+
local = ! v.match(/#{users[name][:home]}/).nil?
|
622
|
+
else
|
623
|
+
local = false
|
624
|
+
end
|
625
|
+
when :home_exists
|
626
|
+
if users[name].is_a?(Hash) and users[name].has_key?(:home_exists)
|
627
|
+
local = ! v.to_s.match(/#{users[name][:home_exists].to_s}/).nil?
|
628
|
+
else
|
629
|
+
local = false
|
630
|
+
end
|
631
|
+
when :shell
|
632
|
+
if users[name].is_a?(Hash) and users[name].has_key?(:shell)
|
633
|
+
local = ! v.match(/#{users[name][:shell]}/).nil?
|
634
|
+
else
|
635
|
+
local = false
|
636
|
+
end
|
637
|
+
when :uid
|
638
|
+
if users[name].is_a?(Hash) and users[name].has_key?(:uid)
|
639
|
+
local = v.to_i.eql?(users[name][:uid].to_i)
|
640
|
+
else
|
641
|
+
local = false
|
642
|
+
end
|
643
|
+
when :type
|
644
|
+
# noop allowing parse_catalog() output to be passed directly
|
645
|
+
else
|
646
|
+
raise InternalError.new(sprintf('unknown expectation[%s / %s]', k, v))
|
647
|
+
end
|
648
|
+
|
649
|
+
return false if local.eql?(false) and fail_fast.eql?(true)
|
650
|
+
results[k] = local
|
651
|
+
end
|
652
|
+
|
653
|
+
@log.info(results)
|
654
|
+
results.find{|k,v| v.false? }.nil?
|
655
|
+
|
656
|
+
end
|
657
|
+
|
658
|
+
## internal methods
|
659
|
+
private
|
660
|
+
|
661
|
+
##
|
662
|
+
# meets_constraint?
|
663
|
+
#
|
664
|
+
# powers the :constrain value in expectations passed to validate_*
|
665
|
+
# gets facts from node, and if fact expectation regex matches actual fact, returns true
|
666
|
+
#
|
667
|
+
# parameters
|
668
|
+
# * <fact> - fact
|
669
|
+
# * <expectation>
|
670
|
+
# * [cache]
|
671
|
+
def meets_constraint?(fact, expectation, cache=true)
|
672
|
+
|
673
|
+
unless self.respond_to?('facter')
|
674
|
+
# if we haven't loaded puppet.rb, we won't have access to facts
|
675
|
+
@log.warn('using constraints without loading [rouster/puppet] will not work, forcing no-op')
|
676
|
+
return false
|
677
|
+
end
|
678
|
+
|
679
|
+
expectation = expectation.to_s
|
680
|
+
facts = self.facter(cache)
|
681
|
+
|
682
|
+
res = nil
|
683
|
+
if expectation.split("\s").size > 1
|
684
|
+
## generic comparator functionality
|
685
|
+
comp, expectation = expectation.split("\s")
|
686
|
+
|
687
|
+
res = generic_comparator(facts[fact], comp, expectation)
|
688
|
+
|
689
|
+
else
|
690
|
+
res = ! expectation.match(/#{facts[fact]}/).nil?
|
691
|
+
@log.debug(sprintf('meets_constraint?(%s, %s): %s', fact, expectation, res.nil?))
|
692
|
+
end
|
693
|
+
|
694
|
+
res
|
695
|
+
end
|
696
|
+
|
697
|
+
##
|
698
|
+
# generic_comparator
|
699
|
+
#
|
700
|
+
# powers the 3 argument form of constraint (i.e. 'is_virtual != true', '<package_version> > 3.0', etc)
|
701
|
+
#
|
702
|
+
# should really be an eval{} of some sort (or would be in the perl world)
|
703
|
+
#
|
704
|
+
# parameters
|
705
|
+
# * <comparand1> - left side of the comparison
|
706
|
+
# * <comparator> - comparison to make
|
707
|
+
# * <comparand2> - right side of the comparison
|
708
|
+
def generic_comparator(comparand1, comparator, comparand2)
|
709
|
+
|
710
|
+
# TODO rewrite this as an eval so we don't have to support everything..
|
711
|
+
case comparator
|
712
|
+
when '!='
|
713
|
+
# ugh
|
714
|
+
if comparand1.to_s.match(/\d/) or comparand2.to_s.match(/\d/)
|
715
|
+
res = ! comparand1.to_i.eql?(comparand2.to_i)
|
716
|
+
else
|
717
|
+
res = ! comparand1.eql?(comparand2)
|
718
|
+
end
|
719
|
+
when '<'
|
720
|
+
res = comparand1.to_i < comparand2.to_i
|
721
|
+
when '<='
|
722
|
+
res = comparand1.to_i <= comparand2.to_i
|
723
|
+
when '>'
|
724
|
+
res = comparand1.to_i > comparand2.to_i
|
725
|
+
when '>='
|
726
|
+
res = comparand1.to_i >= comparand2.to_i
|
727
|
+
when '=='
|
728
|
+
# ugh ugh
|
729
|
+
if comparand1.to_s.match(/\d/) or comparand2.to_s.match(/\d/)
|
730
|
+
res = comparand1.to_i.eql?(comparand2.to_i)
|
731
|
+
else
|
732
|
+
res = comparand1.eql?(comparand2)
|
733
|
+
end
|
734
|
+
else
|
735
|
+
raise NotImplementedError.new(sprintf('unknown comparator[%s]', comparator))
|
736
|
+
end
|
737
|
+
|
738
|
+
|
739
|
+
|
740
|
+
res
|
741
|
+
end
|
742
|
+
|
743
|
+
end
|